PPC hit scrambles the secondary displays (phase-14, all three work items)

Restores the PPC's authentic secondary-display effect: an EnergyDamageType hit
scrambles every secondary cockpit display for 0.8s while the main out-the-window
view stays clean.  Was fully specced (phase-14) with the engine half already
present under the original VWE names; only the trigger and the visual were
missing (the visual STUBBED since 2007).

A -- TRIGGER (game/reconstructed/mech.cpp): in TakeDamageMessageHandler, at the
  binary's @0x4a03f3 position (after the cylinder resolve, before the burst
  loop, so ONCE per damage message) fire
  GetGaugeRenderer()->SpecialEffect(scrambleVideo, damageType*0.2f) on
  EnergyDamageType (==4).  That type is authored on exactly the 14 PPC/ERPPC
  records, so the branch is structurally PPC-exclusive -- no weapon-class check.
  Duration DERIVED from the ordinal (4*0.2==0.8s), not a literal.  BT_DMG_LOG
  prints [ppc-scramble].

B -- VISUAL (SVGA16::FunkyVideo, was the 2007 stub): FunkyVideo now arms
  scrambleActive; new SVGA16::ScrambleRowShift is a per-source-row horizontal
  shear+roll, read by BOTH DrawDevSurface (surround/dock) and ExpandPlaneToBGRA
  (glass windows, native + rotated radar), so all secondary surfaces shear
  together in source space and the main 3D view (separate timing chain) is
  untouched -- the modern stand-in for the VGA CRTC Horizontal-Total detune.
  Tunable: BT_SCRAMBLE_SHEAR (px/line, def 4), BT_SCRAMBLE_ROLL (px/sec, def
  220); BT_SCRAMBLE_TEST=1 forces it on for tuning by eye.

