Commit Graph
49612 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 9be0dffc41 Translations: The game-or-video choice for mixed UMDs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:58:15 +02:00
Henrik RydgårdandClaude Opus 5.5 6a1b802e09 UI: Shorter label for the video choice on a mixed disc
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:56:43 +02:00
Henrik RydgårdandClaude Opus 5.5 dc41268bfc Firmware: Ignore empty updaters on discs
UMD Video discs ship an updater PARAM.SFO next to a 0-byte DATA.BIN, and PSN releases zero-fill it.
MountedDiscHasUpdater() only checked that DATA.BIN existed, so booting the EUR Video Demo Disc's
game with no firmware installed started an auto-install of "2.00" that failed with an OSD error.
It now wants a PSAR archive, like ReadBundledUpdateInfo() already did. The game info screen no
longer looks for an updater on UMD Video discs at all.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:55:27 +02:00
Henrik RydgårdandClaude Opus 5.5 c1ec2044db Game list: Don't draw the file type icon on top of a game's picture
UMD Video discs, PS1 EBOOTs and frame dumps have both a picture and a type icon, and the icon and
title were drawn over the picture, showing up as a square in the middle of it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:46:59 +02:00
Henrik RydgårdandClaude Opus 5.5 d331bd6c8c UI: Play UMD Video discs in the XMB, straight from the game list
With firmware 2.71 or later installed, picking a UMD Video disc boots the XMB with the disc in the
drive and UMD auto-start forced on for that boot, so the disc's menu comes up. Without one, the
info screen saying UMD Video isn't supported shows as before. The info screen's Play does the same.

A disc with both a game and video asks which to start. For the video, the XMB comes up with
auto-start forced off, since auto-start runs the game.

Older firmware can't play these: 1.50, 2.00 and 2.60 crash in video_plugin or paf with a video disc
in the drive, and 2.50 refuses it. 2.71 through 6.61 reach Bourne Supremacy's menu.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:46:59 +02:00
Henrik RydgårdandClaude Opus 5.5 bf1f88d6e6 UMD: Tell video discs from game discs by UMD_DATA.BIN, and fix eject+insert while paused
- Fixes #22737

Some video discs (Spider-Man) have a PSP_GAME holding only a firmware update. We reported them as
game discs too, and the XMB said "The disc could not be read". The type now comes from the fourth
field of UMD_DATA.BIN (G, V, A; the EUR Video Demo Disc says GVA), with the directories as a
fallback, and PSP_GAME without SYSDIR/EBOOT.BIN doesn't make a mixed disc a game. Identify_File uses
the same check, so these discs list as UMD Video.

Ejecting and inserting a disc from the pause menu without resuming in between left the old disc in
the XMB: callback notifications don't queue, so the insert's argument replaced the eject's before
the drive callback ran. The insert now waits until the callback has seen the drive empty.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 10:46:59 +02:00
Henrik Rydgård 902e5ccd8c Merge pull request #22738 from hrydgard/android-tv-output
Android TV mode, and an "External display" setting (iOS and Android)
2026-10-23 00:00:12 +02:00
Henrik RydgårdandClaude Opus 5.5 f795f7e07f UI: A press and release in one event is a press only for the top screen
Key ups go to every screen, so keys don't get stuck, and that included
events that are a press and a release at once, like iOS's back swipe. So
with settings open from the pause menu, a back swipe made the pause
screen try to close too. It isn't on top, so finishDialog refused, but
the screen had already stopped taking input, and once settings closed it
was left on top, ignoring everything.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:16:47 +02:00
Henrik RydgårdandClaude Opus 5.5 da33eab776 Outputs: Defensive checks, and don't wait for a Metal drawable twice in a frame
- Metal: when a layer has no drawable to give (it can take a second to
  say so), don't ask again in the same frame for every pass to it.
- Swapchains other than the primary are created, resized and destroyed
  between frames (Metal asserts it), and destroyed with the last
  reference to them (ReleaseAssertLast, Metal and Vulkan).
- BindFramebufferSamplesAsTexture asserts it isn't given a swapchain,
  like BindFramebufferAsTexture.
