Adapt the 8 MB RAM work from the rbengine branch into a master-targeted change. The builtin package is disabled by default, activation requests expanded RAM before memory init, and runtime/state/digest/dispatch paths respect the live RAM map.
A plugin that draws its own overlay primitives needs to know where the
right-hand edge of the screen is, and it cannot work that out for itself.
GPUSTAT carries the horizontal-resolution bits, so a plugin can recover
the coarse mode width, but the visible width also depends on the GP1(06h)
X1/X2 range -- and that register is write-only and mirrored nowhere a
plugin can read.
Ape Escape is the worked example. It scans out 384 (368-mode, dot clock 7,
horizontal range 539..3227) while its mode width is 368, and the usual
assumption of 320 is wrong by 64. A HUD row right-aligned to 316 lands 68
pixels short of the edge, which is exactly what the Quick Gadget Select
mod does today.
psx_mod_display_width()/psx_mod_display_height() return what the presenter
uses, straight from gpu_get_display_info(). Zero means the geometry is not
established yet, and the header tells callers to skip drawing rather than
substitute a guess -- an overlay in the wrong place is worse than an
overlay that waits a frame.
mod_runtime_test covers the passthrough with a stubbed GPU, including the
384-vs-368 case that motivates it and the zero case.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Supersedes the global HYBRID controller-mode shape from PR #233 with a trusted plugin callback that can choose analog or digital presentation per input sample.
Keeps analog mode hardware-faithful by no longer folding physical D-pad input onto the left stick, while leaving debug injection able to steer stick-only paths.
Includes the pad/cursor probe tooling from #233 for diagnosing input doubling.
Players asked for CD speeds beyond the curated 2x/4x list and for the two
loading accelerators to stop being one control (GH TombaRecomp#5: a
speedrunner wants shorter loads but NOT the host speed-up, which runs past
the loading screen and breaks a skip).
Three changes, all general:
- psx_mod_set_disc_speed accepted only divisor 2 or 4. That was policy,
not safety: cdrom.c divides the sector delay and floors the result at
CDROM_MIN_DELAY, and apply_speed leaves XA streaming at authentic
timing, so no positive divisor can produce a zero-delay storm or
pitch-shift FMV audio. Accept 1..PSX_MOD_DISC_SPEED_MAX and let the
mod's description carry the risk warning.
- psx_mod_set_load_acceleration accepted 2..16. Host pacing cannot desync
the guest at all (every frame, CD deadline, interrupt and callback still
happens), so the ceiling was arbitrary. Accept 1..PSX_MOD_LOAD_ACCEL_MAX,
with 1 meaning authentic and 0 still meaning uncapped.
- New option key disabled_by: an option names a BOOLEAN sibling that
overrides it, so "tick Instant and the speed box stops mattering" needs
no second widget type. The parser rejects a dangling, self-referential
or non-boolean reference rather than leaving a control silently always
enabled, and the provider resolves the link against the live selection
so the launcher and the plugins can never disagree about whether a
control counts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The manifest schema already carries typed, validated, launcher-rendered,
persisted options ([[option]] boolean/choice/integer), and the launcher
provider already reads them. A trusted activation plugin could not: the
callback takes no arguments, so a plugin was only ever an on/off switch.
That forced a parameterised feature to be modelled as one feature per
value, and `constraint` only expresses ordered_integer WITHIN a feature, so
those pseudo-features could not even be made mutually exclusive. A game
wanting "Widescreen: 16:9 / 21:9" or "Interpolation: 90/120/144/165/240"
had no way to express it as one entry.
psx_mod_option_value(package, feature, option, out, out_size) closes the
gap against the value the player actually committed. It returns 0 with an
empty string when the plan is not committed or the ids do not resolve, so a
caller applies its own default rather than reading "" as a selection. Ids
are explicit because registration is by plugin id alone and the callback
carries no package/feature context.
Also in this commit, from the same investigation:
* Sustained overlay-autocompile failure is now LOUD. After 3 consecutive
failed runs the runtime prints once to stdout: what failed, the exit
code, that overlays are staying interpreted and frame times will be far
worse than the build is capable of, what to check, and the compiler's own
output tail. Previously the only trace was a `fails` counter reachable
over TCP, so a wholly-interpreted title presented as "why is this slow"
rather than as a message. Threshold is 3, not 1, because isolated
failures (a shard racing a capture rewrite, a transient lock) are
ordinary and self-heal. Measured cost of the silent case on Tomba!:
p50 frame period 105ms, vs 3.1ms once shards existed.
* The autocompile child's working directory is printed at startup beside
cache and captures. Every relative path in overlay_autocompile_cmd
resolves against it and it comes from game.toml's detected project root,
so a game.toml staged outside the real root silently repoints the entire
compile command with no other symptom.
* A build tree whose cache still carries recomp-ui's OFF for
RECOMP_UI_ENABLE_MODS now says so at configure time. 610234a's seed only
fires on a fresh cache, and a stale OFF is indistinguishable from a
deliberate one, so such trees kept producing Mods-less builds across
reconfigures - the same "reads as a stale build" failure one level up,
and invisible. The explicit-OFF contract is deliberate so this does not
override it; it makes the state audible.
Runtime ctest 16/16. Warning verified to fire on a tree configured with
-DRECOMP_UI_ENABLE_MODS=OFF and to stay silent on a fresh configure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>