C -- NON-STACKING LATCH (L4GaugeRenderer::SpecialEffect): ignore the re-arm while
  scrambleVideoFlag is set (matches the binary's `modified` latch) -- a second
  PPC during the window no longer extends it.  Was a divergence.

Verified: Release links clean; surround boots + runs with the effect forced on,
every secondary MFD + the radar shear while the out-the-window view stays clean,
no crash (screenshot).  Open (for the playtesters who filed the report): live
PPC-fire confirmation + by-eye shear tuning -- k/roll are not recoverable from
the binary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-06 16:19:10 -05:00
co-authored by Claude Opus 4.8
parent 1164098d61
commit 74a0db4edd
7 changed files with 155 additions and 22 deletions
+6 -1
View File
@@ -812,7 +812,12 @@ void
switch (type)
{
case scrambleVideo:
if (graphicsDisplay != NULL)
// NON-STACKING LATCH (phase-14 fidelity): the binary latches on `modified`
// @0x4fe0fe -- a second PPC hit while the scramble is ALREADY live does NOT
// re-save the (already detuned) value and does NOT extend the window. This
// original unconditionally overwrote scrambleVideoTimeout, so rapid PPC fire
// STACKED the effect -- a divergence from the binary. Ignore re-arm while live.
if (graphicsDisplay != NULL && !scrambleVideoFlag)
{
scrambleVideoFlag = True;
scrambleVideoTimeout = ((Scalar)Now()) + duration;
+71 -9
View File
@@ -615,17 +615,24 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa
Word *source = pixelBuffer.Data.MapPointer;
Word *dest = (Word*)rect.pBits;
int postRowIncrement = (rect.Pitch / 2) - w;
// PPC sync-scramble (phase-14): read each SOURCE row with a horizontal wrap
// (ScrambleRowShift == 0 unless FunkyVideo is armed), so the surface tears
// like a monitor that has lost horizontal lock. Indexed read (srcRow[sx])
// instead of the linear source++.
if (monoTint < 0)
{
// PALETTE surface (sec/radar) -- palette-LUT expand (== SVGA16::Update case 0).
SVGA16Palette *pal = &palette[paletteID];
for (int y = 0; y < h; y++)
{
Word *srcRow = source + y * w;
int sh = ScrambleRowShift(y, w);
for (int x = 0; x < w; x++)
{
PaletteTriplet *pe = &(pal->paletteData.Color[*source & mask]);
int sx = x + sh; if (sx >= w) sx -= w;
PaletteTriplet *pe = &(pal->paletteData.Color[srcRow[sx] & mask]);
*dest = ((pe->Red >> 3) << 11) | ((pe->Green >> 2) << 5) | (pe->Blue >> 3);
dest++; source++;
dest++;
}
dest += postRowIncrement;
}
@@ -637,10 +644,13 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa
Word tint = (Word) monoTint;
for (int y = 0; y < h; y++)
{
Word *srcRow = source + y * w;
int sh = ScrambleRowShift(y, w);
for (int x = 0; x < w; x++)
{
*dest = (*source & mask) ? tint : 0;
dest++; source++;
int sx = x + sh; if (sx >= w) sx -= w;
*dest = (srcRow[sx] & mask) ? tint : 0;
dest++;
}
dest += postRowIncrement;
}
@@ -719,9 +729,11 @@ void SVGA16::ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotate
{
Word *src = base + y * w;
unsigned long *d = dst + y * w;
int sh = ScrambleRowShift(y, w); // PPC scramble (0 unless armed)
for (int x = 0; x < w; x++)
{
Word s = src[x];
int sx = x + sh; if (sx >= w) sx -= w;
Word s = src[sx];
if (monoTint < 0)
{
PaletteTriplet *pe = &(pal->paletteData.Color[s & mask]);
@@ -742,6 +754,13 @@ void SVGA16::ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotate
// 90-degree rotation: output is transposed (ow = h, oh = w). rotate 3 = CW,
// rotate 1 = CCW (the DrawDevSurface convention; BT_GAUGE_SEC_ROT picks it).
int ow = h, oh = w;
// PPC scramble: precompute the per-SOURCE-ROW shift once. sy is used per output
// pixel and ScrambleRowShift reads the clock, so it must not be called per pixel.
// The shear stays in SOURCE space (keyed on sy), so the rotation displays an
// authentically-sheared raster.
int rowSh[512];
int shRows = (scrambleActive && h <= 512) ? h : 0;
for (int i = 0; i < shRows; i++) rowSh[i] = ScrambleRowShift(i, w);
for (int oy = 0; oy < oh; oy++)
{
unsigned long *d = dst + oy * ow;
@@ -758,6 +777,7 @@ void SVGA16::ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotate
sx = w - 1 - oy;
sy = ox;
}
if (shRows) { sx += rowSh[sy]; if (sx >= w) sx -= w; }
Word s = base[sy * w + sx];
if (monoTint < 0)
{
@@ -5395,6 +5415,7 @@ SVGA16::SVGA16(
BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index);
for (int _i = 0; _i < 10; _i++) // DEV-COMPOSITE: lazily created on first surface draw
mDevSurfaceTex[_i] = NULL;
scrambleActive = False; // PPC sync-scramble (phase-14) -- armed by FunkyVideo
//STUBBED: VIDEO RB 1/15/07
# if defined(DEBUG)
Tell("SVGA16::SVGA16()\n");
@@ -6354,13 +6375,54 @@ void
Check_Fpu();
}
//
// PPC sync-scramble (phase-14). The original (`//STUBBED: VIDEO RB 1/15/07`)
// detuned the VGA CRTC Horizontal Total by -9 so every secondary monitor lost
// horizontal lock for 0.8 s. There is no CRTC here, so FunkyVideo just arms /
// disarms a flag; the per-surface expansions (DrawDevSurface for the surround,
// ExpandPlaneToBGRA for the glass windows) shear the pixelBuffer read while it is
// set. The 0.8 s window + non-stacking latch stay in L4GaugeRenderer.
//
void
SVGA16::FunkyVideo(Logical on_off)
{
//STUBBED: VIDEO RB 1/15/07
//Check(this);
//SVGAFunkyVideo(on_off);
//Check_Fpu();
scrambleActive = on_off;
}
//
// Per-SOURCE-ROW horizontal shift for the scramble. Model (phase-14 §4):
// shift(y,t) = ( y*shear + roll*t ) mod width
// a constant per-line shear (the CRTC detune offset every scanline by a fixed
// amount -> a diagonal tear) plus a time roll (the monitor never re-locked, so
// the tear drifts). The physically-derived shear is ~9% of a line, but the
// on-screen result depended on each pod monitor's H-sync PLL -- not recoverable,
// so the default is a legible moderate tear; tune by eye against the playtesters
// who filed the report (BT_SCRAMBLE_SHEAR px/line, BT_SCRAMBLE_ROLL px/sec).
//
int
SVGA16::ScrambleRowShift(int srcRow, int width) const
{
// BT_SCRAMBLE_TEST forces the effect always-on so the shear can be TUNED by eye
// (adjust BT_SCRAMBLE_SHEAR / BT_SCRAMBLE_ROLL and relaunch) without waiting to
// be hit by a PPC. Off by default.
static int sTest = -1;
if (sTest < 0) sTest = (getenv("BT_SCRAMBLE_TEST") != NULL) ? 1 : 0;
if ((!scrambleActive && !sTest) || width <= 0)
return 0;
static double shearPx = -1.0, rollPx = -1.0;
if (shearPx < 0.0)
{
const char *e = getenv("BT_SCRAMBLE_SHEAR");
const char *r = getenv("BT_SCRAMBLE_ROLL");
shearPx = (e != NULL) ? atof(e) : 4.0; // px/line (aggressive ~9%: width*0.09)
rollPx = (r != NULL) ? atof(r) : 220.0; // px/sec drift
}
double roll = rollPx * ((double)GetTickCount() * 0.001);
long s = (long)((double)srcRow * shearPx + roll);
s %= width;
if (s < 0) s += width;
return (int)s;
}
//########################################################################
+14
View File
@@ -334,6 +334,20 @@ public:
void
FunkyVideo(Logical on_off);
// PPC sync-scramble (phase-14): the modern stand-in for the VGA CRTC
// Horizontal-Total detune. FunkyVideo(on) sets scrambleActive; every
// gauge-surface expansion (DrawDevSurface for the surround, ExpandPlaneToBGRA
// for the glass windows) then reads the pixelBuffer with a per-SOURCE-ROW
// horizontal shift so all secondary displays shear together -- authentic by
// construction (one VGA's detuned sync drives them all). Returns the source-x
// offset for a source row, 0 when not scrambling. Tunable by eye
// (BT_SCRAMBLE_SHEAR px/line, BT_SCRAMBLE_ROLL px/sec) -- the exact look
// depended on each pod monitor's H-sync PLL and is not recoverable.
int
ScrambleRowShift(int srcRow, int width) const;
Logical
scrambleActive;
protected:
void BuildWindows(unsigned int width, unsigned int height, bool windowed, int *secondaryIndex, int *aux1Gauge, int *aux2Gauge);