- An offscreen Metal swapchain can't be resized, asserted.
- VulkanRenderManager asserts that no additional swapchain outlives it.
- Native_RemoveOutputAndWait asserts it isn't called on the thread that
  handles the requests, where it would always time out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:08:52 +02:00
Henrik RydgårdandClaude Opus 5.5 ba55ee7e5f Screenshots: Take the game image while the game is on a TV
An output screenshot reads back the device's own screen, which only has
the touch controls on it then.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:07:10 +02:00
Henrik RydgårdandClaude Opus 5.5 080470d041 Display layout: No editing while the TV shows a layout for another orientation
With the game on a TV (landscape) and the device in portrait, the display
layout screen edited the portrait layout, which the TV doesn't use, so
nothing seemed to change. Its controls are now disabled in that case, and
work as before with the device held the same way as the TV.

The screen rotation hotkeys change the layout the game is shown with, the
TV's, instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:06:07 +02:00
Henrik RydgårdandClaude Opus 5.5 b642984755 Outputs: Decide on the TV mode by the running backend
iOS and Android checked the backend setting, which a change sets right
away while the restart prompt is still up. A TV connected then got an
output OpenGL can't draw to, and showed black instead of mirroring.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:04:15 +02:00
Henrik RydgårdandClaude Opus 5.5 ea26b3c7dd SDL: Quit when the main window is closed, with --game-window open too
SDL sends a quit only when the last window closes, so with the game
window open, closing the main one did nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:04:01 +02:00
Henrik RydgårdandClaude Opus 5.5 013460b2fe Outputs: No Cardboard VR on a TV
The design doc said Cardboard VR turns off on a Game output, but only the
config was checked, so with it on the TV got the split screen too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:03:43 +02:00
Henrik RydgårdandClaude Opus 5.5 771e40e297 Vulkan: Recreate an additional swapchain that stays suboptimal
Only out of date and a lost surface recreated one, so a TV whose mode
changed without a resize (MoltenVK reports SUBOPTIMAL when the layer's
size no longer matches) kept a stretched picture. Like the main
swapchain, it's now recreated after a few SUBOPTIMAL presents in a row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:03:16 +02:00
Henrik RydgårdandClaude Opus 5.5 22aa12d047 iOS: Keep the TV's layer until its output's removal has been handled
The add request holds the layer unretained, and the swapchain only takes
a reference once it's handled. If the TV disconnected (or the setting
changed) before that, the window and the layer were freed with the add
still queued. The view controller now keeps a reference for the requests,
released after Native_RemoveOutputAndWait, off the main thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:02:36 +02:00
Henrik RydgårdandClaude Opus 5.5 be4bdf3590 Android: Show the game on the TV again after the system dismisses it
Android dismisses a Presentation by itself on some display changes, like
a mode switch right after plugging in. We kept the dismissed one, so the
TV went back to mirroring until it was replugged. Its dismiss listener
now drops it and checks again, and so does a change to any display other
than the device's own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:01:03 +02:00
Henrik RydgårdandClaude Opus 5.5 5799bdee59 Outputs: Rebuild the post-shader chain when the game moves to another output
Post-shader framebuffers (for shaders at output resolution, and those
that keep the previous frame) are sized for the output they were built
for. Moving the game to a TV or back, or the TV changing size, left them
sized for the other one. SetOutputOverride now asks for a rebuild when
the output changes.

Also corrects the design doc: "auto" render resolution follows the
device's screen, not the TV.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:00:13 +02:00
Henrik RydgårdandClaude Opus 5.5 3e53d4bde1 Outputs: Don't let a removal skip the wait while the graphics come back
The outputs were only marked live after deviceRestored(), so a
Native_RemoveOutputAndWait() that came in while it was recreating their
swapchains returned at once. On Android the surface could then be freed
while a swapchain was being made on it (unplugging the TV as the app
returns to the foreground). Mark them live before the queue is
processed, so such a removal waits for the next frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:59:29 +02:00
Henrik RydgårdandClaude Opus 5.5 a0e0115e65 NativeApp: Say why output requests have their own queue
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:44:16 +02:00
Henrik RydgårdandClaude Opus 5.5 d8f9ffc49f Translations: External display setting, and the TV mode messages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:31:07 +02:00
Henrik RydgårdandClaude Opus 5.5 04ae3fea5b External display setting: Show the game, or mirror the screen
Graphics > "External display" on iOS and Android. "Show the game" (the
default) is the TV mode, "Mirror the screen" leaves the TV to the system.
A change applies right away. It's disabled on backends that can't draw to
a second swapchain.

