mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
Root cause (HIGH confidence): pure variable-timestep frame pacing, not PRNG/uninit state. osGetCount() is wall-clock on PC (libultra.c:88), and frametiming.c waitForNextFrame advances game logic by however many 60Hz ticks of real time elapsed per render -> two runs diverge 32-69% of pixels by frame 200-440. random.c seeding verified deterministic and correct; the PRNG stream only diverges as a victim of the pacing jitter. GE_DETERM fixed-tick mode: assessed NOT narrow (frame-coupled virtual clock deadlocks the boss.c retrace gate / pre-first-frame waits; robust version redesigns VI retrace + pacemaker semantics). Deferred with a full design in PCPortResearch.md F/D117 per AGENTS.md. tools_pc/framediff.py: structural/tolerant frame compare (16x12 grid mean-colour delta + non-clear-% swing + 16x16 aHash), --mask for the animated HUD, --exact for a future deterministic build, --update to refresh tools_pc/golden/. Reads .ppm and .png (built-in PNG decoder). Validated: two nondeterministic re-runs pass structural, --exact reports the real 32-69%, a wrong-frame pair fails. No game-logic or port-layer C changes; no probes left in tree.
4.2 KiB
4.2 KiB
PORT-LEARNINGS — recurring N64→PC bug classes
One-screen index into docs/PCPortResearch.md §F. Every investigation
subagent reads this first and appends any new generalisable quirk.
Details, evidence, and the fix for each instance live in §F under the
cited Dxx label.
A. Pointer-width struct growth (32→64) — the dominant class
A decomp struct with pointer fields, or one pun-allocated into a fixed N64-sized hole / hardcoded byte count, is larger on x86-64. Reading it from ROM bytes misaligns; allocating it N64-sized overruns adjacent state.
- Symptom: garbage pointer deref, corrupted neighbour field, delayed fault far from the cause.
- Fix: store embedded ROM addresses as
u32, cast at use site (PD ground truth); or give real inline storage /sizeof()-based alloc under#ifdef PORT. - Instances: D53.2 (ModelSlot pun), D56 (watch Model raw offsets), D57 (rwdata record count), D67 (image_entry), D79 (bg_room_data), D98 (struct player alloc), D100 (player.model inline Model), D101/D102 (ModelNode*/weapon Model puns), D115 (gunfire THROW* raw offsets).
- Open landmine: raw hardcoded-offset accessors into
struct player/struct hand— seedocs/AUDIT-M6-player-offsets.md.
B. 16-byte PC Gfx / Vtx vs 8-byte N64
Any buffer reservation, memcpy size, slot stride, or pool budget
expressed in N64 Gfx/Vtx units is half-size on PC.
- Instances: D50.6 (texCopyGdls copied only w0 of each 16-byte slot),
D58 (DL reserve 0x100→0x200), D85 (
bgWidenRoomGdl8→16 + bswap), D95 (2× master-DL buffer + raised mempool ceiling).
C. Big-endian rodata / ROM data read on little-endian PC
ROM assets and compiled-in .rodata are big-endian. Anything not run
through a converter or a runtime bswap fixup reads scrambled.
f32values are BE word pairs — a naive byte reversal off-by-one corrupts every float: D73 (sinf/cosfdupairs →DVAL()macro), D112 (d43_emit.py put_f32src[doff:doff+4][::-1]).- Header offset tables / pointers: D54 (cseq ALMidiHdr), D68 (Globalimagetable), D87 (ramromfilestructure), D88 (Usetup* tables).
- Packed bitfields cross byte boundaries differently: D78 / D83 (StandTile id/room, header mid/tail).
- Rule: prefer an offline sidecar converter (D43, D69, D88 pattern,
tools_pc/d*_emit.py) over a runtime fixup for a whole format.
D. N64 hardware idioms fast3d does not emulate
- Z buffer cleared by pointing the colour image at it + fill-rect → does
nothing in fast3d; must emit
G_CLEAR_DEPTH_EXT(D105). - LOD / detail mip tiles: fast3d fabricates a crop when detail textures are off → force base tile (D107).
- K0 segment-address folds (
OS_K0_TO_PHYSICAL | 0x80000000) and hand-inlinedBG_SEG_TO_PTRneed the(u32)32-bit wrap the macro has (D58, D84). - Portal near-plane: z==0 clip points project to ±1e20; x86-64 float
garbage can come back
min>max/ non-finite and slip past a degenerate-box check that clamps to full-screen on N64 (D106).
E. Process / method notes
- Investigation loop is: reproduce → env-gated capped probe → root-cause
→ narrow
#ifdef PORTfix → visual verify (GE_PCDUMP+tools_pc/pixcount.pyvsdocs/reference/n64-footage-*). - gdb launch mode is too slow for timing-dependent faults; gdb attach to an already-running process is fine and fast.
- Check
PD_PORT_CHECKOUTfor the PD analogue before writing anything new — same Rare engine family. - Don't re-investigate a closed §F finding or re-derive a format spec that already has a converter.
- The port is NOT frame-deterministic (D117):
osGetCount()is wall-clock on PC, and GE is a variable-timestep sim (frametiming.c waitForNextFrame→deltaFrames= 60 Hz ticks of real time elapsed per render), so "frame N" differs 15–40 % between runs. Usetools_pc/framediff.py <ppmdir>(structural: 16×12 grid mean-colour + non-clear-% + aHash,--mask X0,Y0,X1,Y1for the HUD,--updateto refreshtools_pc/golden/) — NOT an exact compare. AGE_DETERM=1fixed-tick mode was assessed not-narrow (redesigns retrace/tick semantics); design is in §F D117 if someone picks it up, after whichframediff.py --exactbecomes usable.