Files
goldeneye-pc-port/docs/dev/AUDIT-M6-player-offsets.md
James Dansereau 52539d10de Prepare for public release
- README, LICENSE (MIT for the port layer), NOTICE, CONTRIBUTING.
- Restructure docs: internals.md, porting-notes.md, dev-process.md +
  dev/agentic-development.md (the research write-up), dev/ (raw record).
- Replace decomp status-badge CI with a PC-port validate workflow.
- Remove session/agent scaffolding and stale planning docs from the tree.
- Add .gitattributes (normalize line endings to LF); add screenshots.

The pre-2026-08 history is the GoldenEye 007 decompilation by Larry Ficken
(KholdFuzion) and contributors, preserved unchanged. The PC-port work from
2026-08-17 on is mine.
2026-09-01 19:28:59 -04:00

12 KiB
Raw Permalink Blame History

AUDIT-M6 — struct player / struct hand raw-offset pass

Survey only (Track 3 of docs/PLAN-M6-playable.md). No source changed, no build. Feeds a follow-up fix session. Companion to §F D98/D100/D101/D102.

Method

Grep of src/ for: handoffset, (u8 *)g_CurrentPlayer, g_CurrentPlayer + 0x, + 0x[3-hex], sizeof(struct hand|player), * sizeof(struct, hardcoded 0x2A80/0x2A70, ->hands[i] +, punned ->m[ / inline-Model accesses, bondheadmatrices, copy_of_body_obj_header, field_59C..field_6CC, field_654, gaitRwData. Cross-checked every hit against the live struct player / struct hand definitions in src/game/bondview.h (player @ line 314, hand @ line 119).

PC layout facts (from bondview.h + bondtypes.h)

  • struct hand (bondview.h:119): N64 sizeof ≈ 0x3B8 (base 0x870→0xC28 inside player). PC adds: audioHandle +4, rocket (AttachedObj*) +4, mtxlist (Mtxf*) +4, and the D102 #ifdef PORT tail struct Model weaponModel (~0x2A8) + u32 weaponRwPool[192] (0x300). PC sizeof(struct hand) ≈ 0x968 — roughly +0x5B0 vs N64, and not a clean multiple, so handnum * sizeof(struct hand) de-syncs for handnum==1.
  • struct player pointer growth below N64 offset 0x594 (contradicts the D100 note that “anything below 0x594 is unaffected”): cameratile (0x34), prop (0xA8), bodyModel (0xD4), autoaim_target_y (0x130), autoaim_target_x (0x140) → +0x14 by offset ~0x144. field_10C4.. and the bloodImg* / InvItem* / textoverride* blocks add much more higher up.
  • model (N64 0x598): D100 made it an inline struct Model (~0x2A8) replacing an 8-byte pointer → +0x2A0 for everything after it, before field_59C..field_6CC (which D100 grep-verified dead except field_654).
  • ptr_hand_weapon_buffer[2] +0x8; copy_of_body_obj_header[2] — inline ModelFileHeader grew 0x20→0x30 (4 pointers ×+4) → +0x20; item_related[2] struct texpool pointer growth; bondheadmatrices / viewports unchanged.
  • Net delta from player base to hands[]: ≈ +0x2E0 (0x14 + 0x2A0 + 0x08 + 0x20 + texpool). So N64 hands[0] @ 0x870 is at PC ≈ 0xB50; N64 hands[0].throw_item_pos_related @ 0x870+0x268 = 0xAD8 is at PC ≈ 0xDB8.

Findings table

# file:line accessor targets (N64 field) N64 off PC off (real) PORT-adj? BUNKER1 FP reachable? severity recommended fix
1 src/game/gunfire.c:4960 THROWMTX (macro) used @ 5043,5047,5070,5126,5360,5364,5387,5443 (Mtxf*)((u8*)g_CurrentPlayer + handoffset + 0xAD8) (0xAD0 EU) hands[handnum].throw_item_pos_related 0xAD8 (h0) / 0xAD8+0x3B8 (h1) ≈0xDB8 (h0) / ≈0xDB8+0x968 (h1) NO YES — sub_GAME_7F068508 runs on every shot with a cartridge (PP7/KF7/…), solo only (getPlayerCount()>=2 early-out) HIGH — memory corruption. Raw 0xAD8 on PC lands inside the inline model / field_59C region of struct player; matrix_4x4_copy(THROWMTX,…) writes 64 bytes there. handnum==1 (dual wield) additionally uses the wrong stride. Corrupts the gait Model / bondhead matrices → wrong Bond-body render or a delayed fault in model code. Replace macro body with (&g_CurrentPlayer->hands[handnum].throw_item_pos_related). Drop THROWMTX_OFFSET, handoffset.
2 src/game/gunfire.c:4961 THROWPOS(k) used @ 5112-5114, 5164-5166, 5429-5431, 5481-5483 ((f32*)((u8*)g_CurrentPlayer + handoffset + 0xB08))[k] (0xB00 EU) hands[handnum].throw_item_pos_related.m[3][k] (translation row) 0xB08 ≈0xDE8 NO YES (same path as #1) HIGH — silent-wrong + reads corrupted memory. Feeds casing->vel from the frame-to-frame delta of a garbage location → casings fly wrong / NaN, and the read is OOB-ish relative to intent. g_CurrentPlayer->hands[handnum].throw_item_pos_related.m[3][k]
3 src/game/gunfire.c:4962 THROWPREV(k) used @ 5112-5114, 5164-5166, 5429-5431, 5481-5483 ((f32*)((u8*)g_CurrentPlayer + handoffset + 0xB48))[k] (0xB40 EU) hands[handnum].throw_item_pos_related_prev.m[3][k] 0xB48 ≈0xE28 NO YES (same path) HIGH (same as #2) g_CurrentPlayer->hands[handnum].throw_item_pos_related_prev.m[3][k]
4 src/game/gunfire.c:5031 and :5348 handoffset = handnum * sizeof(struct hand) array stride for hands[handnum] 0x3B8 0x968 (and non-uniform) NO (the raw math is only consumed by #1–#3) YES HIGH — root of #1–#3’s handnum==1 error; harmless once #1–#3 use hands[handnum].field. delete; fold into the field accessors above.
5 src/game/gunfire.c:600-661 weapon-model setup (copy_of_body_obj_header[handnum] → modelInit(HAND_WEAPON_MODEL, mdlhdr, HAND_WEAPON_RWPOOL); hand->mtxlist = rwmtx; render_pos = (RenderPosView*)rwmtx) field-name access + D102 macros hands[h].weaponModel / weaponRwPool / mtxlist; copy_of_body_obj_header[h] (inline ModelFileHeader) — — YES (D102) for the Model/pool pun; copy_of_body_obj_header is a plain inline struct (layout-consistent) YES — this is the 1P weapon draw path MED — prime suspect for “weapon model doesn’t draw.” rwmtx is dynAllocate’d (transient per-frame arena) but weaponModel.render_pos is set to it once at setup and consumed at render (gunRenderFirstPersonGunModels:1605); on N64 render_pos aliased the persistent mtxlist field, here it can point at reused arena memory. Also flashvisptr = HAND_WEAPON_RWPOOL + node->…RwDataIndex (gunfire.c:640) assumes N64 word indices into a pool whose PC records are ~2×. Verify dynAllocate lifetime vs. render; consider persisting the matrix list. Re-check weaponRwPool[192] sizing against modelCalculateRwDataLen(mdlhdr) on PC. Confirm subdraw matrix path (shared with Track 2).
6 src/game/bondview2.c:3155-3162 (PORT branch of sub_GAME_7F07E7CC) (Model*)&g_CurrentPlayer->something_with_watch_object_instance; static u8 watchRwPool[0xC8]; g_CurrentPlayer->step_in_view_watch_animation = 0 watch-menu item preview Model punned at N64 player+0x230, pool at +0x2EC 0x230 / 0x2EC 0x244 / 0x300-ish YES (D56) but suspect YES — open watch menu / cycle to an item page MED — under-reservation. D56 hosts the pool in a 0xC8 static (N64 capacity) but PC RW records are wider; and animInit writes a full inline struct Model (~0x2A8) starting at &something_with_watch_object_instance, overrunning field_234..watch_scale_destination.. (N64 those were the tail of the 0xBC Model hole; on PC they are live watch fields). Give the watch Model real inline storage in struct player under #ifdef PORT (D100 pattern) + size watchRwPool from modelCalculateRwDataLen.
7 src/game/gunfire.c:4951-4957 #define THROWMTX_OFFSET 0xAD8 etc. (the constants behind #1–#3) — — NO (EU/non-EU split only, both N64) — INFO — remove with #1–#3. delete block.
8 src/game/player.c:136 mempAllocBytesInBank((sizeof(struct player)+0xF)&~0xF, …) player allocation — — YES (D98) — OK — listed for completeness; correct. none
9 src/game/initBondDATAdefaults.c:103 animInit(&g_CurrentPlayer->model, &player_gait_object_header, g_CurrentPlayer->gaitRwData) inline gait Model + gaitRwData[256] — — YES (D100) YES OK. none
10 src/game/bondhead.c:217-480, bondview2.c:922-9102 &g_CurrentPlayer->model, bondheadmatrices[0].m[..] (field-name) inline gait Model + head matrices — — YES (D100 makes field access correct; single TU layout) YES OK — no raw offsets; safe as long as D100 inline-Model stays. none

Not player/hand but same class (noted, out of scope for the fix list)

  • src/game/gunfire.c:5513 render_data.mtxlist = (Mtxf*)model_matrices — local, fine.
  • initexplosioncasing.c, initobjects.c, propobj.c, glass2.c, initunk_005520.c — all N * sizeof(struct X) allocations, already sizeof-correct (D57 covered the rwdata capacities).
  • ramromreplay.c +0x110/+0x1F8/+0x21E — ramromfilestructure, handled by D66/D87, unrelated.

Total

10 distinct offset sites; 3 live HIGH-severity bugs (findings #1–#3, one underlying cause #4), 1 dead constant block (#7), 2 MED items to verify (#5, #6), 4 already-correct (#8–#10).

All 3 HIGH bugs are the same construct in gunfire.c and are reachable in BUNKER1 first-person the instant the player fires a weapon that ejects a casing (every ballistic weapon). They are NOT reachable before the first shot, so they do not explain a weapon model that is missing from frame 1 — but once firing starts they corrupt the inline gait Model region and will produce cascading render/anim breakage or a fault.

Where the “1P weapon model doesn’t draw” bug most likely lives

Not in a surviving raw offset — D102 already gave the weapon Model and its RW pool dedicated storage, and gunRenderFirstPersonGunModels / gunTickHandState use field names. Best candidates, in order:

  1. Finding #5 — the D102 wiring in gunfire.c:600-661. weaponModel.render_pos is pointed at a dynAllocate’d transient (rwmtx) that on N64 aliased the persistent hand->mtxlist field; if the arena is recycled between the hand-tick setup and gunRenderFirstPersonGunModels, the model draws with garbage / off-screen matrices (looks like “not drawn”). Check dynAllocate lifetime and whether weaponModel should own its matrix list on PC.
  2. The subdraw / subcalcmatrices / process_02_position matrix composition — shared with Track 2 (inverted characters). A handedness / lookat-sign error there hides the weapon model just as it inverts guards.
  3. field_87F gating (gunfire.c:520, :587-598) — if the setup branch that sets field_87F = 1 and calls modelInit isn’t taken (e.g. get_ptr_weapon_model_header_line(item) returns NULL because ptr_hand_weapon_buffer / weapon-model load failed), the render loop continues and nothing is emitted. Cheap to instrument.
  1. #1–#4 & #7 (gunfire.c THROW macros) — one mechanical change, no layout work: rewrite THROWMTX/THROWPOS/THROWPREV as g_CurrentPlayer->hands[handnum].throw_item_pos_related[_prev] field accesses (.m[3][k] for the POS/PREV variants), delete handoffset and the THROW*_OFFSET defines. Wrap in #ifdef PORT keeping the N64 macro for the N64 build (or, since the field access is identical semantics on both, use it unconditionally — verify byte-match). Log as §F “D115”.
  2. #5 — investigate the weapon-model render_pos / mtxlist lifetime and weaponRwPool sizing; this is the likely playability blocker. May fold into the Track-2 matrix work.
  3. #6 — give the watch preview Model inline PORT storage + sized pool (only matters once the player opens the watch in-level).
  4. Add a debug assert in gunfire.c weapon-model setup logging mdlhdr->numRecords vs weaponRwPool capacity and field_87F state, to settle candidate 3 above.

§F pointer

Add to docs/internals.md §F after D112: “D115 (audit) — struct player/struct hand raw-offset pass: see docs/dev/AUDIT-M6-player-offsets.md. Live HIGH bugs = gunfire.c THROWMTX/POS/PREV macros (+handoffset+0xAD8/0xB08/0xB48), not PORT-adjusted; fix = field accessors. MED = D102 weapon render_pos transient-arena aliasing (likely the ‘weapon model doesn’t draw’ cause) and the D56 watch-Model pool under-reservation.”