Files
goldeneye-pc-port/docs/SMALL-FIXES.md
T
James Dansereau 99c5926924 D165/D166: front-end 1:1 pointer P-controller + speed-proportional hipfire pitch pulses
port/src/input.c only (port-layer, no game-code change):

D165 - the M-30 menu 'pointer mode' fed mouse velocity into the stick, but
front.c frontUpdateControlStickPosition integrates the stick as a velocity
into a screen-pixel cursor -> velocity^2 feel. Replace with a P-controller:
maintain menuEst{H,V} (our estimate of the game cursor, stepped each poll with
front.c's exact recurrence via menuCursorStep), accumulate menuTgt{H,V} from
raw mouse motion, emit stick = clamp(6.0*(target-est), +/-70). Gain 6.0 =>
per-poll error decay 0.45, no overshoot, ~8-poll settle, parks under the
game deadzone with no drift. Input.MenuPointerMode (default 1) keeps the old
velocity mode as a fallback; Input.MenuPointerSpeed scales the mapping;
GE_INPUTLOG prints an est/tgt/eff/stick trace. No mouse motion => emitted
stick unchanged => prior behaviour exactly.

D166 - hipfire mouse pitch was a fixed digital threshold while yaw is analog.
Emit the C-button as a duty-cycled pulse whose frequency scales with mouse-Y
speed (fast = solid hold as before, slow = sparse taps). Input.HipfirePitchSpeed
tunes it. Aim-mode (RMB, already analog) untouched.

Build green; -level_09 (~360 frames) + GE_STARTMENU=6 smoke crash-free.
Feel-check owed (headless can't drive the mouse).
2026-08-31 12:18:15 -04:00

460 lines
24 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.
# SMALL-FIXES — ready-to-execute fix notes for small / low-level open items
**Read-only document.** This file records *proposed fixes and verification
procedures* for the small, low-level bugs and noted issues that are parked in
`BACKLOG.md`, `GRAPHICS-BACKLOG.md`, `HANDOFF.md`, and the §F/§H finding log.
No code was changed when writing it (M-28). Each item is self-contained enough
to be picked up cold; items marked **VERIFY-ONLY** already have their fix in
the tree and only need confirmation + commit hygiene.
Standing rules for every item below:
- Game-code edits must stay inside the narrow `#ifdef PORT` ABI/layout
exception (AGENTS.md #2); port-layer work has no such limit. Anything
bigger than described here → stop, write it up with a confidence rating.
- New findings get the next free Dxx in `docs/PCPortResearch.md` §F/§H + an
index row; recurring bug classes go to `docs/PORT-LEARNINGS.md`.
- After any converter change: full regen chain
`python tools_pc/d43_emit.py ntsc-final && python tools_pc/d69_emit.py
ntsc-final && python tools_pc/d88_emit.py ntsc-final --regen`.
- Verification anchors (quiet machine only — D153): target level boots to ≥1
non-degenerate frame (`tools_pc/pixcount.py`), `-level_09` + `-level_20`
unregressed (`tools_pc/framediff.py`, use `--mask` for the HUD).
- Crashes: `tools_pc/debug.ps1` (gdb + FATAL line + backtrace → `gdb.txt`).
---
## A. VERIFY-ONLY — fix is in the tree, needs confirmation
### A1. D154 — `bg.c` room-geometry hit-test GBI parser (committed WIP `072b5c44`)
**Status:** written + builds green; **never exercised** (a no-input
`GE_PCDUMP` capture fires no bullets, and the M-28 box was thrashed).
**What it is:** `bgTestRayIntersectionInRoom` (`src/game/bg.c:~3331`, called
from `bgTestBulletHitBackground` on every shot that resolves against
background geometry) was the D135 sibling: raw 8-byte-N64-`Gfx` byte/word
indexing into the 16-byte PC room DL → desync → OOB `vtxbase[idx]` / walk-off
AV the first time a wall or floor is shot. The `#ifdef PORT` port reads
`gdl->words.w0/.w1` (G_TRI1 from `(w1>>16/8/0)&0xff/10`; G_TRI4 nibbles from
`(u32)words.w0/.w1` with the byte-mapping table in the code comment); both
`texturenum` recoveries return `-1` (generic impact decal/sound, parked w/ D77).
**Procedure:**
1. Quiet machine. `./tools_pc/debug.ps1 -level_09`.
2. Get into a firefight and **shoot walls/floor deliberately** (not just
guards) for ~60 s — this is the only trigger.
3. Pass = no AV in `bgTestRayIntersectionInRoom` / `bgTestBulletHitBackground`,
bullets still resolve hits (decal/sound may be generic).
4. `-level_09` + `-level_20` framediff unregressed.
5. Then fix up the commit (`072b5c44` is labelled "WIP, unverified") and close
the §F D154 row.
**Risk:** low — `#ifdef PORT` only, N64 path verbatim under `#else`. If a new
fault appears in this function, the nibble-mapping table is the thing to
re-derive against `rsp/graphics/gmain.s` (ground truth for GBI decoding).
### A2. D139 — stage-unload `cleanupObjects` type byte (fix committed M-23)
**Status:** fix in (`src/game/cleanup_objects.c`, `#ifdef PORT`
`CLEANUP_PDTYPE(o)` = `((PropDefHeaderRecord*)o)->type` instead of `(u8)obj[0]`,
which on LE reads `extrascale` and walks off the propDef blob). **Never
verified** — the fast teardown test hit D140 first (since fixed), and M-27's
level-exit loop was playtested through the *real* front-end flow, which does
exercise `lvlUnloadStageTextData` → `cleanupObjects` on level exit.
**Procedure:**
1. Re-check whether M-27's Dam→Facility playthrough already exercised it:
if a real level exit completed without a teardown crash after the D139
commit, this is done — just record "verified via M-27 exit loop" on the
§F row and stop.
2. Otherwise: `debug.ps1 -level_09`, complete/abort a real level exit (not
pause-abort) and confirm no AV in `objFree` / `objFreeEmbedmentOrProjectile`.
**Risk:** low — one-line read-source change, every other propDef consumer
already uses the struct member.
---
## B. Root-caused small fixes (not yet applied)
### B1. D74 — dead + OOB texture-wrap block in fast3d (`port/fast3d/gfx_pc.cpp:~1570`)
**Status:** latent. Harmless for CLAMP glyphs today; could corrupt wrapped
textures on triangles (room panels, model skins).
**Root cause (confirmed against the tree):** two independent defects in the
D74 pre-wrap block inside the per-vertex UV loop:
1. **Always-false guard.** The codebase's `gbi.h` (`include/PR/gbi.h`, used by
both game and fast3d via `port/shim/PR/gbi.h`) encodes wrap mode as a
2-bit field: `G_TX_NOMIRROR|G_TX_WRAP = 0x0`, `G_TX_MIRROR = 0x1`,
`G_TX_CLAMP = 0x2`. So `cms & G_TX_WRAP` is `& 0` — **always false**. The
"is this tile in wrap mode" test must be **"clamp bit not set"**:
`(cms & G_TX_CLAMP) == 0`. (Compare the correct switch at
`gfx_opengl.cpp:706-712`, which keys on the four combinations.)
2. **OOB index.** `tex_width2[i]` / `tex_height2[i]` are **per-texunit `[2]`
arrays** (set up at `gfx_pc.cpp:~1471`), but the block is inside the
**per-vertex** loop where `i` is 0..2 → read of slot `[2]` if the guard
ever fires.
**Fix:**
1. Hoist the tile-window computation out of the vertex loop: for each used
texunit `t`, compute once — `tw = tex_width2[t]`, `th = tex_height2[t]`,
`wrapS = (cms & G_TX_CLAMP) == 0 && tw < tex_width[t]`, same for T.
2. In the vertex loop, apply `u = fmodf(u, tw); if (u<0) u += tw; u += uls/4`
(and V) only when the precomputed flag is set.
3. Delete nothing else — leave the surrounding D74 comment, extend it with a
line noting the `G_TX_WRAP == 0` encoding trap (PORT-LEARNINGS §D class:
"N64 hardware idioms fast3d does not emulate" — add the bit-encoding note).
**Verify:** build; `-level_09` + `-level_20` framediff; then a level with
visible wrapped triangle textures — **Depot (`-level_30`)** is the known
worst case (overlaps B2, but D74's block only fires for WRAP tiles). Check
`tools_pc/golden/` dumps for no new garbage.
**Risk:** low-medium. The block has never run, so this is *new* behavior —
if wrapped textures suddenly look wrong, the fmod window size (`tw` from
`lrs-uls+4`/4) is the thing to validate against `rsp/graphics/gmain.s`'s
tile-window handling before "fixing" it further.
### B2. D152 — find the leaking `osSetIntMask` site + proper narrow fix
**Status:** mitigated (self-healing lock in `port/src/libultra.c`, M-28) — a
leaked section now costs ~2 s + a log line instead of a permanent black
screen. The **root leak is still unidentified**.
**Procedure to root it:**
1. Replay the trigger: Facility (`-level_34`), kill Trevelyan → mission-fail
fade-out, under `tools_pc/debug.ps1`.
2. When the log shows `D152: ... stealing from owner=...`, take the logged
stale-owner address **and** the stealing caller's return address;
`addr2line -e build-pc/ge007.x86_64.exe -f -C <both>` (image base
`0x140000000`). The *owner* is the leak site.
3. Audit target: libaudio's `alEvtqPostEvent` / `alEvtqNextEvent` /
`sndSetSfxSlotVolume` paths (`src/libultra/audio/event.c`, `src/snd.c`) —
look for early returns between an `osSetIntMask(OS_IM_NONE)` and its
matching `OS_IM_ALL` (the D147 assumption that "the decomp never blocks /
never early-returns while masked" is falsified on this path). Also check
transient threads that acquire-and-exit (`gdb.txt` showed New Thread /
exited churn every few k frames).
**Proper narrow fix (once the site is known), pick one:**
- **(a) Preferred:** a dedicated `ALEventQueue` lock (mutex per queue or one
global in the port layer) taken by `alEvtqPostEvent`/`alEvtqNextEvent`, so
the audio event paths stop using the process-wide `osSetIntMask` section at
all; then the self-healing steal becomes dead weight that can be reverted to
the plain D147 recursive mutex.
- **(b) Cheaper:** make the leak site balanced (the actual bug), keep the
self-healing lock as belt-and-braces.
**Verify:** replay the mission-fail 3×: no `D152:` steal log lines, fade-out
completes, screen returns to the mission-failed UI. `-level_09` framediff.
**Risk:** (a) is port-layer only if the lock lives in the shimmed
`alEvtq*` wrappers — but note `event.c` is *compiled game-side libultra*, so
the lock likely has to go inside `event.c` under `#ifdef PORT` (ABI-exception
class, document as Dxx). Keep the self-healing lock either way until audio
(Phase 3) lands.
### B3. D120 — blood-stain hang: extend `d43_emit.py` for opcode-0x18 — **DONE (M-30)**
**Status:** FIXED (`tools_pc/d43_emit.py`, commit `da98cdf1` + validation
`4a7ab609`). The `CollisionVertices` sub-array + its `.index` /
`CollisionRelatedNode` / `CollisionRelatedIndex` fields were already converted
(`op24_is_collision` path). The real gap was narrower than this note assumed:
**`PointUsage[]` was reserved in the layout pass (`add_region(puo, 2*nv)`) but
the emit pass never wrote it → all-zero → `chr.c`'s decal walk
`index = PointUsage[index]` cycled 0→0** (capped by the guard). Fix:
`op24_pointusage[puo] = nv` + a byteswap loop in the emit pass. Converter now
validates: 1469 regions / 70177 `s16` entries round-trip, every index
< numVertices, none all-zero. The `chr.c` guard is kept as a safety net.
**Verify:** BUNKER1 firefight — blood decals on shot guards should appear, no
hang. Regen chain must be re-run for the sidecar to update.
**Procedure:**
1. Byte-spec the record: dump one converted guard model's opcode-0x18 region
from a sidecar (`data/pccg-*`) and diff against the raw ROM bytes; write
out the N64 layout (fields, sub-array strides, terminators) in the
converter comment — same method as the D69/D88 audits.
2. Extend `d43_emit.py`: emit the widened struct with the 6 pointers via
`put_ptr`, and convert both sub-arrays (endianness + PC stride).
3. Keep the `chr.c` guard until verification passes, then decide whether to
keep it as a safety net (recommended — same philosophy as D85's bounds
check) or revert.
4. Regen full chain; verify: BUNKER1 (`-level_09`) guard firefight — blood
decals now appear and no hang; framediff anchors.
**Risk:** medium — converter work with a runtime guard underneath, so a bad
conversion degrades to today's behavior rather than crashing. Good subagent
brief on its own (per HANDOFF-ARCHIVE M-9).
### B4. B3 — RMB aim too sensitive (`port/src/input.c`)
**Status:** user-reported (M-28 playtest). Port-only.
**Current math (aim mode, `input.c:~471`):**
`m = 61 + |dx| * (MouseAimSpeed/100) * AIM_GAIN(4.0)`, clamped to
`60 + AIM_BAND(20)` = 80. At the default `MouseAimSpeed=50`, **25 px/poll
saturates the band** — a fast flick is "max stick" for many polls, and GE's
aim curve `(stick-60)/10` then runs at full rate the whole time.
**Fix (in order of cheapness):**
1. Lower the default: `mouseAimSpeed = 50 → 25` (`input.c:~128`). One line;
users can still dial it back up in `ge007.ini`.
2. Make the band itself tunable: register `Input.AimBand` (int, 5..40,
default 20) and replace the `AIM_BAND` constant in the two clamp sites —
a smaller band = finer control near the gate, a larger one = more headroom.
3. Optional curve change if it still feels bad: make the mapping into the
band sub-linear (`m = 61 + AIM_BAND * sqrt(gx / saturating_px)`) so small
movements get proportionally less stick — but only after 1+2 are
playtested; don't stack changes.
**Verify:** playtest-only (no headless signal): aim at a guard in BUNKER1,
flick the mouse, confirm no overshoot orbiting the target; hipfire turn
(`MouseTurnSpeed`) is untouched by this.
**Risk:** trivial — port-only, config-backed, default-reversible.
### B5. B4 — menu screens need an absolute pointer mode (`port/src/input.c`)
**Status:** user-reported (M-28). Port-only design item; **no code yet**.
**Problem:** front-end / watch menus with the crosshair cursor behave like
an analog-stick game, not a PC pointer. Today all mouse input is *delta*
based (turn/look), and menu navigation goes through `joyGetStickX/Y` level
checks in `front.c` / `options.c` — synthetic stick deltas make menu cursors
crawl or slam.
**Design (port-only, no `src/` change):**
1. Add a **menu-pointer mode** to the input layer: when active, emit
*absolute* stick values derived from the cursor position —
`sx = (mouseX / windowW) * 2 - 1 → [-80..80]`, same for Y. GE's front-end
maps stick deflection to on-screen cursor position, so absolute→absolute
gives true pointer behavior with zero game-code changes.
2. **Mode detection is the open question** — the port doesn't know when the
game is in a menu. Options, cheapest first:
- **(a)** Heuristic: mouse is *ungrabbed* (cursor visible) ⇒ menu mode.
Pair with a rule that front-end screens release the grab and gameplay
grabs it (the grab state already flips on focus loss; extend it to be
driven by a key, e.g. hold-to-release, or auto-detect via the same
signals D145 used for ESC-as-B). Simple, no game-state peeking.
- **(b)** Peek a game-side "am I in the front end" flag through an existing
exported symbol (check `front.c` / `lv.c` globals) and branch on it in
`inputPoll`. Read-only access to a global is arguably inside the
documented exception class, but **record a Dxx** if used.
- **(c)** Explicit user toggle (config key + hotkey). Ugly but zero risk.
3. While in menu mode: suppress aim/turn delta emission entirely; wheel and
clicks map to A/B as today.
**Verify:** drive the mission-select / SELECT FILE screens with the mouse —
cursor should track 1:1, click selects; in-game aim unaffected.
**Risk:** low (port-only) under option (a)/(c); (b) needs a Dxx write-up.
Sequence *after* B4 so the gameplay feel is settled first.
### B6. D118d latent sibling — `front.c` menu-nav stick level checks
**Status:** latent, noted M-27. Same bug class as the fixed watch-menu
over-scroll (`options.c`, `GE_WATCH_STICK_FAST{UP,DOWN}` → 0 under PORT).
**What to do:** find the `joyGetStick*InRange` / raw `joyGetStickY < -0x46`
style *level* checks in `front.c` menu navigation (main menu, mode-select,
mission-select paging) and apply the same treatment: under `#ifdef PORT`
drop the stick fast-scroll term so keyboard/digital input goes through the
latched single-step path only. Keep N64 verbatim under `#else`.
**Verify:** keyboard W/S (and D-pad) paging on every front-end menu = one
step per press, no multi-skip; controller sticks still fast-scroll.
**Risk:** low — mechanical copy of an already-landed fix. Do it together with
B5 so the menu-input area is touched once.
### B7. D118a residual — hipfire pitch is digital, yaw is analog
**Status:** parked minor feel issue. The *full* fix needs a game-side hook;
the deferred design is:
An `#ifdef PORT` analog-aim hook in `bondview.c`/`bondview2.c` where GE reads
digital C-up/C-down for hipfire pitch — replace the digital read with the
analog stick-Y value (which our input layer can emit even in hipfire, since
stick-Y is "move" only *when the game consumes it as such*; validate that
emitting a small stick-Y does not also make Bond walk). This is the first
*behavioral* `src/` edit of the aim path — it exceeds pure ABI/layout; **write
it up with a confidence rating before implementing** (AGENTS.md #2: "stop and
write it up"), and gate it behind a config key defaulting to *current*
behavior.
**Cheaper alternative worth trying first:** raise `MOUSE_PITCH_THRESH`
granularity by emitting the hipfire pitch as *repeated C-button pulses with
dwell proportional to speed* — pure port-layer, approximates analog. Try this
before touching `src/`.
---
## C. Cosmetic / graphics — investigation plans (root cause not fully known)
These are parked below crash/level work (`GRAPHICS-BACKLOG.md`). Each entry is
the *next diagnostic step*, because the root cause is not yet pinned. Do not
treat them as ready-to-apply fixes.
### C1. D114/D116 — HUD text + ammo digits X-mirrored per glyph
**Hard rule (PORT-LEARNINGS §D2): do NOT re-run the static trace** (four
sessions did; every stage verified non-mirrored at the GL boundary) and do NOT
apply a global texrect S-swap (mirrors the whole screen).
**Next attempt — pick one, in order:**
1. **RenderDoc (or apitrace) capture of exactly one glyph's texrect draw.**
The contradiction is "VBO verified non-mirrored but pixels mirrored" — a
GPU-side capture resolves it in one shot: inspect the bound texture's
upload bytes vs the `gDPLoadTextureBlock` source, and the final quad's UVs.
2. **Asymmetric-1-texel experiment:** temporarily force one HUD glyph's image
to a 2×1 or 3×1 *asymmetric* pattern (e.g. left half white) in the port
layer; if it renders right-half-white, the flip is between upload and
sample (texture origin / `glPixelStorei(GL_UNPACK_ROW_LENGTH)` / Y-flip);
if it renders correctly, the mirror is applied *per-glyph placement*
(texrect S-range inversion in `textRenderGlyph`'s GBI — check the
`gDPTile`/`gDPSetTile` uls/lrs signs for the HUD tiles specifically).
Suspects to check with the capture in hand: 4-bit CI tile upload stride vs
`G_IM_SIZ_4b` sampling, TLUT index endianness (wrong-but-symmetric TLUT would
not mirror — deprioritize), and the `uls/lrs` sign convention for *rect*
draws (D116 is a rect path; triangles were never suspected).
### C2. D75 — front-end 3D models misplaced / absent (logo, gun-barrel Bond, cast)
Triage per §F D75 — **distinguish (a) vs (b) before any fix:**
1. For the Nintendo logo and one cast model, determine which matrix path
builds their transforms: `guRotate`/`guLookAt` (→ candidate (a), D73
scope gap in `guint.h` `DVAL()`) or the `animInit` + embedded raw-offsets
into `struct player` path (→ candidate (b), independent model/RW-pool bug,
cf. D56/D100/D102 — all now-fixed struct-pun classes; a *new* instance of
the class is the leading hypothesis).
2. Capture `GE_PCDUMP` frames across the logo transition and gun-barrel/cast
segments to localize which frame first diverges.
3. Files: `src/libultra/gu/guint.h`, `src/game/model.c`, `bondview2.c`
(animInit / modelSetScale), `port/fast3d/gfx_pc.cpp`.
**Note:** D148/D149 below are the same family — fixing D75's root cause will
likely take them with it. The D149 garbled geometry is also worth one check on
the *converter* side: the malformed sub-DL (`seg5+0x9ee4`, unresolved matrix
ptr + garbage opcodes) may be a `d43_emit.py` conversion defect rather than a
runtime transform bug — dump that sub-DL's raw ROM bytes vs the sidecar before
assuming it's runtime.
### C3. D76 — disclaimer screen only partially drawn
Likely the D68 image-table class, incomplete: some `sImageTableEntry`s the
disclaimer references are missed by `port/src/gimgfixup.c`'s bswap/shadow-sync.
**Procedure:**
1. Enumerate the image entries the disclaimer screen references (trace
`textRender`/`display_image_at_position` calls in the legal-screen code, or
log every `gDPLoadTextureBlock` source during PPM frames f20–f100).
2. For each, confirm it is covered by the D68 fixup (bswapped IMAGESEG w1 +
synced compiled globalDL shadow). The "only the classification line + one
line below draw" symptom = exactly the covered subset rendering.
3. Files: `port/src/gimgfixup.c`, `src/game/tex.c`, image-table consumers.
### C4. D148 / D149 — Dam exit cutscene absent; MISSION COMPLETE models garbled
Both are D75 family (see C2). Additional notes:
- **D148:** the missing piece is a *scripted* sequence — cinematic camera +
Bond rappel anim. Once D75's model path is fixed, verify the cutscene
trigger still fires (check whether the exit trigger even dispatches the
cinematic on PC — it may be gated on a front-end state flag our flow skips;
`GE_STARTMENU` tooling can crash-test the screen in isolation).
- **D149:** see the converter-side check in C2. D144/D146 already keep the
game alive on the corrupt DL; the fix should make the model *correct*, then
D146's "unknown opcode → end DL" guard can stay as a safety net.
### C5. D85 (residual) — room DL `texLoadFromGdl` decode
**Status:** OPEN but safety-netted; geometry renders, so this is below level
work. Remaining: decode the room-specific opcodes / CC-RM-LUT markers that
`texLoadFromGdl` still handles generically, and verify `csize_*_DL_binary`
sizing. Only worth touching if a level shows placeholder/garbage textures on
room geometry that can't be explained by B2 (Depot wrong-colors).
---
## D. Housekeeping / cleanup (no gameplay effect)
### D1. Stale §F index row — D132 says "proposed fix (not applied)"
The D132 fix **is applied** in the tree: `tools_pc/d88_propdefs.py`
(`PROPDEF_PC_BYTES[14/19]=32, [44]=40`; the `(14,19,38,44)` handler emits each
`Index{k}` into the low 4 B of the 8-aligned slot at PC `8+8*k`) and
`src/game/loadobjectmodel.c sizepropdef()` (LINK/SWITCH/LOCK_DOOR → 8 words,
SAFE_ITEM → 10). The §F index row still reads "proposed fix (not applied) —
see below" while the subsection says "APPLIED (M-20, commit pending)".
**Action:** update the index row to `resolved (M-20)` and confirm the M-20
commit landed (git-blame both files). Docs-only.
### D2. Stale BACKLOG technical-cleanup items
`docs/BACKLOG.md` "Technical cleanup" lists:
- ~~"Add a D24-implications bug class to PORT-LEARNINGS"~~ — **done M-19**
(PORT-LEARNINGS §E, lines ~374).
- ~~"Watch item: `osYieldThread` = `Sleep(0)`"~~ — **done M-19** (§E, line
~407). Keep the watch item itself; it's still accurate (the one place we
fake cooperative scheduling — first suspect for level-sweep hot-loop
misbehavior under host load).
- "Remove TEMP D63 debug scaffolding" — **partially done M-19** (`libultra.c`
+ game-side probe blocks removed). Remaining: `port/fast3d/gfx_pc.cpp`
trail code — the `/* TEMP D63 (strip before commit) */` block at ~2462, the
`GE_D63`-gated dram-branch trace at ~2516-2531, and the unknown-opcode dump
at ~2759-2765. All env-gated / inert; strip in one pass (keep the
`cached = getenv("GE_D63")` pattern only if a probe is retained — recommend
removing all of it).
**Action:** one docs+code cleanup commit: mark the two done items, strip the
gfx_pc.cpp D63 trail, rebuild + `-level_09` framediff.
### D3. `osYieldThread = Sleep(0)` watch item (no action — recorded)
When a flaky timing bug appears (especially Phase 3 audio underruns): reach
first for host thread priorities (`SetThreadPriority`: scheduler thread
time-critical, tick normal) and the deferred `GE_DETERM` mode (D117) — not
for kernel changes. See PORT-LEARNINGS §E.
---
## Status (updated M-30)
- **B3 / D120** — DONE (PointUsage emit + validation).
- **B4** — DONE (M-29 `0bd0ceec`).
- **B5** — DONE (M-30 velocity mode; **M-31 D165** replaced it with a true
~1:1 P-controller pointer — `Input.MenuPointerMode` 1=pointer default,
0=old velocity). Feel-check owed.
- **B7 / D118a** — DONE (M-31 **D166**, cheaper port-only alternative):
hipfire pitch is now speed-proportional C-button pulses, not a digital
threshold. `Input.HipfirePitchSpeed` tunes it. The full game-side analog
hook remains unimplemented (not needed unless the pulse still feels off).
- **B6** — investigated,
the `front.c` stick checks are already edge-gated / toggle-guarded, not a
real over-scroll (unlike the fixed options.c watch case). No-op.
- **RC4** (palette off-by-one, was in `C` / TEXTURE-GLITCH-ANALYSIS) —
RETRACTED: the current RGBA5551 decode is correct.
- **A2 / D139** — likely closeable: M-27's Dam→Facility playthrough ran a
real `lvlUnloadStageTextData` → `cleanupObjects` with no teardown crash
after the fix. Confirm the commit landed, then close the §F row.
- **D2 (D63 scaffolding strip)** — the `port/fast3d/gfx_pc.cpp` trail is
stripped (M-30 `d0789358`). The remaining `#if defined(PORT)` `GE_D63`
probe blocks in ~10 `src/game/*.c` files are inert but **some sibling
`getenv`/`static` lines nearby are NOT `#if defined(PORT)`-guarded** (e.g.
`bg.c:2880` `d63bgprimarycount`) — a strip pass must verify N64-build
safety first (Non-Negotiable #1). Low value; defer.
## Suggested order for what's left
1. **A1** (D154 wall-shoot GBI parser) — needs a firefight-into-a-wall
playtest, then close/commit.
2. **B1** (D74 wrap-block) — the in-place fix boot-crashes; needs the
hoist-out-of-vertex-loop rework + per-level visual check. Cosmetic, gate
behind a config flag like RC2. Risky, low priority.
3. **B2** (Depot ceiling blue-speckle) — RC2 didn't touch it; needs `GE_DTEX`
/ RenderDoc on that specific surface. RC1 (wallet-Bond) is the D75
front-end-model track.
4. **C1–C4** cosmetics — after in-level crashes / campaign playtest.