Files
goldeneye-pc-port/docs/dev/AUDIT-M6-player-offsets.md
T
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

126 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
`continue`s and nothing is emitted. Cheap to instrument.
## Recommended fix order
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.”*