D303: model converter copied 1/4 of each star muzzle flash's vertices

The op-22 DisplayListPrimaryRecord count is the number of star-flash
arms; dorottex (and PD's modelRenderNodeStarGunfire) read count*4
vertices. d43_emit/d43_convert sized the array as count vertices, so
3/4 of every muzzle star came from neighbouring sidecar bytes and drew
as long spikes (M16 "shooting up"). Size it 16*4*count. Emitter
round-trip checks all pass; M16 renders a clean star headless.

Also TEMP GE_STARTWEAPON (bondview_r.c) for weapon repros. Re-run
tools_pc/d43_emit.py to regenerate data/pcmodels-*.
This commit is contained in:
James Dansereau committed 2026-09-26 18:15:17 -04:00
1 parent 00b0006971
commit d061ca3d12
7 files changed
+45 -4

No files matched your search

+1
View File
@@ -71,6 +71,7 @@ Two classes:
| `GE_D207S` | `src/game/gunfire.c` (player shot-sound block, `#ifdef PORT`) | **TEMP** (M-201): one line per player shot-sound decision: `D207S t=<g_GlobalTimer> hand item rate -> H1|H2|SKIP` (SKIP = both per-hand sound handles still held). Read-only, uncapped, cheap; use instead of `GE_AUDIOTRACE` for live captures (that probe does file I/O on the audio path). | **TEMP** (D207/D241) |
| `GE_D306C="f0 f1 [wmax]"` | `port/fast3d/gfx_pc.cpp` (after `zf_note_emit`) | **TEMP** (M-201): depth-state census of every emitted triangle in frames f0..f1 (GL depth mode, geometry_mode, other_mode_l, tile-0 texture, NDC centroid, z/w range, min w); optional `wmax` keeps near geometry only. Cap 60000 lines. | **TEMP** (D306 closed; remove post-release) |
| `GE_INTROCAM=<n>` | `src/game/bondview_r.c` (`#ifdef PORT`, intro camera pick) | **TEMP/test-only**: force the level intro's first camera index (Dam truck shot = 5). The PRNG draw is still consumed. Logs `D306: intro camera index` when set (or with `GE_D306C`). | **TEMP** (useful repro tool) |
| `GE_STARTWEAPON=<ITEM_IDS>` | `src/game/bondview_r.c` (`#ifdef PORT`, after the start-inventory setup) | **TEMP/test-only** (M-201, D303): adds the item + 400 rounds of its ammo and makes it the starting right-hand weapon (M16 = 13). Pair with `GE_INPUTSCRIPT` `ZHOLD`. | **TEMP** (useful repro tool) |
| `GE_D235` | `src/boss.c` (per-stage `while (!done)` block, after `memaReset`; `#ifdef PORT`) | D235 evidence probe: logs how many GL-texture-cache entries the per-stage `videoResetTextureCache()` call purges at each level load. A cold boot into a level should log 0; a death/restart (or any transition from a level that uploaded textures) logs >0 — direct evidence of the stale-entry mechanism. The clear itself is **not** gated on this var (it's the fix, see below); the var only controls the log line. | **live** (D235) |
| `GE_D235_OFF` | `src/boss.c` (same block) | **TEMP** A/B switch: skips the per-stage `videoResetTextureCache()`, i.e. reverts the D235 fix for one run. For comparing a restart with and without the fix. | **TEMP** (D235; remove when D235 closes) |
| `GE_D235_RELOAD=<frames>` | `src/boss.c` (same block + `bossMainloop` retrace branch) | **TEMP** reload harness: once the title stage loads after the first level (e.g. after a scripted watch Abort), wait `<frames>` retraces, then re-enter that level via `bossSetLoadedStage()`, the front end's own mission-start call. A headless death/restart without the pointer-menu walk. Pair with `GE_INPUTSCRIPT="800:START;1200:A;1300:RIGHT;1400:A"` (abort) on `-level_XX`. | **TEMP** (D235; remove when D235 closes) |
+1 -1
View File
@@ -181,7 +181,7 @@ D299,FIXED,Game.SkipIntro crashed selecting any save slot except one —,"FIXED
D300,FIXED,Hipfire look feels non-uniform (issue #89):,"FIXED, user-confirmed live —"
D301,FIXED,"fileGetIsCheatUnlocked() NULL-deref, unguarded on every call site, not just the D299/SkipIntro one (GitHub #87, two reports with an identical crash PC/fault ad…",FIXED locally on release/v0.3.0 (not pushed) —
D302,FIXED,"Render FPS regression to ~40-45fps on release/v0.3.0 (user QA on the local v0.3.0 pre-release build, 2026-09-18) —","FIXED — 4 uncached getenv() calls added by D236 passes 10/12/14/16 directly inside gfx_sp_tri1 (port/fast3d/gfx_pc.cpp, per-triangle) cached with the same static int x = -1 pattern D250 established e…"
D303,OPEN,"Some muzzle flashes (M16 named specifically, ""and some others"") draw wrong —","OPEN — CONFIRMED PORT BUG (2026-09-26, user vs 1964:"
D303,FIXED,"Some muzzle flashes (M16 named specifically, ""and some others"") draw wrong —","FIXED (M-201, 2026-09-26):"
D304,FIXED,"Mouse sensitivity menu overhaul (user request, 2026-09-18):","FIXED — see full entry for both the range/parity pass and the same-day follow-up (collapsed to a single Input.MouseSensitivity menu row + fixed overlayUpdateScroll()'s unconditional bottom-anchor, th…"
D305,FIXED,"Steam Deck SIGSEGV on the v0.3.0 pre-release build (Cavern, early in the level, Game.AllUnlocked previously enabled on the session/save) —",FIXED (defensive guard) —
D306,CLOSED,"Dam intro/truck wheels render with odd transparent-looking areas, possibly z-fighting (user QA, 2026-09-18).","CLOSED — faithful, confirmed on 1964 (M-201):"
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
181 D300
182 D301
183 D302
184 D303
185 D304
186 D305
187 D306
+12 -1
View File
@@ -589,7 +589,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D300 | **Hipfire look feels non-uniform (issue #89): mouse px routed through the N64 analog stick's quadratic natural-turn curve, so slow motion is "almost unrecognised" and fast motion saturates at full turn speed by ~13 px/poll.** — full `## D300` entry at file tail | FIXED, user-confirmed live — WI-1 per `docs/dev/GEPD-INPUT-PLAN.md`: new `hipDirectCompute()` (`port/src/input.c`) writes `vv_theta`/`vv_verta` directly, mirroring GEPD's hipfire formula and D194's existing aim-mode direct-write pattern — linear px→degree, FOV-scaled, dt-normalized, bypassing the stick curve entirely. Safety-gated on `bonddead`/`outside_watch_menu`/`pause_state` plus (M-190, 2026-09-18) the scripted-camera-mode check the original safety-gates writeup named but never implemented — mouse could freely move the camera during POSEND/INTRO cutscenes until this session. `Input.MouseDirectLook` (default 1) is the escape hatch back to the legacy stick path. Build/link-verified; user playtested live: "input already feels better," then again post-M-190: "ok yeah looks good." |
| D301 | **`fileGetIsCheatUnlocked()` NULL-deref, unguarded on every call site, not just the D299/SkipIntro one (GitHub #87, two reports with an identical crash PC/fault address).** — full `## D301` entry at file tail | FIXED locally on `release/v0.3.0` (not pushed) — NULL-guarded `fileGetIsCheatUnlocked()` itself (`src/game/file2.c`, `#ifdef PORT`) rather than only its one known trigger route; build-verified, crash-free smoke test. Live confirmation against the actual #87 repro still owed — a synthetic `GE_STARTMENU=6` repro attempt did not reproduce the crash even pre-fix, so the exact #87 trigger condition (why the save lookup fails) remains unconfirmed. |
| D302 | **Render FPS regression to ~40-45fps on release/v0.3.0 (user QA on the local v0.3.0 pre-release build, 2026-09-18) — a reintroduction of D250's own uncached-`getenv()`-in-the-hot-path bug class.** — full `## D302` entry at file tail | FIXED — 4 uncached `getenv()` calls added by D236 passes 10/12/14/16 directly inside `gfx_sp_tri1` (`port/fast3d/gfx_pc.cpp`, per-triangle) cached with the same `static int x = -1` pattern D250 established elsewhere in the file. Verified via `GE_D193=1 -level_33` (Dam): rock-steady `render=59-61` post-fix, matching D250's own post-fix baseline exactly (pre-fix D250 pinned this level at 45-50fps). Not yet re-confirmed by the user's own hands-on playtest. |
| D303 | **Some muzzle flashes (M16 named specifically, "and some others") draw wrong — the flash renders very long, extending straight up from the gun (user QA on the v0.3.0 pre-release build, 2026-09-18).** — full `## D303` entry at file tail | OPEN — CONFIRMED PORT BUG (2026-09-26, user vs 1964: M16/AR33 muzzle flash shoots up and draws much higher on PC; N64 shows a normal star/burst at the muzzle). Earlier: OPEN — 1964 comparison owed; absorbs D220 (user 2026-09-26: M16/AR15 flash rises regardless of FOV). Earlier: OPEN — investigated: pipeline + weapon stats + model conversion ruled out; GE_D303 capture probe in-tree (`port/fast3d/gfx_pc.cpp`), blocked on a live firing repro. |
| D303 | **Some muzzle flashes (M16 named specifically, "and some others") draw wrong — the flash renders very long, extending straight up from the gun (user QA on the v0.3.0 pre-release build, 2026-09-18).** — full `## D303` entry at file tail | FIXED (M-201, 2026-09-26): d43_emit sized op-22 star-flash vertex arrays as count, not count*4 arms; regenerated sidecars render a clean star. Earlier: OPEN — CONFIRMED PORT BUG (2026-09-26, user vs 1964: M16/AR33 muzzle flash shoots up and draws much higher on PC; N64 shows a normal star/burst at the muzzle). Earlier: OPEN — 1964 comparison owed; absorbs D220 (user 2026-09-26: M16/AR15 flash rises regardless of FOV). Earlier: OPEN — investigated: pipeline + weapon stats + model conversion ruled out; GE_D303 capture probe in-tree (`port/fast3d/gfx_pc.cpp`), blocked on a live firing repro. |
| D304 | **Mouse sensitivity menu overhaul (user request, 2026-09-18): aim speed/turn speed sliders weren't "1:1", had too little range, and (follow-up) risked decoupling into a bad state; F10 menu also had a click-jump bug.** — full `## D304` entry at file tail | FIXED — see full entry for both the range/parity pass and the same-day follow-up (collapsed to a single `Input.MouseSensitivity` menu row + fixed `overlayUpdateScroll()`'s unconditional bottom-anchor, the actual cause of the reported "menu jumps to the setting below on click"). Build-verified (Windows) both passes; headless overlay smoke test clean after each. **Live F10 playtest done (2026-09-18): user confirmed the collapsed single-sensitivity menu works, the click-jump is gone, and the D251 bottom-row duplication no longer occurs.** |
| D305 | **Steam Deck SIGSEGV on the v0.3.0 pre-release build (Cavern, early in the level, `Game.AllUnlocked` previously enabled on the session/save) — same exact FAULT ADDR 0x62 / `sndHandleEvent` signature as D285, reproduced AFTER D285's own lock fix was already live-verified crash-free on real hardware.** — full `## D305` entry at file tail | FIXED (defensive guard) — `sndHandleEvent`'s preempt-scan (`src/snd.c`) dereferenced its list-walk pointer before ever checking it for NULL (a do-while always runs its body once); D285's `osSetIntMask` lock only prevents another thread from emptying the list mid-scan, not a scan that starts with an already-empty list, so the crash could still happen even with that fix in place. Added the missing `iterState != NULL` guard before the loop, matching D255's established skip-instead-of-dereference precedent. Root desync mechanism (why pool-exhaustion accounting and the list can disagree) still not identified; `Game.AllUnlocked` correlation not proven, only reported. **Live-verified on the Deck (2026-09-18):** user ran two test sessions on Cavern with the updated build, plus toggled `Game.AllUnlocked` off then back on and relaunched — no crash in any of them. |
| D306 | **Dam intro/truck wheels render with odd transparent-looking areas, possibly z-fighting (user QA, 2026-09-18).** — full `## D306` entry at file tail | CLOSED — faithful, confirmed on 1964 (M-201): all PC-specific leads refuted (FOV, depth precision, depth state, near plane, wheel transforms); it is the authored truck geometry seen from intro camera 5 under the truck. Absorbs D291. Earlier GE_ZF "real fighting" was a probe-quantisation artefact. |
@@ -12256,6 +12256,17 @@ Adversarial pre-ship review of the whole v0.3.0 window (147 commits since v0.2.2
**Status: REOPENED (was incorrectly treated as fully covered by the D219 closure) — OPEN, root cause still unconfirmed for this specific manifestation.**
**M-201 (2026-09-26, v0.4.0): ROOT CAUSE FOUND + FIXED — the offline model converter copied only a quarter of every star muzzle flash's vertices.**
- **Confirmed port bug:** user side-by-side with 1964: the N64 M16/AR33 shows a normal star/burst at the muzzle; PC draws a long flame shooting up. D220 (flash "rises at high FOV") merged here: FOV-independent.
- **Headless repro:** new TEMP `GE_STARTWEAPON=13` (M16, `bondview_r.c`, adds 400 ammo) + `GE_INPUTSCRIPT` `ZHOLD` on Dam: every flash frame shows the upward spike (`scratch/d303_sheet.png`).
- **Ruled out by dump:** gun model bindings (group 0 body mtx 0, group 1 muzzle mtx 1 = `Switches[3]`, group 2 billboard mtx 2 = `Switches[2]`) and both flash matrices (`rw1` Z-stretched ×6 = `MuzzleFlashExtension`, as PD's `muzzlez`) are as designed; every matrix helper matches PD (`mtx00015ea8` = column_3, `mtx00016710` = row_3, `mtx00016e98` = axis-angle).
- **Cause:** op-22 `DisplayListPrimaryRecord` (PD `modelrodata_stargunfire`) stores the number of flare **arms**; `dorottex()` (`model.c`) and PD's `modelRenderNodeStarGunfire` read `count*4` vertices (`src += 4` per arm). `tools_pc/d43_emit.py` (and its `d43_convert.py` twin) sized the vertex region as `16*nv`, copying only `nv` vertices. The M16 star (6 arms, 24 vertices) kept vertices 0–5; 6–23 came from whatever followed in the PC sidecar (dump: `src[6]=(4,0,0) st=(-28648,28709)`, `src[7].x=-28960`), rendering as long spikes on top of the real arms.
- **Fix:** size op-22 vertex regions as `16*4*nv` in both tools. `d43_emit.py ntsc-final` re-run: ALL CHECKS PASSED (round-trip field validation, region tiling, buffer bounds); 512 sidecars regenerated. After: `src[6..7]=(289,±20)` real arm vertices; the M16 renders a clean 6-arm star (`scratch/d303_after.png`). Affects every star-flash weapon. Golden gate 3/3.
- **Note:** `data/pcmodels-*` is generated (gitignored). Existing installs must re-run `python3 tools_pc/d43_emit.py <region>`; bundles run the shipped emitter.
**Status: FIXED (M-201, headless-verified 2026-09-26; user visual check owed).**
## D304 — Mouse sensitivity menu overhaul (user request, 2026-09-18): aim speed/turn speed sliders weren't "1:1" and had too little range.
**User request, immediately after cutting the v0.3.0 Deck pre-release build:** review the mouse sensitivity configs against "the new logic" (D194/D300's GEPD-direct-write input model) and overhaul the F10 menu — specifically, "aim speed/turn speed are not 1:1 in the menu" and players need "more range for mouse sensitivity/settings," referencing the fact that GE's whole aim/hipfire model was ported from the GEPD Mouse Injector (`reference/mouse-injector/`, our D194 mirror), which itself mirrors Perfect Dark's own input scheme.
+4
View File
@@ -273,6 +273,10 @@ framing — it never looked like game logic to begin with.
IDO accepts a struct that redeclares an inherited member name (`inherits ObjectRecord;` then its own `pad`); GCC rejects it, so the port renames one. **The rename changes which field existing code reads**: on N64 the later declaration shadows the inherited one, so `rec->pad` meant the derived record's own field. After renaming it (`lookpad`), unchanged game code silently binds to the inherited field instead. D307: `setupCctv` read the mount pad as the look-at pad and every security camera faced its wall. When a port rename resolves a duplicate, grep every `->name` on that struct type and decide per site which field N64 meant (PD's named-field sources are the tie-breaker).
### A-cnt. A record's "count" field may not count what the converter copies (D303)
The offline model converter sizes each record's trailing arrays from its count field. Check what the *runtime consumer* does with that count before trusting its name: GE's op-22 `DisplayListPrimaryRecord.numVertices` is really the number of star-flash ARMS, and `dorottex()` reads `count*4` vertices (PD names it `stargunfire.unk00` and reads `unk00*4`). Sizing by the field name dropped 3/4 of every muzzle star; the missing vertices came from neighbouring sidecar bytes and drew as spikes. For every record type, derive the copied extent from the reading loop, and cross-check PD's typed struct.
## B. 16-byte PC `Gfx` / `Vtx` vs 8-byte N64
Any buffer reservation, `memcpy` size, slot stride, or pool budget