forked from Ports/goldeneye-pc-port
port: D178 — byte-swap the raw briefing segment (blank objectives / briefing text)
Family B of the M-36 triage. The Ubrief*Z segment (assets/obseg/brief/) is
a raw 48-byte ROM image of struct BriefStruct { u16 brief[4]; struct { u16
textid; u16 enabled_difficulty; } objective[10]; }, loaded by front.c
load_briefing_text_for_stage() via _fileNameLoadToAddr() with no converter
and no BE->LE fixup. On the LE host all 24 u16 read byte-swapped:
- textid 0x2C04 (getStringID(LDAM,4)) -> 0x042C -> langGet() resolves an
unloaded bank -> NULL -> blank objective/briefing text. This is the whole
of the D143 "briefing text blank" residual (the bank loaded fine; the ids
into it were swapped).
- enabled_difficulty 0x0001 -> 0x0100 = 256, so `selected_difficulty >=
enabled_difficulty` is always false -> difficulty-gated objectives vanish
(on Agent only the one AGENT-difficulty line survived -> lone "a." bullet).
Fix: romdataFixupBriefing() in port/src/romdata.c, called from a #ifdef PORT
block in load_briefing_text_for_stage() right after the load — same shape as
langFixupLoadedBank(). BE-serialized-struct reconciliation (AGENTS.md #2),
semantics-preserving, N64 build untouched. GE_D178=1 dumps raw + fixed words.
Verified: Dam briefing renders all four objectives at 00 Agent with correct
difficulty gating and "007: James Bond" header; -level_09 framediff 3/3;
GE_STARTMENU=7/=13 crash-free; build links clean 240/240.
Investigation + patch: subagent (worktree). Suspect 2 (Gfx-width temp_s0
arithmetic) was ruled out — benign 512-byte gap, langLoadToBank records the
bank base itself. Follow-ups noted in findings.md: annotate D143 as
superseded; latent 64->32 pointer truncation at front.c:6569 / :2202.
Claude-Session: https://claude.ai/code/session_01GULf6knbsqfsnW39tMr9WT
This commit is contained in:
1 parent
9a69d57afd
commit
cd1ed57449
5 files changed
+80
-1
No files matched your search
@@ -390,7 +390,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| RC3 · D167 | **Non-power-of-two texture wrap period (`docs/dev/TEXTURE-GLITCH-ANALYSIS.md` §6 RC3 — "textures repeat oddly", residual Depot-ceiling noise after D161).** The N64 RDP masks the texel coordinate of a wrapping render tile at `1<<mask`, and GE sets `mask = texDimensionToMask(dim) = ceil(log2(dim))` (`src/game/tex.c:361`), so a non-PoT tile (Depot's 65×65 / 96×48 / 56×56 room surfaces) repeats at the **next power of two**, not at its image size the way GL `GL_REPEAT` does → the pattern is squashed/stretched and the seam lands in the wrong place. fast3d never stored `masks`/`maskt` at all (`gfx_dp_set_tile` dropped them) and wrapped purely at the uploaded image dimension. **Fix (`port/fast3d/gfx_pc.cpp`, behind the existing `Video.WrapFix` knob, default OFF):** store `masks`/`maskt` on the tile; in the hoisted per-texunit pre-wrap block in `gfx_sp_tri1` (D74 block — already lifted out of the vertex loop, indexed by texunit `t` not vertex `i`), when the tile is WRAP (no CLAMP/MIRROR bit) and `1<<mask != tex_width`, fold the UV at the N64 period `1<<mask` and clamp the `[dim, 1<<mask)` overflow band (which is a TMEM smear on console, no real texels) to the last texel so it reads as an edge streak instead of a bogus early image restart. `GE_WRAPFIX=0/1` env override added (env wins over the ini, matching `Debug.FrameDump`). **Per-level captures (`-level_09`/`-30`/`-34`/`-20`, WrapFix OFF vs ON, `GE_PCDUMP` 6-frame windows):** no crashes, 6/6 frames each; on settled/comparable frames Silo is ~pixel-identical (phash 0–11), Facility 180–260 pixel-identical, Depot shows small localized texel changes on ceiling/wall cells (dmean 5–9, no structural break); the large per-run deltas are all the D117 intro-camera-pan nondeterminism, not the fix. Default kept **OFF** — no regression, but a headless structural diff can't confirm the Depot ceiling actually looks *better*; needs a human eyeball with `Video.WrapFix=1`. Default-off is byte-identical to golden (all new behaviour is inside `if (g_wrap_fix)`). D74's dead in-vertex-loop wrap block was already reworked/hoisted at M-30; this only adds the mask-period trigger. Confidence: **medium** (mechanism correct; overflow-band handling is an approximation, not exact TMEM-smear emulation; visual win unconfirmed). porting-notes.md §D. | KNOB ADDED, default OFF (`port/fast3d/gfx_pc.cpp`). Needs user visual check on Depot. |
|
||||
| D168 | **`GE_PCDUMP` / F12 PPM captures were vertically flipped — the entire source of the bogus D114/D116 "HUD/text X-mirror" (M-33, developer-confirmed).** `gfx_opengl_dump_bound_fbo` (`port/fast3d/gfx_opengl.cpp`) wrote `glReadPixels` output straight to a P6 PPM. GL framebuffer origin is bottom-left; PPM P6 is top-row-first — so every capture (and the `tools_pc/golden/` set, and every screenshot pasted into the finding log since M-6) was upside-down. On real hardware / an actual screen the game renders correctly (developer confirmed). The successive D114→D116 "shared fast3d mirror" investigations — each of which found *every probed stage clean yet the output "mirrored"* — were reading an inverted capture and pattern-matching upside-down asymmetric content (text, guards, the Nintendo logo) as "mirrored". Fix: emit PPM rows bottom-to-top. `tools_pc/golden/*.png` flipped in place to match (see `tools_pc/golden/README.md`); regenerate from a real run when convenient. **Not runtime-verified this session** (no ROM / toolchain in the migrated tree — see the migration note) — needs a fresh capture to confirm text reads normally. Confidence: **high** (mechanism is unambiguous; developer has hardware confirmation the screen is correct). porting-notes.md §D2. | FIXED (`port/fast3d/gfx_opengl.cpp`); D114 / D116 reclassified as capture-orientation artifacts (below). |
|
||||
| D169 | **Front-end mouse pointer can't reach the outer cells of the mission/level-select grid — only an inner ~3×3 selectable (M-33, developer bug report).** The D165 pointer P-controller in `port/src/input.c` is hard-coded to a **320×240** virtual field (`MENU_CURSOR_HI_H`/`_HI_V` = 300/220, `MENU_CURSOR_MID_H`/`_MID_V` = 160/120, `input.c:145-149`), but GE's front end runs at **440×330** (`front.c:8570` `viSetViewSize(440,330)`; default cursor home 220/165 `front.c:285`). `frontUpdateControlStickPosition` clamps the real cursor to `[20,420]×[20,310]` (`front.c:1195-1217`); the mission-select hit-test grid spans x 73…352 / y 62…270 with outer split points 317 / 235.5 (`front.c:516,519,3180,3194`). The port clamps its pointer *target* and *estimate* to 300/220 (`input.c:553-564`) → the stick zeroes out once both saturate → the game's `cursor_h_pos`/`cursor_v_pos` park at ≈300/220, short of the two outer columns and the bottom row. File-select / mode-select / main-menu are unaffected because their hit targets (centred folder boxes, the `x=126` mode list, the `x=106` difficulty list) all sit inside the 320×240 sub-box. Not the D118d `joyGetStickY`-threshold class — this is a virtual-resolution constant mismatch, closer to D164 (a front-end layout constant wrong on the PC path). Also note: the "re-syncs whenever the target is held at a screen edge" comment at `input.c:141-143` describes behaviour the code doesn't implement — the only estimator reset is the activation re-home (`input.c:545-549`), so once pinned at the clamp the estimate never recovers. Confidence: **medium-high** (constants + clamp math unambiguous in source; static-only, no build/run this session; exact reachable block "~4×3" vs the reported "3×3" within tolerance). | **FIXED (M-33, `port/src/input.c`, port-only, no `#ifdef PORT`).** The `menuMode` pointer branch now derives its clamp bounds from the live virtual screen — `[screenleft+20, screenleft+screenwidth-20] × [screentop+20, screentop+screenheight-20]` via `getPlayer_c_screenwidth/height/left/top()` (`src/game/bondview.c:880-895`) — falling back to the old 320×240 constants when the front-end screen isn't set (`sw` outside 200…2000). The estimator seed on activation is now the real `cursor_h_pos`/`cursor_v_pos` (`front.c:285`, externed in `front.h`) instead of the 160/120 centre guess. `MENU_CURSOR_HI_H/_V` / `MID_H/_V` kept only as the fallback. **Verified:** builds + links clean (`getPlayer_c_screen*` and `cursor_[hv]_pos` all resolve — non-static engine symbols, as expected); `GE_STARTMENU=7` mission-select boots crash-free 600+ frames; `-level_09` unregressed (framediff 3/3 within threshold, 91.6% nonclear). The in-level path is untouched (menu-only branch). **Interactive feel-check still owed** — headless input can't drive the mouse pointer, so "every grid tile is now reachable" is inferred from the corrected clamp math, not observed. `Input.MenuPointerMode=0` (legacy velocity) and the constant fallback remain as escape hatches. |
|
||||
| D178 | **Objectives not listed correctly on the pre-mission briefing screen** (user QA report) — the mission overview shown before starting a level. Symptom unspecified (wrong objectives / wrong order / blank lines). Distinct surface from the in-level watch: D151's type-35/23 `text`-slot fix and its pending interactive re-verify don't necessarily cover it; D143 (`langGet` NULL → silent blank) and D157 (difficulty byte offset → wrong objectives shown for a difficulty) are the leading relatives. See `GRAPHICS-BACKLOG.md` D178 for next steps (compare vs `docs/dev/LEVEL-OBJECTIVES.md`). | Observed, not investigated |
|
||||
| D178 | **Pre-mission briefing screen: objectives blank / missing, briefing pages blank (also the D143 "briefing text blank" side effect) — FIXED (M-36).** Root cause: the briefing segment (`Ubrief*Z`, source `assets/obseg/brief/*.c`) is a raw ROM image of `struct BriefStruct` = `{ u16 brief[4]; struct { u16 textid; u16 enabled_difficulty; } objective[10]; }` (48 bytes), loaded by `front.c load_briefing_text_for_stage()` via `_fileNameLoadToAddr()` — **no converter and no BE→LE fixup anywhere in the chain**. On the LE host all 24 `u16` read byte-swapped. Measured on Dam (`GE_D178=1`): raw `brief=002c,012c,022c,032c obj0=042c/0100 obj3=072c/0000` vs correct `2c00,2c01,2c02,2c03 / 2c04/0001 / 2c07/0000`. Two independent symptoms follow: (a) `textid` `0x2C04` (= `getStringID(LDAM,4)`, bank 11 slot 4) reads as `0x042C` → bank 1 slot 44, never loaded → `langGet()` NULL → **blank text** (this is exactly the D143 residual, `front.c:6732` and `brief[0..3]` at `front.c:6855-6867`); (b) `enabled_difficulty` `0x0001` (Secret Agent) reads as `0x0100 = 256`, so `selected_difficulty >= enabled_difficulty` (`front.c:6729`) is false for every difficulty-gated objective — on Agent only the one `DIFFICULTY_AGENT`(0) objective survived the filter, which is why a single bare "a." bullet printed. **Fix:** `romdataFixupBriefing()` in `port/src/romdata.c` (+ `port/include/romdata.h`), called from a `#ifdef PORT` block in `load_briefing_text_for_stage()` right after the load — the same shape as `langFixupLoadedBank()` in `language.c` (BE-serialized-struct reconciliation, semantics-preserving, no game logic touched). `GE_D178=1` prints the raw and fixed words. **Verified:** Dam briefing on Agent now shows "a. Bungee jump from platform"; on `GE_STARTMENU_DIFF=3` (00 Agent) all four (Neutralize all alarms / Install covert modem / Intercept data backup / Bungee jump from platform), correct difficulty gating. `-level_09` framediff 3/3; `GE_STARTMENU=7`/`=13` crash-free. | **FIXED (M-36)** — high confidence |
|
||||
| D175 | **In-game stutter / brief hang during normal play** (user QA report): opening a door on Runway (`-level_35`, mission 3) and also observed on Surface. Self-recovered; no backtrace captured. Likely one of the known transient-hang classes (D155 catch-up spiral / D156 anim NaN loop / D134 task-done event / D147-D152 audio-lock steal) or a benign new-room texture-import frame spike on door open. See `GRAPHICS-BACKLOG.md` D175 for the gdb pattern-match sheet. | Observed, not investigated |
|
||||
| D176 | **Skybox broken on Surface** (user QA report; which of `-level_36`/`-level_43` unconfirmed). Symptom unspecified — missing/black sky, bad transform (D75 family), or sky-tile texture import. See `GRAPHICS-BACKLOG.md` D176. | Observed, not investigated |
|
||||
| D177 | **Ladders don't work** (user QA report): climbing is non-functional and **blocks progression on some levels**. FUNCTIONAL bug, not cosmetic — ranks above the graphics backlog. Untested by every harness so far (level sweep is boot-only; no scripted ladder input). Suspects: player ladder state machine in `bondview2.c`/chr physics (struct-offset family D100/D140), or the climb input mapping (A-button hold?) not reaching the game on the PC input path. **Next:** repro on a level with a mandatory ladder, check whether the player's ladder state ever changes (probe) vs the input never registering (`GE_INPUTLOG`). | Observed, NOT investigated — high priority (progression blocker) |
|
||||
|
||||
@@ -325,6 +325,25 @@ through a converter or a runtime bswap fixup reads scrambled.
|
||||
emit the value (`_bswap32` into the low 4 bytes, LE). Decide per field,
|
||||
not per type.
|
||||
|
||||
- **Audit for ROM segments loaded raw with no fixup at all** (D178). Every
|
||||
`_fileNameLoadToAddr()` / `_fileNameLoadToBank()` call site loads a raw
|
||||
big-endian ROM image; the ones with a `struct` overlay need a BE→LE pass
|
||||
and there is nothing in the load path that supplies one — the fixups are
|
||||
bolted on per call site (`langFixupLoadedBank()` in `language.c`,
|
||||
`romdataFixup*()` in `port/src/romdata.c`). The briefing segment
|
||||
(`Ubrief*Z` = `u16 brief[4]` + 10 × `{u16 textid; u16 difficulty}`) had
|
||||
none for a year, and its failure mode is silent and *split*: a swapped
|
||||
string id (`0x2C04` → `0x042C`) still passes `!= 0`, so the loop body
|
||||
runs and `langGet()` quietly returns NULL → **blank text, not a crash**
|
||||
(that was the whole of the D143 "briefing/objective text is blank"
|
||||
residual); and a swapped small enum (`0x0001` → `0x0100 = 256`) turns a
|
||||
`>=` difficulty gate into an always-false filter, so lines silently
|
||||
*vanish* rather than render wrong. When a screen renders its own
|
||||
chrome/headers correctly but the file-driven rows are empty, suspect the
|
||||
raw file, not the renderer: dump the first words of the loaded blob and
|
||||
look for a byte-mirrored constant you can recognise from the asset `.c`
|
||||
source in `assets/obseg/`.
|
||||
|
||||
## C2. Port-layer / SDL shims
|
||||
|
||||
- `#include <PR/os.h>` in a port `.c`/`.h` that also sees `<errno.h>`
|
||||
|
||||
@@ -75,6 +75,16 @@ void romdataFixupCseq(u8 *blob);
|
||||
*/
|
||||
void romdataFixupLangBank(u8 *blob, u32 blobSize);
|
||||
|
||||
/*
|
||||
* D178 (docs/dev/findings.md): byte-swap a briefing segment (Ubrief*Z) in
|
||||
* place. `struct BriefStruct` is 24 big-endian u16 (u16 brief[4] +
|
||||
* 10 x { u16 textid; u16 enabled_difficulty }) loaded raw from ROM by
|
||||
* front.c load_briefing_text_for_stage(); without this every string id and
|
||||
* difficulty gate is byte-swapped -> blank briefing / objective text.
|
||||
*/
|
||||
#define ROMDATA_BRIEFING_U16S 24
|
||||
void romdataFixupBriefing(u8 *blob);
|
||||
|
||||
/*
|
||||
* D50 (docs/internals.md): re-lay out a font segment (struct font:
|
||||
* kerning[169] + chars[94] + glyph pixel data) from the N64 ROM layout to
|
||||
|
||||
@@ -437,6 +437,28 @@ void romdataFixupLangBank(u8 *blob, u32 blobSize)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* D178 (docs/dev/findings.md): a briefing segment (Ubrief*Z, assets/obseg/
|
||||
* brief/) serializes `struct BriefStruct` = { u16 brief[4];
|
||||
* { u16 textid; u16 enabled_difficulty; } objective[10] } — 24 big-endian
|
||||
* u16 in ROM, loaded raw by load_briefing_text_for_stage() with no
|
||||
* converter. On a little-endian host every field is byte-swapped:
|
||||
* textid = getStringID(LDAM, 4) = 0x2C04 reads as 0x042C -> langGet()
|
||||
* resolves bank 1 slot 44 (never loaded) -> NULL -> blank objective text,
|
||||
* and enabled_difficulty 0x0001 (Secret Agent) reads as 0x0100 = 256 so
|
||||
* `selected_difficulty >= enabled_difficulty` filters the line out entirely.
|
||||
* Same for brief[0..3] (the Overview / M / Q / Moneypenny pages, D143).
|
||||
* Fixed size (48 bytes), so a straight u16 swap of the whole struct.
|
||||
*/
|
||||
void romdataFixupBriefing(u8 *blob)
|
||||
{
|
||||
u32 i;
|
||||
for (i = 0; i < ROMDATA_BRIEFING_U16S; i++) {
|
||||
u16 v = *(const u16 *)(blob + 2 * i);
|
||||
*(u16 *)(blob + 2 * i) = (u16)((v >> 8) | (v << 8));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* D50 (docs/internals.md): font segments (Zurich Bold, Bank Gothic)
|
||||
* serialize `struct font` in the N64 layout — big-endian scalars, 4-byte
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
#include "assets/obseg/text/LtitleE.h"
|
||||
#include "matrixmath.h"
|
||||
#include "bg.h"
|
||||
#ifdef PORT
|
||||
#include <stdio.h>
|
||||
#include "romdata.h" /* D178: briefing-segment byte-order fixup */
|
||||
#endif
|
||||
#include "chrai.h"
|
||||
#include "title.h"
|
||||
#include <assets/font_dl.h>
|
||||
@@ -6572,6 +6576,30 @@ void load_briefing_text_for_stage(void)
|
||||
argg = 0x200;
|
||||
ptrbriefingdata = _fileNameLoadToAddr(mission_folder_setup_entries[briefingpage].briefing_name_ptr, FILELOADMETHOD_DEFAULT, (u8 *) temp_s0, argg);
|
||||
|
||||
#ifdef PORT
|
||||
/* D178: the Ubrief*Z segment is a raw ROM image of `struct BriefStruct`
|
||||
* (24 big-endian u16, no converter). On a little-endian host every
|
||||
* string id and difficulty gate reads byte-swapped -> langGet() misses
|
||||
* and the objective/briefing text renders blank (D143). Decode in
|
||||
* place, mirroring langFixupLoadedBank() in language.c. */
|
||||
if (ptrbriefingdata) {
|
||||
if (getenv("GE_D178")) {
|
||||
const u16 *w = (const u16 *)ptrbriefingdata;
|
||||
fprintf(stderr, "D178 %s raw brief=%04x,%04x,%04x,%04x obj0=%04x/%04x obj3=%04x/%04x seldiff=%d\n",
|
||||
(const char *)mission_folder_setup_entries[briefingpage].briefing_name_ptr,
|
||||
w[0], w[1], w[2], w[3], w[4], w[5], w[10], w[11],
|
||||
(int)selected_difficulty);
|
||||
}
|
||||
romdataFixupBriefing((u8 *)ptrbriefingdata);
|
||||
if (getenv("GE_D178")) {
|
||||
const u16 *w = (const u16 *)ptrbriefingdata;
|
||||
fprintf(stderr, "D178 %s fix brief=%04x,%04x,%04x,%04x obj0=%04x/%04x obj3=%04x/%04x\n",
|
||||
(const char *)mission_folder_setup_entries[briefingpage].briefing_name_ptr,
|
||||
w[0], w[1], w[2], w[3], w[4], w[5], w[10], w[11]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// what is this
|
||||
temp_s0 += argg / 8;
|
||||
|
||||
|
||||
Reference in new issue
Block a user