docs(D263): confirm Ourumov/Trevelyan standoff timeout fallback is by design, not a bug

User reproduced the "stuck standoff" 3-4 times; traced to a hard
timeout in ai_22 (label 0x13, UsetuparkZ.c) that fires the execution
unconditionally after ~2.5 min if no other trigger fires first. User
waited it out and confirmed it resolves normally with dialogue -- not
a freeze, not a new bug. Documented so it isn't re-flagged.

Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
This commit is contained in:
James Dansereau committed 2026-09-15 20:17:17 -04:00
1 parent 269de57941
commit 791bf6b7c0
1 file changed
+1 -1
+1 -1
View File
@@ -556,7 +556,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D261 | **v0.2.1 (user request: "fix watch menu sensitivity for wasd/controller buttons"): holding W/S (or a fully-pressed stick axis) on the watch inventory page crawled one item per tap — no repeat.** Root cause is the interaction of two existing behaviours: keyboard W/S map to `stickY = ±STICK_MAX (0x50)` and SDL pads peg at 0x80, both *outside* the smooth-scroll band (0x1f..0x45) that D118d intentionally left for the port — so a held press only ever hit the latched single-step path (`watch_stick_y_pressed_*`, one step per press edge, no repeat). On N64 the same stick level was the *fast-scroll* band (one step per frame), which D118d removed because digital sources peg there and it became runaway scroll. **Fix (port-only, `#ifdef PORT` in `src/game/options.c`):** a bounded auto-repeat helper `geWatchStickFastStep()` — while the stick is held past the smooth band (≥ 0x46 up / ≤ −0x45 down), step one extra item every `GE_WATCH_NAV_RATE` (6) frames after an initial `GE_WATCH_NAV_DELAY` (15)-frame hold; the first press still goes through the untouched latched path, so a tap behaves exactly as before, and the smooth band + D-pad/C-button edge-repeat are unchanged. Called from `game_options_inventory_navigation()` only. **Verified:** build green; headless smoke (`GE_INPUTSCRIPT` long-W-hold on the inventory page) runs clean. Feel-check (repeat rate) owed to the user — both constants are at the top of the helper for a one-number tune. **User feel-check (v0.2.1 test pass): repeat "feels fine for now", but W/S is still too sensitive overall — desired behaviour is tactile one-press-one-item with no risk of scrolling the whole list on an over-held key; candidate tweaks (not yet done): longer `GE_WATCH_NAV_DELAY`, slower `GE_WATCH_NAV_RATE`, or gating the auto-repeat off for keyboard taps entirely (repeat only for analogue stick). Logged, no code change. | FIXED — feel-check received; W/S sensitivity follow-up logged above. |
| D262 | **v0.2.1 (user report): "3D models missing on watch menu screen item select" — the selected-item 3D preview does not appear on the watch inventory page.** User visually confirmed this session: no 3D model on the item-select page where one should be (default selection is *unarmed*, which has no model — a weapon must be scrolled to with S/DOWN). Headless repro (`GE_INPUTSCRIPT` pulse train + `GE_PCDUMP`, Dam `-level_33`): watch can't open during the intro freeze (`bondviewFrozenMoveBond` zeroes input while `g_CameraMode == CAMERAMODE_INTRO`; freeze ends ~frame 1400–1500 on Dam, and controller reads run faster than sim ticks, so scripts are timed by trial); once open + paged to INVENTORY (R_JPAD), the watch UI renders but the centre of the face is empty for every item. **Root cause found and fixed as D264** (`renderdata.flags == 0` in the watch subdraw pass — the cross-global ModelRenderData template read zeroes on PC; `subdraw()` then gated *every* geometry node, emitting exactly 1 word instead of the model's commands). Residual: with flags restored, the preview emits matrix/light-setup words (71 for item 5) but dump frames still show no visible centre geometry — a possible second layer (LOD/distance gating or fast3d rasterisation of the sub-DLs) remains unverified; user visual re-check pending. | FIXED (primary cause, D264) — pixel-level confirmation pending user run. |
| D264 | **D262's root cause (ABI/layout class, cf. D140/D52): the watch item preview's `ModelRenderData` template is read across two adjacent globals that no longer sit adjacent on PC.** `set_enviro_fog_for_items_in_solo_watch_menu()` (`src/game/gunfire.c`) starts with `ModelRenderData renderdata = *((ModelRenderData *) (&D_80035D00));` — a 64-byte copy that on N64 spans `D_80035D00` (a zeroed u32, .bss) *and* `D_80035D04[] = {1, 3, 0…}` (.data): the effective template is the one documented in the comment above `D_80035D00` in `gun.c` — `basemtx=NULL, zbufferenabled=TRUE, flags=3, rest zero` (`CULLMODE_BOTH` is 0). The PC link places .bss and .data in separate sections, so the read is all zeros: **`flags==0`**, and every geometry emitter in the subdraw node dispatch gates on `renderdata->flags & 1/2` (e.g. `modelRenderNodeDl`) — so `subdraw()` emitted exactly one word (`gSPSegment`) per preview, no matter the item. Probes this session: entry gates all pass (hand has item, model header non-NULL), node tree present (~400 nodes, isLoaded=1), world-path geometry nodes carry valid built DLs — but in the watch pass `flags=0` on every node. **Fix (`#ifdef PORT`, values identical to the N64 ROM bytes):** explicit template — `ModelRenderData renderdata = {0}; renderdata.zbufferenabled = TRUE; renderdata.flags = 3;`. Verified at Gfx level: preview emission went 1 → 71 words (item 5) / 81 (mid-scroll) / 9 (item 47, knife-class). Note for follow-up: the 71-word dump is a repeating {matrix/segment/light} unit with no `gSPDisplayList` in the first 24 words — if the user's re-check still shows no visible model, next suspects are LOD/distance gating of the local watch-space matrices (`modelUpdateDistanceRelations`, cf. 7c48f664) or fast3d handling of the preview sub-DLs (segment 3 / render modes). | FIXED — build green, probes removed; visual confirmation pending. |
| D263 | **v0.2.1 (user report): Facility — "ouromov not shooting Trevelyan"; the scripted opening beat never triggers. RESOLVED: not a port bug — the beat is proximity/LOS-triggered exactly as on N64, and the full chain (incl. the execution shot) was verified firing end-to-end on PC.** The triage suspects (tick pacing, camera-mode gate, silent anim lookup) were all ruled out at runtime. **The beat, fully mapped** (static decode of `UsetuparkZ.c` — remember every DBYTE/TBYTE in the dump is bswap16/32'd relative to game interpretation): Trevelyan = guard chr 67 (body 9 = Trevelyan_006, pad 131) on ai_18: walks to pad 124, wanders, then loops a sees-Bond check; **only when Bond is in his vision** (range <10k, facing ±110°, fog, random gate ≈1/49/tick, LOS + *same stand tile* via `chrCanSeeBond`) does he jump to ai_19: runs at Bond, alarm + dialogue timers, sets an objective bit; BG list ai_41 (ID 0x1000, auto-started) polls that bit and spawns **Ourumov (body 7) at pad 88 with ai_23** plus a 6-soldier squad; ai_23 renumbers him to chrnum 78 and arms him → ai_22: runs to pad 123, waits for Trevelyan within 0x1400 of it, ~95 s dialogue cascade, then the **execution shot** (`HitChrWithItem` from 78 on 67). It is a multi-minute sequence that *starts* only when the player walks Bond west and down a floor into Trevelyan's room — Bond spawns 8900 units away on the upper floor with LOS blocked (probed directly: `stanTestLineUnobstructed` = obstructed, different stan tiles), identical geometry to N64. **Runtime verification** (`-level_34`, `#ifdef PORT` probes in the AI executor/vision path, all since removed): all 15 BG lists + guards start; Trevelyan alive on ai_18; every vision gate passes except the geometric ones at spawn distance; player movement confirmed working (note for future headless tests: under the port's default SOLITARE/natural-pitch scheme, walking is C-button/D-pad fed — W→C-up — and the analog stick carries pitch, so `GE_INPUTSCRIPT` SUP/SDOWN tokens do *not* walk Bond there; use UP/DOWN pulses). **Decisive test:** a temporary PORT-only teleport probe dropped Bond 65 units from Trevelyan at t≈12 s → seconds later: vision fires (chr 67 → ai_19), objective bit set, `SpawnChr body=7 pad=88` + squad, chrnum→78 on ai_23; ~2½ min later **`HitChrWithItem from_chr=78 target=67 part=8 item=6`** — Ourumov executes Trevelyan. Most likely explanation of the report: the tester never walked Bond into Trevelyan's sightline (or pre-D253 crash masked it). No game-code or port-code change required. **Kept from this session (test-harness only):** `port/src/input.c` — `GE_INPUTSCRIPT` analog-stick tokens were applied *after* the stick was already written out, so SUP/SDOWN/SLEFT/SRIGHT were silently discarded; the script override now runs before the clamp/write. | RESOLVED (not a bug), but refined (v0.2.1 further user testing, 2026-09-15) — beat verified firing end-to-end on PC incl. execution shot; **user playtest confirmed the sequence plays normally.** **Refinement: the exact trigger behavior is significantly sensitive to Bond's precise location and does not fire 1:1 identically to the N64 original across different approach paths/positions** — consistent with the beat's own documented sensitivity (range/facing/LOS/same-stand-tile + a ≈1/49-per-tick random gate, see the mapped mechanism above), so this is likely the same finicky-trigger behavior rather than a new bug, but not confirmed byte-identical to N64 for every approach. **User confirms this does NOT block level progression** either way — logged for awareness, not tracked as an open defect. Probes removed; input-script stick fix kept. |
| D263 | **v0.2.1 (user report): Facility — "ouromov not shooting Trevelyan"; the scripted opening beat never triggers. RESOLVED: not a port bug — the beat is proximity/LOS-triggered exactly as on N64, and the full chain (incl. the execution shot) was verified firing end-to-end on PC.** The triage suspects (tick pacing, camera-mode gate, silent anim lookup) were all ruled out at runtime. **The beat, fully mapped** (static decode of `UsetuparkZ.c` — remember every DBYTE/TBYTE in the dump is bswap16/32'd relative to game interpretation): Trevelyan = guard chr 67 (body 9 = Trevelyan_006, pad 131) on ai_18: walks to pad 124, wanders, then loops a sees-Bond check; **only when Bond is in his vision** (range <10k, facing ±110°, fog, random gate ≈1/49/tick, LOS + *same stand tile* via `chrCanSeeBond`) does he jump to ai_19: runs at Bond, alarm + dialogue timers, sets an objective bit; BG list ai_41 (ID 0x1000, auto-started) polls that bit and spawns **Ourumov (body 7) at pad 88 with ai_23** plus a 6-soldier squad; ai_23 renumbers him to chrnum 78 and arms him → ai_22: runs to pad 123, waits for Trevelyan within 0x1400 of it, ~95 s dialogue cascade, then the **execution shot** (`HitChrWithItem` from 78 on 67). It is a multi-minute sequence that *starts* only when the player walks Bond west and down a floor into Trevelyan's room — Bond spawns 8900 units away on the upper floor with LOS blocked (probed directly: `stanTestLineUnobstructed` = obstructed, different stan tiles), identical geometry to N64. **Runtime verification** (`-level_34`, `#ifdef PORT` probes in the AI executor/vision path, all since removed): all 15 BG lists + guards start; Trevelyan alive on ai_18; every vision gate passes except the geometric ones at spawn distance; player movement confirmed working (note for future headless tests: under the port's default SOLITARE/natural-pitch scheme, walking is C-button/D-pad fed — W→C-up — and the analog stick carries pitch, so `GE_INPUTSCRIPT` SUP/SDOWN tokens do *not* walk Bond there; use UP/DOWN pulses). **Decisive test:** a temporary PORT-only teleport probe dropped Bond 65 units from Trevelyan at t≈12 s → seconds later: vision fires (chr 67 → ai_19), objective bit set, `SpawnChr body=7 pad=88` + squad, chrnum→78 on ai_23; ~2½ min later **`HitChrWithItem from_chr=78 target=67 part=8 item=6`** — Ourumov executes Trevelyan. Most likely explanation of the report: the tester never walked Bond into Trevelyan's sightline (or pre-D253 crash masked it). No game-code or port-code change required. **Kept from this session (test-harness only):** `port/src/input.c` — `GE_INPUTSCRIPT` analog-stick tokens were applied *after* the stick was already written out, so SUP/SDOWN/SLEFT/SRIGHT were silently discarded; the script override now runs before the clamp/write. | RESOLVED (not a bug), but refined (v0.2.1 further user testing, 2026-09-15) — beat verified firing end-to-end on PC incl. execution shot; **user playtest confirmed the sequence plays normally.** **Refinement: the exact trigger behavior is significantly sensitive to Bond's precise location and does not fire 1:1 identically to the N64 original across different approach paths/positions** — consistent with the beat's own documented sensitivity (range/facing/LOS/same-stand-tile + a ≈1/49-per-tick random gate, see the mapped mechanism above), so this is likely the same finicky-trigger behavior rather than a new bug, but not confirmed byte-identical to N64 for every approach. **User confirms this does NOT block level progression** either way — logged for awareness, not tracked as an open defect. Probes removed; input-script stick fix kept. **M-141: re-confirmed by user, one more mechanism now documented — the standoff has a hard TIMEOUT fallback, not just the shot-miss/objective-bit gates.** User reported (misread at first as a new freeze) reaching the Ourumov/Trevelyan standoff, waiting without shooting or triggering anything else, and after a couple of minutes Ourumov delivered his lines and executed Trevelyan on his own — **this is by design**: `ai_22`'s label `0x05` OR-gate (four early-exit conditions: two objective bits, a "Bond's shot missed" check, and a third objective bit set by an unrelated guard's surrender elsewhere in the level) is not the only path forward — falling through the whole dialogue-timer cascade (`label 0x42`'s chain of `if_local_timer_greater_than` checks) reaches `label 0x13`, which unconditionally fires `chr_hit_body_part_with_item_damage(0x43, 0x08, 0x06)` (the execution) once enough real time has elapsed, no player action required. Matches this row's own earlier "~2½ min later" measurement. **Do not re-flag "stuck standoff" as a bug without confirming the user waited the full timeout window first.** |
| D265 | **D236 investigation (port-layer, `port/fast3d/gfx_pc.cpp`): the tree cards are IA8/IA4, not IA16 — and `import_texture_ia4` decoded the RDP's I2:A2 texel as I3:A1, inverting the alpha ramp. Fixed; awaiting user visual re-test.** The earlier sessions misidentified the format: a live tri-time tile census (probe in `gfx_sp_tri1`, since removed) shows level_36's tree-card classes are (a) **IA8, line=64B → 64×16** (texdump r107), drawn OPA/bilerp/1-cycle/AC_NONE — *genuinely fully opaque* (A nibble F on 1023/1024 texels; opaque on N64 too) — and (b) **IA4, line=64B → 128×16** (texdump r117), drawn ZMODE_XLU/point/2-cycle with m2a=CLR_MEM/m2b=1MA, i.e. texel-alpha blended. There are no 32-wide IA tiles at tri time; the prior "32×32 tile" reading was wrong, and the whole earlier IA16 byte-pair/I11:A5 debate (r107) was analyzing an IA8 image as byte-pairs. **The wall's root cause:** r117 decoded as I2:A2 is a strip of ~13 discrete conifer silhouettes (trunk+canopy shapes visible in the ASCII render, `scratch/r117_ia4_alpha.png`) with a 3-level alpha ramp {0: 95% of texels, 85, 170} — faint dithered distant trees on N64. But `import_texture_ia4` read each nibble as intensity=`part>>1` (3-bit, SCALE_3_8) + alpha=`part&1`: A2=1 canopy pixels became **fully opaque**, A2=2 edge dithering became **invisible** → opaque repeating tree silhouettes = the "wall". The bug is inherited verbatim from the PD port (`pd_port/port/fast3d/gfx_pc.cpp` has the identical I3:A1 read — latent there, fatal for GE's tree strip). **Fix (applied):** `intensity = part>>2`, `alpha = part&3`, both via new `SCALE_2_8`; this is the hardware-correct RDP IA4 layout. Also cleaned up: the ia16 importer comment now states plain I8:A8 (the byte-pair read *is* correct IA16; the old "r107 proof" was misidentified data), and the earlier part-1 dimension fix stays (harmless/correct for real IA16 tiles). **Caveats/owed:** (1) user visual re-test of Surface 1 trees — headless A/B was in flight when work halted; a cross-run region diff was inconclusive (camera drift between captures); (2) the decode change affects *every* IA4 tile game-wide — regression-sweep other levels for dithered/alpha IA4 art that now renders with correct 3-level alpha; (3) if a hard repeating band remains after the fix, it is the opaque IA8 strip (a), which matches hardware and D236 needs re-scoping. **OUTCOME (user re-test): NEGATIVE — trees still render as the wall with the I2:A2 decode applied; the change was fully reverted (`git checkout` of `port/fast3d/gfx_pc.cpp`, back to the original PD-derived importers) and work is parked.** The census/RM/texture data above stands as evidence for the next attempt: the decode was *necessary* (I2:A2 is the hardware layout and r117 is unambiguously an authored tree strip under it) but *not sufficient* — remaining suspects, in order: (i) fast3d's blending of these XLU/2-cycle cards (texel alpha reaching the GL blend at all — the `texture_edge`/`SHADER_OPT_TEXTURE_EDGE` path forces `a>0.19 ? 1 : discard`, which for a 3-level ramp quantizes to opaque/discard, i.e. still hard-edged), (ii) per-card UV windowing/placement (all captured cards sampled the same u:0..64/v:0..16 window — confirm against N64), (iii) fog/distance. Note for the next session: verify what the N64 actually shows in that region before more port-side changes (the opaque IA8 strip is faithful to hardware either way). | PARKED — reverted to baseline; evidence + suspect list above. |
| D258 | **PAL/JP asset conversion is broken at the source-data level — `scripts/filelist.e.csv` and `filelist.j.csv` carry naming defects, so `gen_romassets.py e/j` (and the d43/d69/d88 emitters' `find_row`) cannot resolve PAL/JP builds. v0.2.0's drop-in ROM feature therefore ships NTSC-U only.** Discovered while scoping issue #6 (drop-in ROM). **PAL:** 31 of the 512 model rows in `filelist.e.csv` have truncated/renamed basenames (an extraction artifact — e.g. `Pbridge_console` ×6 where the resource table says `Pbridge_console1aZ…3bZ`, `Pchrgrenadelauncher` for `PchrgrenadelaunchZ`). **JP:** one font row is mislabeled — `fontBankGothic_fontchartable.bin` @3044276 is named `fontZurichBold_fontchartable.bin` (a duplicate label). Both were proven without a PAL/JP ROM: (a) greedy two-pointer sequence alignment of the 512-row `file_resource_table.inc.c` model order against each CSV's model rows gives an exact 1:1 match (481 by-name + 31 positional for PAL), offsets strictly non-decreasing, and zero size anomalies vs US (all within [0.5×, 2×]); (b) JP font row proven by the contiguity invariant — Gothic kerning @3043600+676 = 3044276 exactly hits the mislabeled row, whose size (8716) equals the US Gothic chartable, while Zurich's own chain is contiguous one row later. **Consequence:** no PAL or JP PC exe has ever been buildable from this tree (latent — only NTSC was ever exercised); the CSV `file` column is a label only (emitters look up by basename, never read the path), so the repair is a pure data fix: rename the 31 PAL model rows + 1 JP font row to the exact table symbols. **Scope decision (v0.2.0):** ship drop-in ROM support for NTSC-U only — `prepare-assets.py`/the frozen `ge007-convert` gate non-US ROMs with a clear message after SHA-1 region detection; the CSV repair + multi-region matrix + per-region exes are deferred to the Part B follow-up. | OPEN — repair path verified, not applied (out of v0.2.0 scope). |
| D284 | **`port/src/random.c`'s MIPS64 `dsll32`/`dsrl32` shift helpers were backwards (modelled as 32-bit shifts, not "shift by n+32 on the full 64-bit register"), desyncing the PC PRNG stream from the N64's from the very first draw, in every release to date (M-138 security/fidelity review; fix decided M-139b).** Fix: `dsll32(x,n) = x << (n+32)`, `dsrl32(x,n) = x >> (n+32)`, no masking — matches real MIPS64 semantics and `src/random.s`/`src/game/chrObjRandom.s` line-by-line. Verified by hand trace + an independent standalone C reproduction (both diverge from the buggy stream from draw 1). Full body below. | FIXED, 21-level sweep clean, no crashes — RNG stream changes from every prior build; existing replays will diverge, savegames unaffected |