On Android 12 and up, a second screen that's part of the device (like a
dual-screen handheld's) isn't used, so the game doesn't move there by
itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:31:07 +02:00
Henrik RydgårdandClaude Opus 5.5 5fbd134a7d Android: Show the game on a second display, with the controls on the phone
A presentation display (HDMI or USB-C video out, some wireless displays,
or the simulated ones in Developer options) gets a Presentation with a
SurfaceView, whose surface becomes a Game output, when the backend is
Vulkan. Otherwise Android keeps mirroring.

The surface mustn't be touched after surfaceDestroyed returns, so the
output is removed with the new Native_RemoveOutputAndWait(), which
returns once the render thread has let go of it (or right away if the
graphics are down). The UI drawn on an output now leaves out the main
swapchain's pre-rotation, as the game already did.

"In menus on %1" gets the device name from Settings.Global.DEVICE_NAME.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:31:07 +02:00
Henrik Rydgård f6fa84329e Merge pull request #22736 from hrydgard/avc-spliced-stream-timing
AvcDecoder: Keep the output clock going when a new stream is spliced on
2026-10-22 22:14:54 +02:00
Henrik Rydgård 79cedbc13f Merge pull request #22734 from hrydgard/vulkan-stale-extent-workaround
Android Vulkan: Bring back the stale surface extent workaround
2026-10-22 21:52:20 +02:00
Henrik RydgårdandClaude Opus 5.5 56c8d067af pspautotests: avchold covers a stream spliced on without a flush
Updates the submodule for video/mp4/avchold's ref3b1hrdsplice, recorded
on a PSP, which fails without the AvcDecoder fix on this branch: the pictures after
the splice never come out.

avchold and decodeend only test anything with Sony's mpeg and mp4
modules running, which headless never does for homebrew unless asked,
and which CI doesn't have. docs/pspautotests.md now says how to run
them (--disable-hle=0x110 and a memory stick with a firmware dump).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:47:45 +02:00
Henrik RydgårdandClaude Opus 5.5 ddfd3bcfb9 CmdLine: Take integer parameters in hex too, and refuse trailing junk
They were read with sscanf("%d"), which stops at the first character it
can't use, so --disable-hle=0x110 was accepted as 0 and quietly ran
everything on our HLE. Now decimal or 0x hex, optionally signed, and the
whole value has to parse. Up to 0xFFFFFFFF is taken, for full masks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:47:39 +02:00
Henrik Rydgård 1796c78a07 Merge pull request #22735 from hrydgard/multi-swapchain-4
Multi-swapchain support for Vulkan (iOS TV mode, --game-window on Mac, Linux and Windows)
2026-10-22 21:46:32 +02:00
Henrik RydgårdandClaude Opus 5.5 6c8c71a125 AvcDecoder: Keep the output clock going when a new stream is spliced on
Burnout Legends feeds its intro movies to one decoder back to back, with
no flush in between. Each new stream opens with a buffering period whose
cpb_removal_delay is 0, which put our reconstructed clock 18 frames back,
so every picture after it was held until the clock caught up, and then
20 came out of one decode. sceMpegAvcDecodeYCbCr converts every picture
a decode reports into the caller's buffer list, which only has room for
one, so mpeg.prx went on through stack garbage and crashed as the title
screen came up.

