forked from Ports/goldeneye-pc-port
- 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.
126 lines
12 KiB
Markdown
126 lines
12 KiB
Markdown
# 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.”*
|