mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
fix(render): land D266 IA4/texedge decode + D271 portal-culling fixes (M-139 R2)
R2 extraction from the parked D236 investigation tree (park/d236-probes-m139): pulls out only the two verified, behavior-real fixes and drops every D267-D280 debug probe (census counters, PPM dumpers, per-tri/per-portal logging) that had accumulated alongside them. - D266: import_texture_ia4 decodes RDP IA4 as I2:A2 (was I3:A1, an inherited PD assumption); gfx_opengl.cpp's CVG_X_ALPHA "texedge" path now keys off the real alphacompare field (dither/threshold/none) instead of a hardcoded 0.19 cutoff, and gfx_pc.cpp excludes AC_DECAL tiles from the texedge path entirely (it never discards on real hardware). - D271: the D106 near-plane portal-straddling guard's `lim` constant widens from 1e5 to 1e38 so finite out-of-range bounds flow through to bgRectIntersect and clamp/cull exactly as they do on N64, instead of being force-mapped to full-screen (was drawing whole rooms N64 culls). Checked D271 against D249 (distant-geometry drop-out on Streets/Egyptian) before landing per M-139's overlap concern: no overlap, different code paths (near-plane portal guard vs. far-plane/LOD symptom); D249 stays open. Verified: 242/242 clean build; full 21-level verify.sh sweep clean (one dam NO-FRAMES on the sweep pass reproduced PASS on an immediate solo re-run - a load-sensitivity flake, not a regression). The D267-D280 probe code and the rest of the D236 tree-wall investigation stay parked on park/d236-probes-m139 for whenever that thread resumes (still no root cause for the tree wall itself - see D280's negative outcome). Also documents a build-environment gotcha hit this session: this Bash tool's shell reports MINGW64 (uname/$MSYSTEM) but is actually Git-for-Windows Bash, missing cmake/ninja/gcc from PATH until /c/msys64/mingw64/bin and /c/msys64/usr/bin are prepended explicitly (docs/porting-notes.md SS E). Claude-Session: https://claude.ai/code/session_01KwZViAFaXua3PKgHHjNgvi
This commit is contained in:
1 parent
e7fee0d7a4
commit
532092c732
5 files changed
+78
-17
No files matched your search
@@ -9929,6 +9929,8 @@ New env-gated census probe (`GE_D266`, in `gfx_sp_tri1`) logging per-draw tile f
|
||||
|
||||
**State: both fixes remain in-tree, UNCOMMITTED, with the probes.** They are necessary-but-unverified for the wall (the wall turned out to be a different texture class); the I2:A2 decode still owes a game-wide IA4 regression sweep before any commit. Nothing user-visible changed at the wall — consistent with D265's negative re-test.
|
||||
|
||||
**LANDED (M-140, R2 extraction, `fix/d266-d271-extraction` off `release/v0.3.0`):** the two real fixes only (IA4 I2:A2 decode + AC_DECAL-excluded texedge) extracted out of the parked D236 probe tree, stripped of every D267–D280 debug probe (census counters, PPM dumpers, per-tri logging — none of it behavior-affecting, but none of it belongs in a landed commit either). Full 21-level `verify.sh sweep` clean (20/21 PASS on first pass; the 21st, `dam`, came back NO-FRAMES/pixcount-0 on the sweep run but reproduced PASS on an immediate solo re-run — a load-sensitivity flake per the D117/D134 class, not a regression from this change). Build: 242/242, clean link. The D267–D280 probe code + everything else in the D236 investigation stays parked on `park/d236-probes-m139` for whenever the tree-instantiation thread resumes.
|
||||
|
||||
## D267 — M-126: IA8 dump probe + per-tile-slot upload-sequence tracking — the three IA8 images in play are an opaque noise card and two sparse mast sprites; none of them is the wall band
|
||||
|
||||
Probe infra (`GE_D267`, `gfx_pc.cpp`): PPM dumper + alpha histogram for 32px-wide IA8 strips in `import_texture_ia8`; per-tile-slot upload sequence tracking (`d267_seq` counter + `d267_slot_img[128]` stamped in the `import_texture()` dispatch) so tri-time probes can name which uploaded image a draw uses. Caveat discovered: `gfx_texture_cache_lookup()` returns early **before** the stamp on cache hits (key = fmt+addr+size+pal), so a draw's logged imgseq can be stale — the actual image is the earlier import that populated the cache (traceable via the GE_TEXI census log).
|
||||
@@ -9982,6 +9984,8 @@ Texture content (`GE_TEXDUMP`/`GE_TEXRAW` + offline decode of `texdump/r*.bin`):
|
||||
|
||||
**OUTCOME (user re-test 2026-09-14): NEGATIVE.** With the fix in place the trees still render as one big wall — and the user's read is that **the texture itself isn't being mapped correctly**, not merely an over-draw/alpha problem. So suspect #3 was a real N64-divergent over-draw (kept — it is a fidelity fix, do not revert) but it is NOT the wall's root cause. The D265 note gains weight: *"all captured cards sampled the same u:0..64/v:0..16 window — confirm against N64"* — if every card samples the same region of a shared texture, the result is exactly a repeating sheet. Top suspect for the next pass: **per-card S/T coordinates / UV windowing** (model DL vertex S/T, tile setup foldS/foldT/tcShift, or `d43_emit.py`'s S/T conversion), then TEXTLOD/mip sampling, then palette.
|
||||
|
||||
**LANDED (M-140, R2 extraction, `fix/d266-d271-extraction` off `release/v0.3.0`):** just the one-constant fix (`lim = 100000.0f` → `1e38f`), stripped of the `GE_D271`/`GE_D104` debug probes (per-portal capture, hit counters) — none of it changes behaviour, none belongs in a landed commit. Checked against **D249** (distant-geometry drop-out on Streets/Egyptian, per M-139's R2 overlap concern) before landing: no overlap — D249 is a far-plane/LOD symptom on a different code path (open, unrelated), this fix is a near-plane portal-straddling guard; D249 stays open. Full 21-level `verify.sh sweep` clean post-extraction (see D266's landing note for the one dam/NO-FRAMES flake, confirmed non-regression on retry).
|
||||
|
||||
**Owed / next.**
|
||||
1. **Per-card S/T audit** — dump the actual vertex S/T + tile for each wall card at draw time (extend `GE_D266W`) and check whether distinct cards carry distinct S/T windows; compare against the N64 ref (`docs/img/bugs/d227-surface-emulator-ref.png`: smooth bright band + sparse organic clusters, not a continuous sheet).
|
||||
2. Regression sweep of other levels' room counts is cheap insurance (`GE_D104=1` spot-checks); BUNKER1 already re-verified above.
|
||||
|
||||
@@ -1203,3 +1203,25 @@ and turns every such overrun into a fatal `*** stack smashing detected ***`
|
||||
one silently drifts from the other; when investigating a decode bug, diff
|
||||
the suspect path against its sibling path for the same format before
|
||||
re-deriving the bit layout from scratch. §F **D228**.
|
||||
- **A Claude Code session's default `Bash` tool is NOT the MSYS2 MINGW64
|
||||
shell the build needs, even when `uname -a`/`$MSYSTEM` claim it is (M-140).**
|
||||
On this box the tool's shell is Git-for-Windows Bash (root mounted from
|
||||
`C:/Program Files/Git`), which sets `MSYSTEM=MINGW64` and reports
|
||||
`MINGW64_NT-...` from `uname` for compatibility — both of AGENTS.md's own
|
||||
“are you in the right shell” checks pass while `cmake`/`ninja`/`gcc` are
|
||||
still missing from `PATH` (they resolve at `/c/msys64/mingw64/bin` and
|
||||
`/c/msys64/usr/bin`, not `/mingw64/bin` — that path exists but is a
|
||||
Git-Bash stub, not the project's real MSYS2 install). Symptom if you build
|
||||
anyway with only Git-Bash's own `gcc`/toolchain state: `cmake: command not
|
||||
found`, or (if some `gcc.exe` further down PATH resolves) every compile
|
||||
fails with `Cannot create temporary file in C:\Windows\: Permission
|
||||
denied` — a native Windows exe's `GetTempPath` falling back to `C:\Windows`
|
||||
because the child process doesn't actually see a usable `TMP`/`TEMP` the
|
||||
way it does when launched from a real MSYS2 shell window. Fix: explicitly
|
||||
`export PATH="/c/msys64/mingw64/bin:/c/msys64/usr/bin:$PATH"` (adjust if
|
||||
MSYS2 is installed elsewhere) before `./build-pc.sh`/`ninja`/`cmake` in any
|
||||
Claude Code Bash-tool session; do this once per session, first thing, not
|
||||
reactively after a mystery failure. Also: `cd` with a *backslash* Windows
|
||||
path (`cd C:\Users\...`) is unreliable in this same shell and can silently
|
||||
mis-parse to a mangled path on a later command in the same tool call —
|
||||
prefer `cd /c/Users/...` (forward slashes) for the build working directory.
|
||||
@@ -510,11 +510,27 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
|
||||
}
|
||||
}
|
||||
|
||||
/* D266: CVG_X_ALPHA ("texedge") modelled on the RDP's actual semantics,
|
||||
* keyed on the alphacompare field. The inherited line quantized to
|
||||
* opaque/discard at a hardcoded 0.19: it kept texels the N64 discards and
|
||||
* forced survivors fully opaque, defeating the ALPHA_CVG_SEL blend.
|
||||
* GE's Surface 1 IA4 tree strip (D236/D265) rendered as a wall because of
|
||||
* it. Now: AC_DITHER -> dithered discard; AC_THRESHOLD -> discard only
|
||||
* fully-transparent; AC_NONE -> discard below 0x80 and let survivors keep
|
||||
* their alpha for blending. (AC_DECAL never sets SHADER_OPT_TEXTURE_EDGE,
|
||||
* see gfx_pc.cpp.) */
|
||||
if (cc_features.opt_texture_edge && cc_features.opt_alpha) {
|
||||
append_line(fs_buf, &fs_len, " if (texel.a > 0.19) texel.a = 1.0; else discard;");
|
||||
if (cc_features.opt_noise) {
|
||||
append_line(fs_buf, &fs_len,
|
||||
" if (texel.a < ((random(vec3(floor(gl_FragCoord.xy * noise_scale), float(frame_count))) + 1.0) / 2.0)) discard;");
|
||||
} else if (cc_features.opt_alpha_threshold) {
|
||||
append_line(fs_buf, &fs_len, " if (texel.a < 1.0 / 256.0) discard;");
|
||||
} else {
|
||||
append_line(fs_buf, &fs_len, " if (texel.a < 0.5) discard;");
|
||||
}
|
||||
}
|
||||
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise && !cc_features.opt_texture_edge) {
|
||||
append_line(fs_buf, &fs_len,
|
||||
" texel.a *= floor(clamp(random(vec3(floor(gl_FragCoord.xy * noise_scale), float(frame_count))) + "
|
||||
"texel.a, 0.0, 1.0));");
|
||||
|
||||
+16
-6
@@ -44,6 +44,9 @@ uintptr_t gfxFramebuffer;
|
||||
#define SCALE_4_8(VAL_) ((VAL_)*0x11)
|
||||
#define SCALE_8_4(VAL_) ((VAL_) / 0x11)
|
||||
#define SCALE_3_8(VAL_) ((VAL_)*0x24)
|
||||
/* D266: RDP IA4 is I2:A2 (2-bit intensity, 2-bit alpha), not the I3:A1 the
|
||||
* PD-derived importer assumed. */
|
||||
#define SCALE_2_8(VAL_) ((VAL_)*0x55)
|
||||
#define SCALE_8_3(VAL_) ((VAL_) / 0x24)
|
||||
|
||||
// SCREEN_WIDTH and SCREEN_HEIGHT are defined in the headerfile
|
||||
@@ -724,13 +727,16 @@ static void import_texture_ia4(int tile, const LoadedTexture& loaded_texture, bo
|
||||
for (uint32_t i = 0; i < size_bytes * 2; i++, dest += 4) {
|
||||
const uint8_t byte = addr[i / 2];
|
||||
const uint8_t part = (byte >> (4 - (i % 2) * 4)) & 0xf;
|
||||
const uint8_t intensity = part >> 1;
|
||||
const uint8_t alpha = part & 1;
|
||||
const uint8_t c = SCALE_3_8(intensity);
|
||||
/* D266: I2:A2 -- the hardware IA4 layout. The old I3:A1 read made the
|
||||
* A2=1 canopy pixels fully opaque and the A2=2 edge dither invisible
|
||||
* (D236/D265 tree "wall"). */
|
||||
const uint8_t intensity = part >> 2;
|
||||
const uint8_t alpha = part & 3;
|
||||
const uint8_t c = SCALE_2_8(intensity);
|
||||
dest[0] = c;
|
||||
dest[1] = c;
|
||||
dest[2] = c;
|
||||
dest[3] = alpha ? 255 : 0;
|
||||
dest[3] = SCALE_2_8(alpha);
|
||||
}
|
||||
|
||||
const uint32_t width = rdp.texture_tile[tile].line_size_bytes * 2;
|
||||
@@ -1650,7 +1656,11 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
|
||||
bool use_alpha =
|
||||
(rdp.other_mode_l & (3 << 20)) == (G_BL_CLR_MEM << 20) && (rdp.other_mode_l & (3 << 16)) == (G_BL_1MA << 16);
|
||||
const bool use_fog = ((rdp.other_mode_l >> 30) == G_BL_CLR_FOG) || ((rdp.other_mode_l >> 26) == G_BL_A_FOG);
|
||||
const bool texture_edge = (rdp.other_mode_l & CVG_X_ALPHA) == CVG_X_ALPHA;
|
||||
/* D266: G_AC_DECAL (alphacompare == 2) never discards, so it must not
|
||||
* take the texedge path at all; NONE/THRESHOLD/DITHER are distinguished
|
||||
* in gfx_opengl.cpp via the existing options. */
|
||||
const bool ac_decal = (rdp.other_mode_l & (3U << G_MDSFT_ALPHACOMPARE)) == (2U << G_MDSFT_ALPHACOMPARE);
|
||||
const bool texture_edge = (rdp.other_mode_l & CVG_X_ALPHA) == CVG_X_ALPHA && !ac_decal;
|
||||
const bool use_noise = (rdp.other_mode_l & (3U << G_MDSFT_ALPHACOMPARE)) == G_AC_DITHER;
|
||||
const bool use_2cyc = (rdp.other_mode_h & (3U << G_MDSFT_CYCLETYPE)) == G_CYC_2CYCLE;
|
||||
const bool alpha_threshold = (rdp.other_mode_l & (3U << G_MDSFT_ALPHACOMPARE)) == G_AC_THRESHOLD;
|
||||
@@ -1659,7 +1669,7 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo
|
||||
const bool use_modulate = use_alpha && (rsp.extra_geometry_mode & G_MODULATE_EXT) != 0;
|
||||
const bool use_blur = (rdp.other_mode_h & (3U << G_MDSFT_TEXTFILT)) == G_TF_BLUR_EXT;
|
||||
|
||||
if (texture_edge) {
|
||||
if ((rdp.other_mode_l & CVG_X_ALPHA) == CVG_X_ALPHA) {
|
||||
use_alpha = true;
|
||||
}
|
||||
|
||||
|
||||
+18
-9
@@ -1722,16 +1722,25 @@ s32 sub_GAME_7F0B5864(s32 portalnum, bbox2d *bbox)
|
||||
{
|
||||
s32 degenerate = (bounds.max.x <= bounds.min.x) || (bounds.max.y <= bounds.min.y);
|
||||
#ifdef PORT
|
||||
/* D106: a portal straddling the camera near-plane feeds z==0 clip
|
||||
* points into transform3Dto2DWithZScaling, whose inv_z = -1e20 sends
|
||||
* them to +/-1e20-scale screen coords. On N64 those bracket the view
|
||||
* symmetrically so the box just clamps to full screen downstream;
|
||||
* on PC the exact garbage can come back min>max on only one axis, or
|
||||
* non-finite, slipping past the check above and culling the room the
|
||||
* player is walking toward. Any wildly out-of-range / non-finite
|
||||
* bound means "portal fills the view" -> full screensize. */
|
||||
/* D106 (revised by D271): a portal straddling the camera near-plane
|
||||
* feeds z==0 clip points into transform3Dto2DWithZScaling, whose
|
||||
* inv_z = -1e20 sends them to +/-1e20-scale screen coords. On N64
|
||||
* those huge FINITE values flow straight into bgRectIntersect (pure
|
||||
* comparisons), which either clamps them to the parent/screen box
|
||||
* (portal covers the view -> room kept) or yields an empty box
|
||||
* (the portal's visible projection falls off-screen -> room CULLED).
|
||||
* The original D106 guard mapped ANY out-of-range bound to full
|
||||
* screen, which over-included the second case: at Surface 1's boot
|
||||
* view, 9 of 11 near-plane-straddling portals produce garbage that
|
||||
* clamps to an empty box (N64 culls the room) but was forced
|
||||
* full-screen here, drawing whole rooms N64 never submits. Only
|
||||
* NON-FINITE bounds are unsafe to let through (NaN poisons the
|
||||
* comparisons downstream); this projection can reach ~1e26 at most,
|
||||
* so lim=1e38 catches NaN/inf and nothing finite. Finite min>max is
|
||||
* already caught by the `degenerate` check above, which is the N64
|
||||
* code path itself. */
|
||||
{
|
||||
const f32 lim = 100000.0f;
|
||||
const f32 lim = 1e38f;
|
||||
if (!(bounds.min.x > -lim && bounds.min.x < lim) ||
|
||||
!(bounds.min.y > -lim && bounds.min.y < lim) ||
|
||||
!(bounds.max.x > -lim && bounds.max.x < lim) ||
|
||||
|
||||
Reference in new issue
Block a user