D336: fix Frigate 'everything breaks but the gun' — head-bob anim read uninitialised alias fields (A1, D100 follow-up)

bheadAdjustAnimation read player->field_5C0 / animFlipFlag, which on N64
are raw-offset aliases of the inline player.model (model.animframe1 /
model.gunhand). Since D100 made model a real inline struct on PC they were
separate, never-written fields: Bond's head-bob start frame came from
uninitialised player-struct bytes (2e32 etc.), poisoning his model anim
(D156) and wrecking the scene. The D294 2x room pool only moved the player
struct onto dirtier memory. Read/write the real model fields.

Found with gdb hardware watchpoints (field never written). Headless repro
Runway -> Frigate -> turn now clean, D156 0; golden 3/3; Win + Linux.
This commit is contained in:
James Dansereau committed 2026-09-24 09:57:55 -04:00
1 parent b74d2738fd
commit 38354e1c7e
3 files changed
+38 -3

No files matched your search

+1
View File
@@ -213,3 +213,4 @@ D332,FIXED,Reticule jitter in aim mode (RMB) —,FIXED (fix/mouse-pd-model) —
D333,OPEN,"#104 (v0.3.0, Linux):","IMPLEMENTED (port/src/input.c + optionsoverlay.c, port-only); default N64 = unchanged; build + golden clean; feel check owed."
D334,?,"Native widescreen, world side (WIDESCREEN-FOV-PLAN Option B Phase 2):",IMPLEMENTED (Rule-2 approved 2026-09-23); 4:3 identity (golden 3/3); native 16:9 centre matches the 4:3 frame (mean diff 4.3 vs 12.4 stretched); 21:9 clean on Bunker/Facility/Dam.
D335,?,"Native widescreen, HUD + front end (WIDESCREEN-FOV-PLAN Phase 3):","IMPLEMENTED (Rule-2 approved 2026-09-23); 4:3 byte-identical (no emission at ≤4:3); verified at 16:9/21:9 on HUD, watch, title card, legal/file-select/gun-barrel."
D336,FIXED,"Frigate (and any level) ""all polygons break except the gun"" after turning:","FIXED (src/game/bondhead.c #ifdef PORT, ABI/layout); headless repro (Runway → exit → Frigate → turn, D294 2× room pool) clean, D156 0 fires; golden 3/3; Win + Linux builds."
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
213 D333
214 D334
215 D335
216 D336
+15
View File
@@ -621,6 +621,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D333 | **#104 (v0.3.0, Linux): FPS-style centred aim on RMB, delivered as `Input.AimStyle` (F10 "RMB aim style": N64 / PD DAMP / CENTRED), folding in D332's toggle.** — full `## D333` entry at file tail | IMPLEMENTED (port/src/input.c + optionsoverlay.c, port-only); default N64 = unchanged; build + golden clean; feel check owed. |
| D334 | **Native widescreen, world side (WIDESCREEN-FOV-PLAN Option B Phase 2): the world is projected at the real window aspect (Hor+) by generalising GE's own N64 16:9 mode; `Video.NativeWidescreen` (default on).** — full `## D334` entry at file tail | IMPLEMENTED (Rule-2 approved 2026-09-23); 4:3 identity (golden 3/3); native 16:9 centre matches the 4:3 frame (mean diff 4.3 vs 12.4 stretched); 21:9 clean on Bunker/Facility/Dam. HUD still stretched → Phase 3. |
| D335 | **Native widescreen, HUD + front end (WIDESCREEN-FOV-PLAN Phase 3): HUD elements anchored left/right/centre and the front end pillarboxed via the fast3d EXT aspect modes (PD pattern), `port/include/hudaspect.h`.** — full `## D335` entry at file tail | IMPLEMENTED (Rule-2 approved 2026-09-23); 4:3 byte-identical (no emission at ≤4:3); verified at 16:9/21:9 on HUD, watch, title card, legal/file-select/gun-barrel. Menu pointer mapped into the pillarbox. One cosmetic leak → GRAPHICS-BACKLOG D335a. |
| D336 | **Frigate (and any level) "all polygons break except the gun" after turning: Bond's head-bob anim read two never-written placeholder fields (`field_5C0`, `animFlipFlag`) that on N64 are raw-offset aliases into the inline `player.model` (D100 follow-up; A1 class). Likely the D311 upstream anim-poison source.** — full `## D336` entry at file tail | FIXED (src/game/bondhead.c `#ifdef PORT`, ABI/layout); headless repro (Runway → exit → Frigate → turn, D294 2× room pool) clean, D156 0 fires; golden 3/3; Win + Linux builds. User re-test owed. |
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
@@ -13267,3 +13268,17 @@ Implemented as `Input.PdMouseAim` (**default off**, because it changes aim feel)
**D335 follow-up (2026-09-23):** end credits (`bondviewRenderCredits`, camera-mode-1 path) wrapped CENTER. Verified at 16:9 via `-level_54`: ending cutscene subtitles undistorted, cast roll inside the 4:3 region. `gunDrawSight` checked: it draws only the crosshair sprite (GE has no scope mask), which D334 already corrects, so no wrap is needed. **D335a resolved:** a full-canvas scissor after the front-end CENTER wrap clips the gun-barrel backdrop at the pillarbox edge (capture-verified).
**D334/D335 16:9 smoke sweep (2026-09-23, native Windows, 1280×720, defaults):** 16 further levels (Runway, Surface 1, Silo, Frigate, Bunker 2, Statue, Archives, Streets, Depot, Train, Jungle, Control, Caverns, Cradle, Aztec, Egypt; plus Bunker 1, Facility and Dam earlier), `GE_PCDUMP` at an intro frame (400) and a gameplay frame (1000). 16/16 render both frames, 0 crash lines. Contact sheets eyeballed: world edge to edge with no scissor slabs or missing rooms, skies full width, intro captions and Archives' dialogue box undistorted inside the 4:3 region, ammo counter right-anchored, Streets' countdown timer undistorted.
## D336 — "All polygons break except the gun" on Frigate after another level: uninitialised head-anim alias fields (A1 / D100 follow-up) (2026-09-24)
**Report (user playtest):** load Runway (or Dam/Surface/Silo…), exit, load Frigate, look around, and everything but Bond's gun breaks for the rest of the session. The `D156` bad-anim guard fired in those sessions.
**Repro (headless, native Windows):** `GE_STARTMENU=10 GE_STARTMENU_PAGE=3` (Runway briefing) → scripted watch Abort → TEMP `GE_D235_NEXT=26` re-entry into Frigate → sustained `SLEFT`/`SRIGHT` turn. The user's ini (DrawDistance 400, LodDistance 400, FOV 120) gives D294's room pool its 2.0× cap. After the turn the world renders black except the gun, and D156 fires. `GE_ROOMPOOL=1.0` (authored pool) is clean. The pool size was the trigger, not the cause.
**Elimination:** stage bank not exhausted (3.8 MB free, `GE_D294BANK`); texture pool full-size (768 KB) with 0 pool-full events (new `D85TEX` pool-size line); no mema alloc/free outside the heap and no room-GDL/room-total overflow (TEMP probes, removed). The D156 victim was always **heap end + 0x838**, i.e. `&player->model` (the player struct is allocated right after the room heap). Hardware watchpoints (gdb Python, `scratch/watch2.py`) on the model's anim fields caught the poisoned value arriving through legitimate code: `MoveBond → bondviewMoveAnimationTick → bheadAdjustAnimation (bondhead.c) → modelSetAnimation → modelSetAnimFrame(frame=2.07e32)`. The startframe comes from `g_CurrentPlayer->field_5C0`, and a watchpoint on that field saw **no writes at all**.
**Root cause (ABI/layout, A1 raw-offset alias, D100 follow-up):** on N64 the player's `model` at 0x598 is an inline `struct Model`. The decomp split it into `Model *model;` plus placeholder fields, so `animFlipFlag` (0x5BC) is `model+0x24` = **`model.gunhand`** and `field_5C0` is `model+0x28` = **`model.animframe1`** (4-byte pointers, no holes; the neighbouring `unk25`/`unk27`/`unk2c` names confirm). D100 made `model` a real inline struct on PC, so those placeholders became separate fields that nothing ever initialises. `bheadAdjustAnimation` computed Bond's head-bob start frame from uninitialised player-struct bytes. Where the player struct lands depends on the room-pool size (D294 2× moves it) and on what previously occupied that memory (hence the level-order dependence). Garbage like 2e32 poisoned Bond's model anim state and wrecked the scene. A sweep of all `field_59C..field_650` placeholders found these two as the only reads.
**Fix (`src/game/bondhead.c`, `#ifdef PORT`):** `BHEAD_CURFRAME` = `model.animframe1`, `BHEAD_FLIPFLAG` = `model.gunhand`, the exact bytes the N64 code touches. No logic change.
**Verified:** the same Runway → Frigate → turn repro at the 2× pool renders correctly throughout, D156 0 fires; bunker1 golden 3/3; Windows + Linux builds clean. **D311 link:** D311 (upstream anim-poison source, open) named `bheadAdjustAnimation` as the one path where a corrupted value reaches `model->speed`. This is very likely that source; re-check D311's Caverns signature in play before closing it. **D294 note:** its "linear 3.0× variant corrupted model anim state on the menu-driven path" is this same bug (a bigger pool moved the player struct onto dirtier memory), not a pool-size problem. The 2× cap can be revisited. **Owed (user):** replay Runway → Frigate → look around.
+22 -3
View File
@@ -39,9 +39,28 @@ void bheadSetdamp(f32 headdamp);
#ifdef PORT
/* D336 (ABI/layout, A1 raw-offset alias; the D100 follow-up): on N64 the
* player's `model` at 0x598 is an INLINE struct Model, and the decomp's
* placeholders after it alias its fields by raw offset: animFlipFlag (0x5BC)
* is model+0x24 = model.gunhand, field_5C0 (0x5C0) is model+0x28 =
* model.animframe1 (4-byte pointers, no holes: unk25/unk27/unk2c confirm).
* D100 made `model` a real inline struct on PC, so those placeholders became
* separate fields that nothing initialises or keeps in sync -- the head-bob
* start frame was read from never-written memory (uninitialised player-struct
* bytes; garbage like 2e32 poisoned Bond's anim -> D156, and with the D294
* 2x room pool the player struct lands on dirty memory, corrupting Frigate).
* Read/write the real model fields, i.e. the bytes the N64 code touches. */
#define BHEAD_FLIPFLAG (g_CurrentPlayer->model.gunhand)
#define BHEAD_CURFRAME (g_CurrentPlayer->model.animframe1)
#else
#define BHEAD_FLIPFLAG (g_CurrentPlayer->animFlipFlag)
#define BHEAD_CURFRAME (g_CurrentPlayer->field_5C0)
#endif
void bheadFlipAnimation()
{
g_CurrentPlayer->animFlipFlag = !g_CurrentPlayer->animFlipFlag;
BHEAD_FLIPFLAG = !BHEAD_FLIPFLAG;
}
void bheadUpdateIdleRoll()
@@ -410,7 +429,7 @@ void bheadAdjustAnimation(f32 speed)
if (g_CurrentPlayer->headanim >= 0)
{
startframe = (g_CurrentPlayer->field_5C0 - g_BondMoveAnimationSetup[g_CurrentPlayer->headanim].loopframe)
startframe = (BHEAD_CURFRAME - g_BondMoveAnimationSetup[g_CurrentPlayer->headanim].loopframe)
/ (g_BondMoveAnimationSetup[g_CurrentPlayer->headanim].endframe - g_BondMoveAnimationSetup[g_CurrentPlayer->headanim].loopframe);
startframe = g_BondMoveAnimationSetup[i].loopframe + ((g_BondMoveAnimationSetup[i].endframe - g_BondMoveAnimationSetup[i].loopframe) * startframe);
@@ -420,7 +439,7 @@ void bheadAdjustAnimation(f32 speed)
&g_CurrentPlayer->model,
// match hack: addu address backwards
(struct ModelAnimation *) ((s32)g_BondMoveAnimationSetup[i].anim_id + (s32)&ptr_animation_table->data),
(s32) g_CurrentPlayer->animFlipFlag,
(s32) BHEAD_FLIPFLAG,
startframe,
0.5f,
12.0f);