On a PSP, dialog animations advance by animSpeed frames per Update and the
fades take about 200ms. Ours took 500ms (1/30 s per animSpeed, over
FADE_TIME 1.0), and all input was ignored until it finished, which made
dialogs feel sluggish, noticeably so in 30fps games. Input is still
ignored while fading out, once a choice has been made.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sceIoOpen on such a path returns an invalid-argument error on hardware,
not file-not-found (utility/savedata/idlist).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a PSP, dialog init and shutdown happen partly at the accessThread
priority and partly at the graphicsThread priority, one phase after the
other. Model that with one helper thread that switches priority per phase,
and let starting it reschedule normally instead of disabling interrupts.
A caller with worse priority than both now sees shutdown complete inside
ShutdownStart, as on hardware. NFL Street 3 (graphics 17, access 19,
caller 111) calls the next InitStart right after ShutdownStart and used to
loop forever on 'A save request is already running' (#19957). The utility
pspautotests, where the caller has better priority, are unchanged.
Also logs the dialog thread priorities at debug level.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An invalid value (e.g. --debugger-run swallowing the next flag) printed an
error but returned Exit, so the process exited 0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Same shape as the NEON one, which now shares its rounding tail. SSE2 has
no per-lane shift, so the alignment shift is a multiply by a power of two
built from float bits and converted by truncation; the unsigned maxima
use the 16-bit instructions, since every value involved fits in 15 bits.
Nothing depends on the host rounding mode or flush-to-zero.
Checked against the reference by VFPUDot and on 300M more inputs offline,
also with MXCSR set to round toward zero with FTZ and DAZ.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The four lanes are computed together: exponents, 24x24-bit products with
round-to-odd, alignment by truncation and a signed horizontal sum. One
pairwise maximum finds both the alignment exponent and any inf or NaN,
which go to the reference. The final rounding is branch-free and in
integers, since the host rounding mode may be the game's.
About three times the throughput of the reference on Apple M-series
(5.2 vs 15.2 ns per call). VFPUDot checks it against the reference on
four million inputs picked to cover cancellation, ties, subnormals and
the overflow edges; a billion more matched offline.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
videos_ only learns about the CSC output, so the texture we actually draw is
just an ordinary 512x512 8888 texture whose contents happen to be different
every frame: hash, miss, throw it in the secondary cache, rebuild, forever.
So track the copy. NotifyVideoCopy marks the destination as video when the
source is, and the copy funnels call it: sceDmacMemcpy, sceKernelMemcpy, and
the four replaced memcpy/memmove variants. It sits outside their "is either
side VRAM" gate, since a RAM-to-RAM copy of a frame is still a frame.
Being a video texture also gets it forced linear filtering and keeps it out of
texture upscaling, which is what you want for a video either way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
x86 (SQRTSS and libm alike) returns 0xffc00000 for it, the PSP 0x7fc00000.
-0 stays -0 and a NaN input comes through as it is, so only a negative
input needs the sign cleared: a compare and an xor in the x64 JITs, a
branch in the interpreters. cpu/fpu/roundmode.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The C cast is undefined past the int32 range, and x86 makes it INT_MIN, so
round/trunc/ceil/floor/cvt.w.s of anything from 2^31 up gave 0x80000000 on
x86 hosts while the PSP saturates to 0x7fffffff (cpu/fpu/roundmode). Route
all of them through SaturatedFloatToInt, which also covers NaN and inf, and
drop the special cases that did.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The second pack read a lane the first one had just written
(vi2s.q C002, C000). Pack into temps when the outputs overlap the inputs.
Found by the corrected cpu/vfpu/overlap.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
EN/NN compared the source against an uninitialized temp, usually the
previous lane's all-ones mask (a NaN), so every lane came out as NaN. NI
used a less-than compare, which is false for a NaN. TR set the bit and
then replaced it with a garbage bit from the temp. Found by
cpu/vfpu/specials.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
In the interpreter, the IR interpreter, every backend's FSign and the x86
JIT's own vsgn. Recorded in cpu/vfpu/specials.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The same ordering as vmin/vmax (sign and magnitude, denormals compare as
zero), and on a tie vsrt1/2 keep the lower lane of each pair, vsrt3/4 the
upper one. Recorded in cpu/vfpu/specials.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
vf2h truncates the mantissa (no rounding), gives a signed zero below 2^-14
and a signed inf from 65536 up, and keeps the low ten mantissa bits of a
NaN, so one with those clear becomes inf. vh2f flushes a subnormal half to
a signed zero and keeps inf/NaN mantissa bits unshifted. The x86 JIT's own
vh2f gets the subnormal flush; the other backends go through the
interpreter. Recorded in cpu/vfpu/specials.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The other control registers were masked already; the CC path wasn't, so
a value with bits 6 or 7 set made bvt 6 and bvt 7 branch. Hardware never
sets those (cpu/vfpu/vbranch), and the IR masks them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The NaN check compared src1 with itself, so only a NaN in src1 reached
the slow path with the hardware's integer-compare rule. A NaN in src2 went
to MINSS/MAXSS, which just return it. Compare src1 with src2, as the arm64
backend does. Caught by cpu/vfpu/minmax.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The same set of gates the arm64 JIT got: vabs/vneg, vsat1, the vrcp
group, vzero/vone/vidt, vdiv, vscl, vfad/vavg, vrot, vuc2i, and now also
vcrs/vcrsp/vqmul, vbfy and vtfm, which the IR frontend hands to the
interpreter as well. Swizzles naming a lane past the op's size go there
too. mtvc now keeps only the bits the hardware does (six for CC, the low
20 of a prefix, 0x3F8 on top of the RNG state). vcst's SIMD path skipped
the D prefix.
Fixes cpu/vfpu/prefix_ctrl on Windows CI. Verified on an x86_64 build
under Rosetta: prefix_ctrl and the full tests_good pass, prefix_consume
is down to the same three ops that differ on every backend.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mtvc to RCX0-7 keeps the low 20 bits of the value and forces the top
twelve to 0x3F8 (cpu/vfpu/vrnd), which is also the form vrnds and vrndi
leave them in. We masked with 0x3FFFFFFF. The generator itself matches
hardware for every seed and state in that test.
Interpreter, IR and the arm64 JIT. The x86 and ARM32 JITs don't mask
control register writes at all and are left as they were.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The arm64 JIT applied the plain prefix rules to every op. Several ops
don't follow them, and the interpreter and IR frontend know which:
- vabs and vneg are vmov with the abs/negate bit forced, so a negate in
the S prefix doesn't negate twice.
- vsat1 doesn't apply the D saturation.
- vrcp, vrsq, vsqrt, vnrcp (and the rest of that group) apply S and D to
the last lane only, from prefix position 0.
- vzero, vone and vidt are changed by a pending S prefix.
- vdiv, vscl, vfad/vavg and vrot each have their own rule.
- vuc2i and friends with an S prefix.
Hand these to the interpreter, exactly where the IR frontend does. Also
mask what mtvc writes to a control register (the low 20 bits of a prefix,
12 of the D prefix), as the IR already did.
Goes from 19 ops differing from hardware in cpu/vfpu/prefix_consume to
the 3 that differ on every core, and clears cpu/vfpu/prefix_ctrl.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
IsPrefixWithinSize only looked at prefix positions past the size, not at
in-size positions naming a lane past it, so vadd.p with [z, w, ...] was
compiled with whatever the prefix code substitutes. Hardware writes those
result lanes as zero (cpu/vfpu/prefix_ctrl), which the interpreter does,
so send them there.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
vrcp, vrsq, vsin, vcos, vexp2, vlog2, vsqrt, vasin, vnrcp, vnsin, vrexp2
and vdiv apply the S prefix to their last lane only, taking it from prefix
position 0. When that names a lane other than x, we fed the op an
infinity, which happens to give the right answer for vrcp and vexp2 and
the wrong one for the rest. Hardware writes the lane as zero regardless
of the op (cpu/vfpu/prefix_sat, where vlog2 and vsqrt give zero too),
which is also what RetainInvalidSwizzleST already does for the other ops.
Covers the IR path as well, which hands these cases to the interpreter.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The rule is LO = numerator <= 0xFFFF ? 0xFFFF : -1, unsigned. The IR
backend used CC_LE, so any numerator with the top bit set counted as
small and got 0xFFFF. The other backends compare unsigned. Also caught by
cpu/cpu_alu/cpu_div.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every path special-cased the one signed division overflow and set the
remainder to -1. cpu/cpu_alu/cpu_div, recorded on a PSP, says it's 0.
The classic arm64 JIT was the only one that got it right, by not
special-casing it at all.
arm64, RISC-V and LoongArch all produce INT_MIN with remainder 0 natively,
so their fixup blocks go away. x86 has to keep the check (IDIV traps) but
sets HI to 0 now, and so do both interpreters.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RiscVRegCache::FlushBeforeCall claimed the caller-saved X and F registers
"match between X0 and F0". They don't: X0-X4 are zero/ra/sp/gp/tp and are
never allocated, but F0-F4 (ft0-ft4) are caller-saved and are in the FPR
allocation order. A block that spilled into them and then called out (for
vsin or vcos, say) had the callee destroy registers the cache still
thought were live and dirty. LoongArch and arm64 get this right.
The LoongArch signed div-by-zero sequence applied its ADDI_D(-1) after
the jump target, so it ran on both paths: a negative numerator ended up
with a quotient of 0 instead of 1, and a non-negative one borrowed into
the remainder in the high half, giving hi = num - 1. Insert the -1 into
the low word rather than subtracting, and branch around the other case.
FCvtScaledWS loaded 0x7FFFFFFF into SCRATCHF1 and then immediately
overwrote it with the multiplier, while the FSELs picked SCRATCHF2 - a
register the function never writes. A NAN input produced whatever was
left there.
Also:
- RISC-V had no SyscallUnresolved case, falling through to INVALIDOP,
which is a live assert in release builds. Added it, matching arm64.
- applyRoundingMode_ fed fcr31 straight to FSRM, which only takes three
bits - the flags at 2-6 could turn a mode of 0 into 4. Mask with 3
first, as MIPS.cpp and LoongArch64Asm.cpp do.
- OverwriteExit lacked arm64's assert that the exit fits the 8-byte hole,
which matters more here since QuickJ is 4, 8 or 12 bytes.
- Both backends allocated the FMin/FMax temp GPR inside the conditional
NAN path, where a spill store would only run on one side while the
regcache assumed it always ran. Hoisted above the branch.
Both backends build now, and pspautotests and the unit tests pass under
qemu on each - but nothing in the suite reaches the paths above, so the
two worth checking were checked by running the emitted sequences
directly. The div-by-zero one is wrong for all eight numerators tried and
right for all eight after. The rounding one turns a guest mode of 0 into
RMM whenever fcr31 has its inexact flag set, so 0.5 converts to 1 rather
than 0. The rest are still by inspection: they need register pressure, an
unresolved import, or a debug build to reach.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two bugs stacked on each other, and between them every vector path in
this backend was either dead or miscompiled.
The register cache declared mapFPUSIMD unconditionally, but every
compiler here picks its path from cpu_info.LOONGARCH_LSX at runtime.
Without LSX the scalar fallbacks ran against a SIMD mapping and reached
lanes with F(reg + n), which addresses nothing there - ApplyMapping only
allocates per-lane registers when mapFPUSIMD is false. Vec4Unpack8To32
and Vec4DuplicateUpperBitsAndShift1 came out with only their first lane,
Vec2Unpack16To32 put both halves in one register, and the pack ops read
back whatever was next door. riscv64 sets the flag false and has never
had the problem. Tie the two together and the fallbacks are correct as
they stand.
That path was reachable because of the second one: the USE_CPU_FEATURES
block assigned over the hwcaps, and GetLoongArchInfo reads /proc/cpuinfo
and nothing else. Under qemu-user that file belongs to the host, so it
found no features and turned LSX off - leaving the LSX paths dead and the
untested scalar ones running, which is not what any Loongson 3A5000 or
later does. Let it only ever add to what the hwcaps found.
cpu/vfpu/convert, gum and matrix pass now, both with LSX and with it
masked off, putting loongarch64 at 338/342 - the same four as riscv64.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ApplyHostRoundingMode and RestoreHostRoundingMode only had SSE2 and ARM64
branches, so everywhere else the guest's rounding mode was silently
ignored and ceil and floor rounded to nearest. Fall back to fesetround,
which covers riscv64 and loongarch64 and whatever comes next.
That clears both rounding mismatches in cpu/fpu/fpu on those two. What's
left there is flush-to-zero, which neither ISA has any control for, so
the test still fails - a denormal result survives where the PSP would
have flushed it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
--disable-hle had no counterpart, which made "is this our fault or the game's?"
awkward to answer: the only ways to put our HLE back were a per-game config or
hiding the firmware, and neither works from a script - the setting is per-game
and the firmware gets found anyway. --force-hle takes the same bitmask and runs
our HLE for those libraries even where the real module is now the default, so
the same repro can be run both ways and the logs diffed.
Used it on the warnings left over in Tekken 6 under the real mpeg.prx. Three of
them appear identically with --force-hle=16, so they are the game's own and
match what the hardware answers: sceKernelChangeThreadPriority(-1) eight times
in a row (pspautotests/threads/threads/change says hardware returns
UNKNOWN_THID for -1 too), sceAtracAddStreamData on a released id with a zero
byte count, and a sceKernelDeleteMutex on a garbage id.
The fourth only happens under the real module and is ours. mpeg.prx sizes its
allocation through a scratch context in its own bss and calls
sceAudiocodecReleaseEDRAM on that one, while decoding through a different
context that never gets released - so the next movie's sceAudiocodecInit finds
a live decoder and replaces it. That is once per video on every game running
the real module, and it was a WARN_LOG_REPORT, so it would have reported from
everyone's machine. It is bounded - removeDecoder deletes the old one and Init
makes exactly one more - so it is an INFO_LOG now.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both of these fire once per video in Tekken 6, and neither is a fault.
sceAudiocodecReleaseEDRAM warned "failed to remove decoder" whenever there was
no decoder to remove. There usually isn't: mpeg.prx calls CheckNeedMem and
GetEDRAM to size the allocation, and only creates a decoder if the stream turns
out to need one, so releasing without ever having made one is the normal path.
Demoted to debug.
While there, its signature was one argument too long. audiocodec_260.prx's own
sceAudiocodecReleaseEDRAM (080007c0) reads only a0 and sets up a1 through a3
itself, so the "id" we took was whatever the caller happened to leave in the
register - which is how the log came to show an sceMpeg error code as the
second parameter of an audio call.
sceUtilityLoadModule logged MODULE_ALREADY_LOADED at error level. It is a
normal answer that games rely on: Tekken 6 asks for av_avcodec three times and
never unloads it, ignoring the result each time. Only that one code is demoted;
everything else from a module load is still an error.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tekken 6 never reaches gameplay with the real mpeg.prx: it plays its intro
movie, returns to the title screen, starts loading a demo match and loads
forever. With the sceMpeg HLE it plays fine.
The game unloads its video libraries before the level load and expects the
memory back. It gets most of it - scePsmf and scePsmfPlayer do go away - but
mpeg.prx stays resident, 33KB of it, sitting in the middle of the region the
loader then asks for:
08c64000 - 09e24000 18.6MB taken UserSbrk
09e24000 - 09ed4000 720KB free
09ed4000 - 09edc300 33.5KB taken ELF/sceMpeg_library
09edc300 - 09f44000 425KB free
09f44000 - 09f4c000 32KB taken UtilityModule/302_av_atrac3plus
0x09f44000 - 0x09e24000 is 0x120000, which is exactly the allocation that
fails. Without mpeg.prx in the way that span is one free block and the level
loads.
sceUtility notifies the per-library hooks with state 1 when a utility module is
loaded and -1 when it is unloaded. The hooks that swap in a firmware module
only ever handled the load, so nothing ever took them back out. That affects
sceMpeg, sceMp3, sceMp4 and sceAtrac alike; Tekken is just the game whose
memory budget is tight enough to notice.
The unload has to take out what we put in and nothing else, so the loaded ids
are remembered rather than looked up by name: a game like Death Jr ships its
own mpeg.prx and loads it itself, and freeing that would be freeing the game's
memory. Verified that Death Jr still decodes its 1033 frames with our loader
never touching its module.
Savestates from before this have no record of what was swapped in, so they keep
the old behaviour of leaving the modules loaded rather than risk freeing
something the game owns.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reinitialize() wiped the 64 display lists but kept the queue of their ids.
Whatever the old executable still had queued came back as lists with no state
and a pc of 0, behind the first list of the new executable, where they
blocked everything. Crazy Taxi: Fare Wars is a launcher for its two games,
and stopped at a black screen that way. Fixes#19894.
This removes the workaround for it, which dropped such a list but returned
before currentList was cleared, and only worked as long as something else
happened to clear it later. A list with a bad pc is now dropped like one that
ran into an error, instead of sitting at the head of the queue for good.
Also narrows what sceGeBreak(1) throws away to interrupts that have actually
been raised, which is what gpu/ge/intrsuspend shows. The ones we haven't
raised yet are only late because we execute lists ahead of time: a game that
breaks right after its last list, and then waits for what the finish callback
signals, got that callback long ago on hardware.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Resetting the GE also gets rid of an interrupt that was raised but not taken
yet, so a list that reached its FINISH just before never gets its finish
callback. We delivered one anyway, for a list that no longer existed. If the
break comes from inside a GE callback, the interrupt being handled is kept,
since its handler still has to return.
Found by gpu/ge/intrsuspend, which also confirms from a thread, with
interrupts suspended, that nothing moves along the queue until the FINISH
interrupt has been taken.
Savestates: bump GPUCommon to 7. We didn't use to mark a PAUSE signal as
delivered, which sceGeContinue now goes by, so a state saved with a list
paused that way would load into a game that could never continue it. Fixed
up on load.
gpu/signals/handlercalls goes in as known failing: with an old SDK version, a
stall address set from inside a SUSPEND callback doesn't reach the GE, which
we can't express with just the one stall address per list. See docs/sceGe.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On hardware the GE stops at every SIGNAL and FINISH, and it's the interrupt
that gets it going again: on the same list after a signal, on the next one
after a FINISH - once the finish callback has run, with the finished list
still at the head of the queue. We ran the next list right away and dropped
the finished one at once, so a finish callback saw an empty queue. A list
enqueued from there was started instead of queued, and then couldn't be
dequeued, which hung the new gpu/ge/queue2 test.
ProcessDLQueue() now runs nothing while the head of the queue has an
interrupt pending, and InterruptEnd() is what takes a finished list off the
queue. This also keeps a stall update from restarting a list that's stopped
at a signal before the handler has run. drawCompleteTicks is still set when
the last list reaches its FINISH, so a sceGeDrawSync in between doesn't wait.
Other things gpu/ge/queue2 and gpu/ge/breakwait showed, all from a real PSP:
- sceGeListEnQueue compares against the address a list was enqueued with
(or stopped at by sceGeBreak), mirrors included, not against its current pc.
We had that the wrong way around.
- The stack-in-use check only applies to lists that have started executing.
This is probably what IgnoreEnqueue was added for (Metal Gear Acid 2,
#10906). The flag stays until someone has checked the game without it.
- A PAUSE signal makes the list PAUSED at once, before the FINISH delivers it.
In between, sceGeContinue and sceGeBreak say BUSY, and updating the stall
address does nothing, so a list that stalls there is stuck.
- A completed list can't be dequeued, with or without a context.
- sceGeDrawSync(1) looked at currentList instead of the list it had found.
- sceGeBreak(1) doesn't wake anyone, and a late interrupt for a list it reset
no longer marks that list completed. Threads in sceGeDrawSync are woken
before the ones waiting for the last list.
Also fixes currentList being lost when loading a state where it's list 0,
and makes ge_pending_cb a plain std::list - nothing else touches it, and the
GPU thread it was shared with is long gone. Same savestate format.
See docs/sceGe.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comp_Vmfvc read vfpuCtrl[] straight from the context in all four
backends, while the mfvc path in Comp_Mftv flushes first, with the
comment "In case we have a saved prefix" - so "vpfxs X" followed by
"vmfvc sN, $128" returned the stale value in memory. The IR frontend
flushes only for the three prefix registers, which is the tighter form,
so vmfvc does the same there.
On ARM and ARM64 the fix alone wouldn't have been observable: those two
map the destination with no flags, leaving isDirty false, so the flush
dropped the loaded value without storing it. x86 already passes
MAP_DIRTY | MAP_NOINIT. That part is a fix of its own, but the two are
inseparable in this function - a vmfvc that reads the right value and
then throws it away is no better.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
InvalidateICache widened its search by MAX_BLOCK_INSTRUCTIONS, but
block_map_ is keyed by byte addresses - a block can span four times that
many bytes. A block longer than 0x4000 bytes starting just below the end
of the invalidated range could therefore be missed and left behind after
the code under it changed. Multiply by 4.
DestroyBlock's "invalid original address" safety check returned after
setting b->invalid but before UnlinkBlock() and the checkedEntry
poisoning, so other blocks kept jumping directly into a block the cache
had already written off. The check is there to guard the memory access
that restores the original opcode, so only guard that.
Also: codeSize is a byte count assigned from a full pointer difference,
so a block over 64KB truncated it (affecting GetAddressFromBlockPtr and
the disassembly view); widen it to u32. The RAMTOP range check missed a
block sitting exactly on the user-memory midpoint, since the RAMBOTTOM
check next to it is strict. And GetBlockDebugInfo disassembled one
instruction past the end of the block.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The vertex decoder JIT was enabled only when g_Config.iCpuCore was one of the
JIT cores, which tied an unrelated GPU path to the CPU setting. Headless leaned
on that by forcing iCpuCore to the interpreter after ApplyToConfig(), to keep
the decoder JIT off in the tests.
Add CoreParameter::bUseVertexDecoderJit instead. Standalone and libretro set it
wherever the host can JIT, so IR interpreter users on such hosts now get the
decoder JIT too. Headless sets it false, which keeps the test results as they
were and lets --cpu go through ApplyToConfig() like every other option.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>