[video] bloom = 0.0 (off, default) .. 2.0 adds a glow around lights on the
OpenGL backend. PSX_BLOOM overrides it for one run; {"cmd":"bloom"} reads or
changes strength, threshold and a debug view live. It is a plain [video] key,
so [quality.*] presets can switch it off on low-end machines.
The HUD never glows: with bloom on every textured and untextured draw is
followed by two stencil-only passes of the same vertices that keep a
"may glow" mark in stencil bit 1 (bit 0 stays the mask-bit mirror). Opaque
primitives clear it under their coverage, then PGXP-proven 3D primitives and
additive semi-transparent texels/primitives (modes 1 and 3) set it, painter
ordered like the colour; 3D and non-3D primitives never share a batch while
bloom is on. Semi-transparent primitives leave the mark alone and the PGXP
depth-clear pass (colour writes off) marks nothing. Without PGXP geometry only
additive primitives glow.
The pass runs on the composed game image before AA and the OSD: the presented
surface's marks are blitted into a quarter-resolution stencil, a soft-knee
bright pass runs under it, three separable Gaussian iterations spread it and a
screen blend adds it back. The marks follow the picture: native-wide centre
copies and Smooth motion surfaces (redraw and reprojection) copy stencil with
colour, and generated frames get the same pass. Off: nothing runs, no state
changes, and the composed frame is byte-identical to master.
test_gl_bloom (gl_bloom_test): off is byte-identical before/after bloom and
with --dump against master; on, a bright 2D panel and a panel over a lit 3D
polygon are byte-identical to off, an additive and a 3D polygon glow; guest
VRAM is unchanged; on this thread and on the render thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (#654): forget the ring's names after gl_renderer_render_thread_stop
(the worker presents and captures on its own thread, and stop drains it
and hands the context back), before the context is deleted. The call is
unconditional (an empty stub without debug tools). The fixture adds a
render-thread pass: frames captured on the worker and drained, more left
queued at shutdown, then checks the worker stopped first and every name
and pending readback is gone. Fails if the reset runs before the stop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The seed path collapsed ls.texture_filter to a boolean, so a launcher's
xBR (3) was saved and applied as bilinear, and the rematch paths passed
the raw value through unchecked. psx_texture_filter_from_ui is now the
one translation (in-range values pass, others are nearest). Test:
video_texture_filter_test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hidden-GL fixture: presents build the ring, shutdown must forget every
name and pending readback, and a second context rebuilds live objects.
Fails without the fix (names kept). run_gl_texture_filter.py gains
--debug-tools to compile the ring in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a renderer restart (lobby rematch, in-game menu -> match) the
capture read back into a stale pack-buffer name, i.e. into client memory
at offset 0, and crashed in glReadPixels (debug-tools builds).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tcc 0.9.27's lib/bcheck.c uses __malloc_hook, removed in glibc 2.34, so
the pinned build failed on Ubuntu 24.04 (lib/bcheck.c:738). Shards never use
-b; the build now passes BCHECK_O= unconditionally, keeping every other
libtcc1.a object. The bundle smoke also clears LIBRARY_PATH, CC and
GCC_EXEC_PREFIX and checks shards import no bounds-checking symbols.
Verified: linux_tcc_bundle_smoke_test passes on Ubuntu 24.04 x86_64
(glibc 2.39, gcc + libc6-dev installed, so host headers exist but are not
used) and on Steam Runtime sniper (glibc 2.31).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
savestate save/load with slot >= SAVESTATE_SLOTS (12) was refused with the
generic message that blames LLE runs, which sent a test chasing the wrong
cause. It now replies "slot N out of range (0..11)". Guarded in
test_savestate_status_protocol.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SIGPROF can land on a thread that is still being created. On macOS arm64
pthread_self() there hits a pointer-authentication trap (EXC_BREAKPOINT in
on_prof), which killed debug-tools builds at boot when a second instance
started lobby threads. The handler now compares the interrupted stack
pointer with the emulation thread's stack range; Linux keeps pthread_equal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gpu_ws_background_requires_full_composite and gpu_timeline_note were defined
twice after a merge, so every GL fixture that includes this file
(gl_post_aa, gl_bloom, ...) failed to compile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- compile_overlays: the bundle layout now compiles with -nostdinc and only
the bundle's own headers (tcc's include/, then musl's libc-include/), so a
host with libc6-dev cannot change what a shard compiles against.
- runtime/tests/test_linux_tcc_bundle_smoke.py (ctest
linux_tcc_bundle_smoke_test): stages the bundle exactly as a release does
(pinned tcc 0.9.27 built from source, musl 1.2.5 headers, runtime
headers), compiles an emitter-shaped overlay shard through
compile_overlays' own tcc path (preamble shim, call_by_address rewrite,
exports) for PGXP flavors 0 and 2 with PATH emptied, loads the .so and
checks overlay_abi() and the exported function. Linux x86_64 only (skips
77 elsewhere). Passes in the Steam Runtime sniper SDK image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the clean-box smoke: every shard failed with "';' expected (got
va_list)". With the adaptation, 26 overlay shards compile on a Debian box
with no compiler and no libc headers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tinycc publishes no Linux binary, so Linux release toolchains had no tcc and
a player without a compiler -- every Steam Deck: SteamOS has none and a
read-only root -- kept overlays interpreted. stage_toolchain now builds the
pinned, unmodified tcc 0.9.27 source on the packaging machine and stages it
as overlay_toolchain/tcc/ (tcc, libtcc1.a, tcc's include/) with musl 1.2.5's
MIT-licensed C headers in libc-include/ (both sources SHA-256 pinned).
compile_overlays.py recognises that layout and calls tcc with -B<dir>
-nostdlib -isystem <dir>/libc-include <dir>/libtcc1.a, so nothing from the
player's system is needed; shard libc symbols resolve against the game's
glibc at dlopen. Windows and system tcc invocations are unchanged. The
TinyCC notice names the Linux build and the musl headers.
Tests: test_release_stage_cache.py (Linux pins, notice, bundle args).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lobby server starts a 2-player room the host relay cannot carry on ICE
(recomp-net-server start_transport, launch transport "ice"). This client
knew only "host" and the legacy server relay. It inferred a relay from
equal host/guest endpoints -- which is exactly what two peers behind one NAT
advertise -- and each peer dialled its own public address: the match stayed
at "linking" forever.
- psx_lobby_launch_transport.h: one pure decision (host / ice / relay /
direct) with a unit test covering ICE, same-NAT endpoints, host relay and
the legacy relay cases.
- PsxLobbyJoinInfo.transport_ice; the launch handler records it and never
turns it into a relay; launch fill and resolve_use_ice start ICE p2p
(signalled over the lobby) for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep both netplay_peer_frontier.h and netplay_fmv_confirm.h includes; advance lib/recomp-net to a48d355 (main, includes #38).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- The dirty-RAM interpreter's IRQ poll stride is host-only; a load resets
it to phase 0 while Live carried an arbitrary phase, so a replay could
take the VBlank in RAM code at another instruction. It is now
re-anchored at every tick boundary in Live and Replay alike.
- The av digest zeroes the three depth-24 present helpers at the end of
the GPU snapshot (upload span, present hold, previous height): host
presentation state a suppressed-present replay leaves different, not
guest-visible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PSX_RB_DET_SELFTEST on R4 4P showed a replayed save of tick T differing
from the live save of T. Each cause, and the fix:
- Replay's finish_frame poll passed no PC hint (Live used the IRQ resume
chain); it now uses psx_netplay_snap_resume_hint.
- Devices are serviced lazily up to the next event, so root counters and
cycles_since_vblank lagged the clock by a host-path amount at save; a
load re-anchors them at the clock and drops the lag. The save now
publishes pending cycle charges and services every device to the clock
first, and renders the SPU up to it.
- GTE / mul-div completion stamps and the load-delay give-back were
re-anchored at the restored clock on every load, so a stall in flight at
the snap point cost the live run cycles but the replay none. New
BS_SEC_CPUTIM carries them relative to the clock.
- The SPU CD-input FIFO was outside the snap and wiped on rollback loads;
new BS_SEC_SPUCD carries the queued frames and the load keeps them.
- LZCR is serialized as derived from LZCS (the raw backing stays 0 until
the first LZCS write; guests only ever read the derived value).
Older snaps without the new sections load as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replay's finish_frame saved its tick snap with no PC hint while Live used
the IRQ resume / last check / cpu->pc chain; the same tick's snap could
carry a different resume PC (psx_netplay_snap_resume_hint now serves both).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every K ticks the host rewinds 16 ticks with nothing mispredicted, so each
seat replays confirmed rows over ticks it ran live; with PSX_RB_SNAP_AUDIT
the diff tool then compares replayed saves with live ones field by field.
On R4 4P it shows replay saves taken at a different guest point than the
live save of the same tick (CPU pc, cycles_since_vblank, root counters).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The waiting-room pump sent set_ready every frame while the local seat was
not yet echoed as ready. Each set_ready is a lobby_update broadcast to the
whole room, so two waiting peers produced hundreds of updates; the host then
missed the seat update (seat 2 shown open, Start disabled) and, after Start,
the launch itself. Offer changes still go out immediately.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per ring save, the section CRCs (and small sections in hex) of the
boot_state blob, marked live or replay. tools/rb_snap_audit_diff.py
checks a seat's replay against its own live save and seats against each
other, naming the first differing section and bytes. Off unless set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Live realign restores the snap at T, sets the session to T+1 and
longjmps back into the guest from the admit spin. The resume ran before
T+1 was admitted, so that frame latched no inputs and did not advance;
the next frame was then counted as T+1 and the realigned seat ran one
frame ahead of its tick label from there on (ring saves after a NACK
realign carried the next tick's clock, 2x the cycles in the first
tick). Only the realigned seat shifted, so it forked from every other
seat; with four seats this produced the "resim core diverge" splits
and the later hc-fork / hang cascade.
flush_resume now admits the tick first on the Live realign path and
keeps spinning until it can.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>