From 8f0abae18f081711868bc383ba5860f1dd979f24 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Sat, 12 Sep 2026 09:20:13 -0400 Subject: [PATCH] QoL: Game.NoHitFlash toggle (D232) (#58) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * QoL: Game.NoHitFlash toggle (D232, route-(b) in currentPlayerSetFadeColour) The community 'no damage flash' option. Every fade routes through currentPlayerSetFadeColour; the only non-black ones are the two damage paths (white g_DamageTypes envelope + red 150,0,0 death overlay), so zeroing the RGB there kills exactly the hit flash and leaves blackouts, the colour screen and the death fade untouched. Default 0 = original. Frame A/B verification in progress; finding entry to follow. * D232: add GE_NOHITFLASH_TRACE diagnostic to currentPlayerSetFadeColour Env-gated osSyncPrintf (pattern per GE_D160) logging r/g/b/frac/caller whenever a non-black fade reaches the gate. Used to resolve the frame-A/B anomaly from the previous session: zero trace fires during a headless no-input BUNKER1 run confirms the gate is inert absent real damage, and a controlled ini-toggled A/B (framediff.py, exact + tolerant modes) shows the gate's visual effect (99/100 frames match) is far smaller than the port's own run-to-run noise floor (0/100 match between two runs of the identical build) — the anomaly was D117 nondeterminism, not a real bug. Full writeup in docs/HANDOFF.md (local, gitignored). Claude-Session: https://claude.ai/code/session_016TaKy6AAsVUTfjkhrX6CjG * D232 fix: zero frac too, not just RGB, in the NoHitFlash gate User playtest found the white hit-flash was gone but a black fade-to-black still played on every hit -- the gate zeroed r/g/b but left frac (the overlay's alpha/opacity) untouched, so a fully-opaque BLACK rectangle drew at the same envelope the white one would have. Same visual intrusion, different colour, not what the toggle promises. Fix: zero frac too when the gate fires, so the overlay doesn't draw at all. This also drops the GE_NOHITFLASH_TRACE diagnostic added earlier this session to hunt the original (unrelated, since-resolved) headless frame-diff anomaly -- no longer needed now that the real bug is fixed and user-verified in a live playtest. User-verified: hit flash fully gone, no residual black flash, blackouts/ colour-screen/death-fade untouched. --- port/src/video.c | 6 ++++++ src/game/bondview2.c | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/port/src/video.c b/port/src/video.c index 50c309db..8064ff46 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -86,6 +86,11 @@ f32 portScreenShakeScale = 1.0f; * 0 (default) = the legal screen + logo attract sequence plays as normal. */ s32 portSkipIntro = 0; +/* D232: Game.NoHitFlash — the community "no damage flash" toggle (route-(b) + * hook in src/game/bondview2.c currentPlayerSetFadeColour). 0 (default) = + * original damage flash. */ +s32 portNoHitFlash = 0; + /* D211: Video.FovScale as a multiplier on the render FOV. Applied game-side * at the guPerspectiveF chokepoint (src/fr.c) so it lands BEFORE the CPU * pre-multiplies projection x view into the combined world matrix — the @@ -97,6 +102,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void) { configRegisterFloat("Game.ScreenShakeIntensity", &portScreenShakeScale, 0.0f, 10.0f); configRegisterInt("Game.SkipIntro", &portSkipIntro, 0, 1); + configRegisterInt("Game.NoHitFlash", &portNoHitFlash, 0, 1); configRegisterInt("Video.VSync", &cfgVSync, 0, 1); configRegisterInt("Video.FpsCap", &cfgFpsCap, 0, 1000); configRegisterInt("Video.MSAA", &cfgMSAA, 1, 8); diff --git a/src/game/bondview2.c b/src/game/bondview2.c index dc29d2ed..c02b3a5d 100644 --- a/src/game/bondview2.c +++ b/src/game/bondview2.c @@ -4234,6 +4234,30 @@ Gfx *currentPlayerDrawFade(Gfx *gdl) } void currentPlayerSetFadeColour(s32 r, s32 g, s32 b, f32 frac) { +#if defined(PORT) + /* D232: Game.NoHitFlash — the community "no damage flash" toggle. + * Every fade in the game routes through this function, and the ONLY + * non-black ones are the two damage paths (the per-frame g_DamageTypes + * envelope in bondviewPlayerTickDamageAndHealth above — white 0xFF + * with an alpha envelope scaled by remaining health — and the red + * 150,0,0 overlay once the death blood finishes) — every other caller + * passes (0,0,0). User playtest (M-105 cont.) caught that zeroing just + * the RGB isn't enough: `frac` is the overlay's alpha/opacity, so a + * zeroed-RGB-but-nonzero-frac call still draws a fully opaque BLACK + * flash at the same envelope as the original white one -- same visual + * intrusion, different colour. Zero frac too so the overlay doesn't + * draw at all. Leaves all blackouts, the colour screen and the death + * fade's *own* frac-driven timing untouched (their frac is unaffected + * since they only ever pass (0,0,0) here). Default 0 = original + * behaviour. */ + extern s32 portNoHitFlash; + if (portNoHitFlash && (r != 0 || g != 0 || b != 0)) { + r = 0; + g = 0; + b = 0; + frac = 0.0f; + } +#endif g_CurrentPlayer->colourscreenred = r; g_CurrentPlayer->colourscreengreen = g; g_CurrentPlayer->colourscreenblue = b;