diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 2e8c60aa..58a9f83b 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -84,14 +84,22 @@ storage-room void is NOT closed doors failing to render. 4. Add an f32 value spot-check to `d43_emit.py`'s verify pass (it only checks pointers/opcodes today — the D112 bug passed "ALL CHECKS PASSED"). -**Session M-7 — see §F D116.** Rebuilt at D115, captured `-level_09` -frames. The HUD/menu text mirror (D75) is a **per-glyph texture-space -flip** (string order preserved, each glyph mirrored), NOT a screen or -matrix mirror — so D114's unified "shared fast3d screen mirror" theory is -at least partly wrong. Ammo digits (different font path) render correct. -Needs a runtime glyph-texrect probe (tile size / line_size_bytes / -uploaded width vs `curchar->width` / final S texcoords); or check the -compiled-in font blob for a 32-bit word-swap. Artifacts in `ppm/`. +**Session M-7 — see §F D116 + "D116 probe results".** Ran the glyph +probe (overseer, after killing a subagent that was drifting toward a +global texrect S-swap). Corrected finding: the HUD text mirror is a +**per-quad texture-U flip that is NOT path-specific** — the ammo digits +"83" are mirrored too (`ppm/frame_000320.ppm`), refuting M-7's earlier +"proportional-font-specific" claim. Rect *positions* are correct; only +texture content is X-flipped. Every fast3d stage probed clean (glyph +bitmap in memory correct, texrect `ul.u=0→lr.u=max`, `import_texture_i8` +linear, GL shader UV pass-through) — so the flip is in the GL +vertex-buffer/draw layer OR a shared clip-space-X vs U desync on rect +quads. **This re-opens D114's shared-mirror hypothesis** (now per-quad +U/X, not screen-space) and plausibly also explains inverted guards / +mislocated door props. NEXT: shader/vertex-buffer probe — dump per-vertex +(x,u) for one glyph quad; render a 1-texel asymmetric test texture to see +which axis inverts. `GE_D116`-gated probes left in `textrelated.c` + +`gfx_pc.cpp` (zero-cost). **Session M-6 — see §F D113/D114/D115, `docs/PLAN-M6-playable.md`, `docs/AUDIT-M6-player-offsets.md`, `docs/AGENT-WORKFLOW.md`, diff --git a/docs/PCPortResearch.md b/docs/PCPortResearch.md index c77e675e..6abcdeff 100644 --- a/docs/PCPortResearch.md +++ b/docs/PCPortResearch.md @@ -3558,6 +3558,61 @@ Rebuilt at D115 (`14b6b432`), build green, captured `-level_09` frames chunked at 4px). Probe artifacts: `ppm/frame_000300.ppm` (obj text), `ppm/frame_000480.ppm` (ammo). +**D116 probe results (session M-7, overseer-run — supersedes the "specific +to the proportional font" claim above).** Killed a subagent that was +drifting toward a global `GE_D116FLIP` S-swap on every non-flip texrect +(mirrors the whole HUD/3D to "fix" glyphs — wrong). Kept its +`GE_D116`-gated `fprintf` probes in `textrelated.c` (both glyph paths) and +`gfx_pc.cpp:gfx_dp_texture_rectangle`. Built green at D115, ran +`-level_09` + `GE_PCDUMP="200-360:40"`. Data: +- **The ammo digits ARE mirrored too.** `ppm/frame_000320.ppm` top-right: + "83" renders with each digit individually X-flipped, digit order + preserved; the clip-count glyph left of the mag icon likewise. So the + prior session's "ammo digits render correct → bug is `textRenderGlyph`- + specific" differentiator is **FALSE at D115**. The mirror is NOT + path-specific. (`textRenderGlyphOutlined` is what the level text uses; + `-level_09` direct-boot renders no "OBJECTIVE" flash / crosshair at + all — only the ammo HUD — so the M-7 "OBJECTIVE C: FAILED" note came + from a different scenario/run.) +- **Rect screen position is correct; only the texture content is flipped.** + Ammo HUD sits top-right and renders top-right. So this is a texture-U + reversal per quad, not a screen/framebuffer/viewport flip and not a + scissor issue. +- **Every stage probed is clean, yet the output is mirrored:** + - glyph bitmap in memory: correctly oriented — dumped `curchar->pixeldata` + for 'D' (idx 68, w7/loadw8/h9) row-by-row; stroke on the left, bowl on + the right, col 7 = wrap-pad of col 6. Not reversed. Rules out (c) a + font-blob word-swap. + - `gDPLoadTextureBlock` params: `loadw = (w+7)&0xF8` = 8 (or 16 for w9), + `line_bytes` = 8, tile `siz=1 fmt=4 cms=2`. Consistent. No evidence + the load-width vs sample-width interaction reverses anything — rules + out (b) as the *primary* cause. + - fast3d texrect: `ul.u = 0`, `lr.u = ` (e.g. 256 = 8 + texels); `ul.x < lr.x`. Corner↔UV pairing is correct + (`gfx_dp_texture_rectangle` 2187-2211, `gfx_draw_rectangle` 2113-2136: + ul=(ulx,uls), lr=(lrx,lrs)). Rules out (a) at the texrect layer. + - `import_texture_i8` (`gfx_pc.cpp:810`): strictly linear byte copy, no + row/col reversal. + - `gfx_opengl.cpp`: `vTexCoord = aTexCoord` pass-through; no U negation + in the vertex/fragment shader; `cms=2`→`GL_CLAMP_TO_EDGE`. +- **Conclusion:** the U-flip is downstream of everything fast3d computes — + in the GL vertex-buffer assembly / draw, OR it is a shared transform on + the rect quad's clip-space X that desyncs from U. This **re-opens + D114's shared-mirror hypothesis** (relocated from screen-space to + per-quad U/X space) and retires the D116 "proportional-font-specific, + texture-S load path" framing. Same mechanism plausibly explains the + "inverted" guards (mirrored skin) and mislocated door props (D113). +- **Next:** shader/vertex-buffer-level probe — dump the actual per-vertex + (x, u) pairs in the buffer handed to `glDrawArrays` for one glyph quad, + and render a 1-texel asymmetric test texture on a known rect to see + which axis inverts. Confidence the mirror is a real per-quad U/X flip + (not a capture artifact): **high** — reproduced on two independent HUD + text paths, rect positions provably correct. Confidence in the + GL-layer-vs-shared-transform split: **low** — not yet isolated. +- Probes left in tree (all `#ifdef PORT` + `getenv("GE_D116")`, zero-cost + when unset): `textrelated.c` textRenderGlyph / textRenderGlyphOutlined; + `gfx_pc.cpp` gfx_dp_texture_rectangle. + **`data/` deletion + recovery (session M-3).** `git worktree remove --force` on an agent worktree that had a directory *junction* `worktree/data → main/data` followed the junction and deleted the real diff --git a/port/fast3d/gfx_pc.cpp b/port/fast3d/gfx_pc.cpp index 3a783ba8..14abcea3 100644 --- a/port/fast3d/gfx_pc.cpp +++ b/port/fast3d/gfx_pc.cpp @@ -2204,6 +2204,21 @@ static void gfx_dp_texture_rectangle(int32_t ulx, int32_t uly, int32_t lrx, int3 ur->v = lrt; } + { + static int ge_d116 = -1; + if (ge_d116 < 0) ge_d116 = getenv("GE_D116") ? 1 : 0; + if (ge_d116) { + const auto& tt = rdp.texture_tile[tile]; + fprintf(stderr, + "[D116/f3d] tile=%d flip=%d ul(%d,%d) lr(%d,%d) uls=%d ult=%d dsdx=%d dtdy=%d " + "-> lrs=%.2f lrt=%.2f ul.u=%.2f lr.u=%.2f | TILE siz=%d fmt=%d line_bytes=%d " + "uls=%d lrs=%d width=%d height=%d cms=%d\n", + tile, (int)flip, ulx, uly, lrx, lry, (int)uls, (int)ult, (int)dsdx, (int)dtdy, + lrs, lrt, ul->u, lr->u, + tt.siz, tt.fmt, tt.line_size_bytes, tt.uls, tt.lrs, tt.width, tt.height, tt.cms); + } + } + uint8_t saved_tile = rdp.first_tile_index; if (saved_tile != tile) { rdp.textures_changed[0] = true; diff --git a/src/game/textrelated.c b/src/game/textrelated.c index bc4e7200..1d173437 100644 --- a/src/game/textrelated.c +++ b/src/game/textrelated.c @@ -8,6 +8,8 @@ /* D50: font segments are re-laid out from the N64 ROM layout to the PC C * layout (see romdataFixupFont). */ #include "romdata.h" +#include +#include #endif #define SPACE_WIDTH 5 @@ -257,6 +259,28 @@ Gfx *textRenderGlyph(Gfx *gdl, s32 *x, s32 *y, struct fontchar *curchar, struct gDPSetTileSize(gdl++, G_TX_RENDERTILE, 0, 0, (curchar->unkc - 1) << 2, (curchar->height - 1) << 2); */ gDPLoadTextureBlock(gdl++, curchar->pixeldata, G_IM_FMT_I, G_IM_SIZ_8b, ((curchar->width + 7) & 0xF8), curchar->height, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, 0, 0); +#ifdef PORT + { + static int ge_d116 = -1; + if (ge_d116 < 0) ge_d116 = getenv("GE_D116") ? 1 : 0; + if (ge_d116) { + const unsigned char *pd = (const unsigned char *)curchar->pixeldata; + int c = curchar->index; + fprintf(stderr, + "[D116] glyph '%c' idx=%d w=%d loadw=%d h=%d *x=%d s=%d t=%d pd=%p row0:", + (c >= 32 && c < 127) ? c : '?', c, curchar->width, + (curchar->width + 7) & 0xF8, curchar->height, + (int)*x, (int)text_s, (int)text_t, (void *)pd); + if (pd) { + int n = ((curchar->width + 7) & 0xF8) * 2; + int i; + if (n > 32) n = 32; + for (i = 0; i < n; i++) fprintf(stderr, " %02x", pd[i]); + } + fprintf(stderr, "\n"); + } + } +#endif } else { @@ -499,6 +523,28 @@ Gfx *textRenderGlyphOutlined(Gfx *gdl, s32 *x, s32 *y, struct fontchar *curchar, if (curchar->index < 0x80) { gDPSetTextureLUT(gdl++, G_TT_NONE); gDPLoadTextureBlock(gdl++, curchar->pixeldata, G_IM_FMT_I, G_IM_SIZ_8b, ((curchar->width + 7) & 0xF8), curchar->height, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, 0, 0); +#ifdef PORT + { + static int ge_d116 = -1; + if (ge_d116 < 0) ge_d116 = getenv("GE_D116") ? 1 : 0; + if (ge_d116) { + const unsigned char *pd = (const unsigned char *)curchar->pixeldata; + int c = curchar->index; + int loadw = (curchar->width + 7) & 0xF8; + int r; + fprintf(stderr, "[D116] O-glyph '%c' idx=%d w=%d loadw=%d h=%d *x=%d pd=%p\n", + (c >= 32 && c < 127) ? c : '?', c, curchar->width, loadw, + curchar->height, (int)*x, (void *)pd); + if (pd) for (r = 0; r < curchar->height && r < 16; r++) { + int i; + fprintf(stderr, " row%2d:", r); + for (i = 0; i < loadw && i < 24; i++) + fprintf(stderr, " %02x", pd[r * loadw + i]); + fprintf(stderr, "\n"); + } + } + } +#endif } else { gDPPipeSync(gdl++); gDPSetTextureLUT(gdl++, G_TT_IA16);