Such a buffering period now continues from the last access unit instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 21:02:36 +02:00
Henrik RydgårdandClaude Opus 5.5 c542d186bd docs: Multi-swapchain status: Linux and Windows, no OpenGL
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:55:52 +02:00
Henrik RydgårdandClaude Opus 5.5 f8e7efc8b0 Android Vulkan: Check the surface extent against the latest window size
g_display lags behind surfaceChanged until the render thread applies the
resize, and a recreate after a suboptimal present can run in between.
The check then swapped a correct extent for a frame. Android now sets the
draw size callback to the latest size from backbufferResize, and the
check uses that.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:49:08 +02:00
Henrik RydgårdandClaude Opus 5.5 5009724d91 Windows: --game-window, on Vulkan
A second Win32 window, added as a Game output like the SDL one. Keys
typed into it still go to the emulator; the mouse doesn't. It's for
testing multiple outputs (standing in for a TV over AirPlay) until real
multi-window support takes it over.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:41:35 +02:00
Henrik RydgårdandClaude Opus 5.5 82e6ac86f0 Vulkan: Balance the debug label of a swapchain pass that has no image
A swapchain pass closes its step label and opens one in presentCmd, but
only if it gets an image. With the main window minimized it doesn't, and
the end of the step closed a label that wasn't open, in presentCmd if an
extra swapchain's pass had moved recording there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:41:35 +02:00
Henrik RydgårdandClaude Opus 5.5 ea5e39c440 Android Vulkan: Bring back the stale surface extent workaround
e32e558aab dropped it, assuming the stale extent came from our own resize
race. A Redmi 9A (PowerVR, Android 11) shows it doesn't: after a rotation,
vkGetPhysicalDeviceSurfaceCapabilitiesKHR returns the new transform with the
old extent, even when queried after surfaceChanged and ApplyResize have
given us the new size, and the wrong swapchain then stays until the next
rotation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:29:21 +02:00
Henrik RydgårdandClaude Opus 5.5 76d0d37941 Outputs: Vulkan too, for --game-window and the iOS TV mode
The SDL game window gets its Vulkan surface the same way the main window
does, so it should work on Linux too. Windows and OpenGL aren't done.
EmuScreen asks the draw context for the output's size, which follows a
recreated Vulkan swapchain.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:11:36 +02:00
Henrik RydgårdandClaude Opus 5.5 8e8cf5dbb4 Vulkan: Multiple swapchains
Swapchains other than the main one are VKRSwapchains, owned by the
render manager, each with its own surface, image views, framebuffers
and depth buffer, in the main swapchain's format so the backbuffer render
pass and pipelines work for all of them. A render step with no
framebuffer can name one.

They don't pace the frame: acquiring doesn't wait, and the pass is
skipped if there's no image. The frame's last submit waits on and
signals the semaphores of every swapchain acquired that frame, and each
is presented after the main one. When one goes out of date, it's
recreated at the start of the next frame.

The main swapchain's path is unchanged; frametests on Vulkan match.
VulkanContext::CreateSurface is the old per-platform surface code, now
also used for the extra ones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:11:36 +02:00
Henrik Rydgård 47e67cb122 Merge pull request #22728 from hrydgard/multi-swapchain-3
Multi-swapchain (multi-window rendering) support, for Metal
2026-10-22 20:09:32 +02:00
Henrik Rydgård ba26373cb1 Merge pull request #22731 from hrydgard/zuma-analog
Zuma: Correct aiming algorithm to behave better with analog sticks
2026-10-22 20:03:04 +02:00
Henrik Rydgård 8485e8ebc0 Merge pull request #22733 from hrydgard/appimage-headless
AppImage: Also package PPSSPPHeadless, as an AppImage of its own
2026-10-22 20:02:45 +02:00
Henrik RydgårdandClaude Opus 5.5 ba54d88d16 AppImage: Also package PPSSPPHeadless, as an AppImage of its own
Built in the same job, uploaded with the PPSSPP AppImage and attached to
releases. Its .zsync isn't shipped, since already-installed PPSSPP
AppImages update from any *<arch>.AppImage.zsync in the release. The
PPSSPP AppImage's own pattern now starts with PPSSPP- for the future.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:40:03 +02:00
Henrik RydgårdandClaude Opus 5.5 b3c44cba8c Zuma: Correct aiming algorithm to behave better with analog sticks
Zuma is a pretty bad port from PC and has really weird aiming math,
clearly partially derived from the mouse aiming code.

- Each stick axis got its own dead zone (7864 of 32767), so the angle
  bent towards the nearest axis, and at half deflection anything within
  about 15 degrees of an axis was on it.
- The aim puts a cursor 450 pixels out along the stick and points the
  frog at it, but only moves a cursor coordinate when that axis is
  non-zero. With an axis in its dead zone, or the stick straight along
  the other one, the frog pointed at wherever the cursor had last been:
  full right after a diagonal aimed 27 degrees off.
- The axes are read as 127 minus the PSP's value, so a stick at rest
  reads -1, which the old dead zone hid.

Two hooks: one gives the left stick a circular dead zone of the same
size, centred on 128, once the game has stored its floats; the other
moves a cursor coordinate that wasn't updated to the frog's own.
Reading the angle the game sets for 24 directions at full, 50% and 45%
deflection, it now matches the stick's to within 0.6 degrees, the
stick's 8-bit steps. Before, it was off by up to 27 degrees.

The aim still ignores the stick below about 43% of its travel: the
game's own minimum length on top of the dead zone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:31:54 +02:00
Henrik Rydgård ac8976fc71 Merge pull request #22732 from hrydgard/moltenvk-portability
Vulkan: MoltenVK portability fixes (loader on Mac, portability subset features)
2026-10-22 19:13:22 +02:00
Henrik RydgårdandClaude Opus 5.5 b46e386f12 Vulkan: Enable the portability subset features MoltenVK has
We queried VkPhysicalDevicePortabilitySubsetFeaturesKHR but never enabled
it at device creation, so to the validation layers we were using image
view swizzles (and sampler LOD bias) without having them. Enable whatever
the device offers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:46:43 +02:00
Henrik RydgårdandClaude Opus 5.5 db6cc41b9d Vulkan: Ask for portability drivers on Mac
Through the Khronos loader (like the Vulkan SDK's, needed for the
validation layers), MoltenVK is only listed if the instance enables
VK_KHR_portability_enumeration. Without it, instance creation failed with
VK_ERROR_INCOMPATIBLE_DRIVER and we fell back to OpenGL. Loading MoltenVK
directly, as the app normally does, works as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:46:43 +02:00
Henrik RydgårdandClaude Opus 5.5 d39df0b45c iOS: Render the TV output at the display's full resolution
The 1080p cap was meant to save Wi-Fi bandwidth, but an Apple TV 4K
only offers 3840x2160 over AirPlay, so the stream was 4K anyway, just
upscaled.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 3b1432d851 Outputs: Begin the UI frame after binding, and scissor to the output
UIContext::BeginFrame sets the scissor, which on Vulkan needs a bound
render target, and sets it to the primary's size. In portrait, that cut
the TV's menu message off past the phone's width.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 014f6dceea Outputs: Name the device in the menu message
"In menus on Henrik's iPhone" if the platform gives us the name the user
gave the device, otherwise "In menus on your iPhone" (or Mac). iOS only
gives the name with an entitlement Apple has to grant, so for now it's
the model there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 3ca76c674c Outputs: Say the menus are on the device, instead of showing black
When no screen on the stack shows anything on an output (we're in the
menus), the background screen draws "Menus are on your device" there.
UI drawn to an output gets the primary's DPI scale, over the whole output.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 923611b445 Display layout: Disable move/resize while the game is on another output
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 7bd4b13baa iOS: Show the game on an AirPlay TV, with the controls on the phone
An external display (like an Apple TV over AirPlay) becomes a Game
output, when the backend is Metal. The game is shown there full screen,
and the phone shows the touch controls with "Playing on TV" in place of
the game. The menus stay on the phone. With other backends, iOS keeps
mirroring.

Up to iOS 26 the external display scene comes from the Info.plist role.
iOS 27 ignores that, and only offers the scene to apps that register a
scene accessory for it, so we do that too. The registration is withdrawn
when the view controller shuts down, so it follows a backend change, and
a restart now goes to the app's scene rather than the first one.

An Apple TV 4K only offers 3840x2160 over AirPlay, so the stream stays
4K, but we render at 1920x1080 and let the layer scale it up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00