mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
D219: scope the fire-particle byte-order fix by pixel format; resolved
M-115: re-apply M-113's store-time bswap scoped to the 16-bit GBI-size family only (PORT_PIXEL16 = bswap16 under #ifdef PORT; PORT_PIXEL32 stays identity). The fast3d importers disagree on pool byte order: import_texture_rgba16/_ia16 read manually big-endian bytes, while import_texture_rgba32 does PD_BE32() on a native load -- M-113's blanket swap hit both and regressed muzzle flash/ammo HUD (M-114). Format is the structural discriminator, not compmethod (fire frames span LOOKUP/RLE/HUFFMANBLUR/RLELOOKUP) or texnum range. Offline census of all 2698 ROM images (scratch/d219_headers.py / d219_census.txt): 94 non-zlib wide-pixel; the 16-bit family is IMAGE_FIRE_0..14 + texnum 1198-1201/2430/ 2510-2523, every ammo/flare/crosshair/muzzle-flash image is RGBA32. Runtime-verified on Dam (scripted fire): all 15 frames decode format=1, pool dumps warm R-dominant (mean opaque RGB 255,238,139). RGBA32 pool bytes bit-identical to the M-114-revert build. User playtest confirmed; D219 closed. GE_D219RAW probes generalized (no texnum filter), inert.
This commit is contained in:
1 parent
f55a89515f
commit
b1cf0eac4c
6 files changed
+410
-25
No files matched your search
@@ -74,7 +74,7 @@ D215,FIXED,1P weapon viewmodel never drew in-level —,"FIXED (src/game/gunfire.
|
|||||||
D216,PARTIAL,"Game.SkipIntro — boot straight to the file-select menu (M-83, port + one #ifdef PORT game-code line, PD parity, Phase 4 quick win).",LANDED + VERIFIED this session.
|
D216,PARTIAL,"Game.SkipIntro — boot straight to the file-select menu (M-83, port + one #ifdef PORT game-code line, PD parity, Phase 4 quick win).",LANDED + VERIFIED this session.
|
||||||
D217,FIXED,1P weapon viewmodel + third-person Bond model textures render garbled —,FIXED (M-90/M-92/M-97; PR #47 texpool alignment + PR #49 CI-cache palette key) —
|
D217,FIXED,1P weapon viewmodel + third-person Bond model textures render garbled —,FIXED (M-90/M-92/M-97; PR #47 texpool alignment + PR #49 CI-cache palette key) —
|
||||||
D218,PROPOSED,Wide FOV exposes the per-level draw-distance / fog boundary —,"Logged (M-84), fix proposed + parked."
|
D218,PROPOSED,Wide FOV exposes the per-level draw-distance / fog boundary —,"Logged (M-84), fix proposed + parked."
|
||||||
D219,OPEN,"PPK explosions render purple on Dam (M-87, user QA, ""not 3D but noting"").","OPEN — observed, not investigated."
|
D219,RESOLVED,"PPK explosions render purple on Dam (M-87, user QA, ""not 3D but noting"").","RESOLVED (M-115, 2026-09-12) —"
|
||||||
D220,PARTIAL,"1P muzzle flash position shifts upward at higher Video.FovScale (M-87, user QA, Jungle/M16).","ANALYZED (M-91) — the suspected unscaled-viewmodel root does NOT hold; this is correct perspective behavior, likely cosmetic/QoL."
|
D220,PARTIAL,"1P muzzle flash position shifts upward at higher Video.FovScale (M-87, user QA, Jungle/M16).","ANALYZED (M-91) — the suspected unscaled-viewmodel root does NOT hold; this is correct perspective behavior, likely cosmetic/QoL."
|
||||||
D85,OPEN,room primary/secondary DL → texLoadFromGdl garbage,"OPEN (safety-netted; widen/pool fixes landed, geometry now renders)"
|
D85,OPEN,room primary/secondary DL → texLoadFromGdl garbage,"OPEN (safety-netted; widen/pool fixes landed, geometry now renders)"
|
||||||
D86 · D87,RESOLVED,modelInitRwData truncated ptr · attract-demo BE ramromfilestructure,resolved
|
D86 · D87,RESOLVED,modelInitRwData truncated ptr · attract-demo BE ramromfilestructure,resolved
|
||||||
|
|||||||
|
@@ -448,13 +448,19 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
|||||||
| D216 | **`Game.SkipIntro` — boot straight to the file-select menu (M-83, port + one `#ifdef PORT` game-code line, PD parity, Phase 4 quick win).** New `s32 portSkipIntro` (`port/src/video.c`, `configRegisterInt("Game.SkipIntro", 0, 1)`), read once in `src/game/lv.c` at `LEVELID_TITLE` stage load — in the `else` of the existing `GE_STARTMENU` `#ifdef PORT` block. When set it does what every intro stage's own `!is_first_time_on_main_menu` branch does (`front.c`): `is_first_time_on_main_menu = FALSE; prev_keypresses = TRUE; maybe_is_in_menu = TRUE; menu_update = MENU_FILE_SELECT;` — i.e. it reuses the game's own post-intro route rather than inventing one, so the legal screen + Nintendo/Rare/GoldenEye logo attract loop is skipped. `GE_STARTMENU` still wins when both are set. Default 0 ⇒ untouched. | **LANDED + VERIFIED this session.** Build clean. Headless: default boot frame 90 = the TWYCROSS classification screen (10% non-black), `SkipIntro=1` frame 90 = the **SELECT FILE menu** (folders/photos/cursor all render, 89% non-black), no crash across repeated boots. Screenshots captured + eyeballed. **Owed:** nothing critical — a human boot to confirm the menu is fully interactive (cursor moves, a save slot opens). |
|
| D216 | **`Game.SkipIntro` — boot straight to the file-select menu (M-83, port + one `#ifdef PORT` game-code line, PD parity, Phase 4 quick win).** New `s32 portSkipIntro` (`port/src/video.c`, `configRegisterInt("Game.SkipIntro", 0, 1)`), read once in `src/game/lv.c` at `LEVELID_TITLE` stage load — in the `else` of the existing `GE_STARTMENU` `#ifdef PORT` block. When set it does what every intro stage's own `!is_first_time_on_main_menu` branch does (`front.c`): `is_first_time_on_main_menu = FALSE; prev_keypresses = TRUE; maybe_is_in_menu = TRUE; menu_update = MENU_FILE_SELECT;` — i.e. it reuses the game's own post-intro route rather than inventing one, so the legal screen + Nintendo/Rare/GoldenEye logo attract loop is skipped. `GE_STARTMENU` still wins when both are set. Default 0 ⇒ untouched. | **LANDED + VERIFIED this session.** Build clean. Headless: default boot frame 90 = the TWYCROSS classification screen (10% non-black), `SkipIntro=1` frame 90 = the **SELECT FILE menu** (folders/photos/cursor all render, 89% non-black), no crash across repeated boots. Screenshots captured + eyeballed. **Owed:** nothing critical — a human boot to confirm the menu is fully interactive (cursor moves, a save slot opens). |
|
||||||
| D217 | **1P weapon viewmodel + third-person Bond model textures render garbled — wrong/stale palette on model-DL CI textures (M-84, user QA; surfaced by D215 now that the viewmodel draws).** BUNKER1/Facility: the PP7 viewmodel has yellow+blue scrambled texels and a solid **green** patch by the hammer; Bond's in-game **watch face** shows the same green mismatch; user reports the **third-person Bond world model** does this "sometimes" too. Signature matches the **D161 family** — a CI-format tile drawn without the expected TLUT state, so fast3d's `import_texture` indexes a **stale `rdp.palette`**. D161 fixed the explicit `G_TT_NONE` case; this looks like model DLs not emitting `gDPLoadTLUT` through the port path, or the palette segment not resolving for weapon/character geometry. **Not a D215 regression** — D215 only un-gated the DL. | **FIXED (M-90/M-92/M-97; PR #47 texpool alignment + PR #49 CI-cache palette key) — the green-texture defect is fixed and merged, confirmed by two independent human playtests. The white missing-texture residual (AK47 / NPC-held world-model weapons) is split out as D228.** Status cell had gone stale at OPEN; corrected M-105. History: root cause characterised M-86; headless repro: `-level_34` FACILITY ~frame 300–400, PP7 **grip = flat green block** + green ring at the barrel joint + green-cast hand; barrel/slide upper correct black. Also green on `-level_30` DEPOT. **Clean on `-level_09` BUNKER1** (A/B control — same PP7, identical DL). **Two separate defects found:** ① **Wrong palette bound (the visible green — upstream of fast3d).** The grip is a CI4 solid-fill of palette index 0. Green `(24,65,0)` == RGBA5551 `0x1A00` == `rdp.palette[0]` at grip-import time. The grip's `count=1` model `G_LOADTLUT` is fed **different source bytes per level** for the same tile: BUNKER `00 01`→`PD_BE16`→`0x0001` (black ✓), FACILITY `1a 00`→`0x1A00` (green ✗). fast3d byteswaps + stores faithfully (proven correct by BUNKER) — the wrong bytes *arrive* from the model-DL / segment-address path. Strong tell: every *larger* model TLUT has `raw[0]==raw[1]==0x0000` with real colour from index 2 ⇒ GE's model-TLUT payload likely carries a **4-byte (2-entry) leading zero pad**, OR the port's model-TLUT source pointer is **4 bytes / 2 entries too low** (then the `count=1` grip load reads the tail of the preceding CI blob — BUNKER lucky, FACILITY not; fits "widespread across levels + weapons"). Ruled out this session: not tail-overflow (index 0 is in-range; matches M-85 `GE_D217Z`), not the fast3d byteswap. **Next:** dump the grip TLUT `G_SETTIMG` target vs the ROM/`pcmodels` sidecar PP7-grip material bytes; confirm the 4-byte offset vs N64; fix in model-DL segment resolution or `tools_pc/d43_emit.py` blob placement (sidecar regen). ② **CI texture-cache collision (proven, real, separate; fix landed).** `gfx_texture_cache_lookup` (`gfx_pc.cpp:522`) keyed CI textures on `{addr, palette_addrs[0/1], palidx, size_bytes}` with **no palette-content check**; GE reissues `gDPLoadTLUT` from repeated scratch-arena addresses with different content, so a later material could take a stale HIT and sample another material's decoded palette. **Fix on branch `fix/d217-model-texture-palette` (commit `f6fc0399`):** FNV-1a hash of the 512-byte `rdp.palette` computed only in `gfx_dp_load_tlut()`, added to the CI cache key (`port/fast3d/gfx_pc.{cpp,h}`, fast3d-internal, no `#ifdef`). Cheap, cannot worsen a decode; **owes a full 21-level `verify.sh sweep`** before merge (touches every palettized world texture's import path — cache-churn risk only). Does **not** fix defect ①. Screenshots: session scratchpad `d217/`, `fac_gun_crop.png`. Cross-ref `docs/dev/GRAPHICS-BACKLOG.md`. **User playtest matrix (M-87, raw notes — not yet triaged):** DAM/PPK: watch still green; AK47 viewmodel has white (missing-texture) patches on the magazine + barrel. FACILITY: PPK garbled **green AND blue**; 3rd-person Bond world model shows greenness on level load-in; AK47 has "weird blue / glitched" textures. JUNGLE: M16 viewmodel is orange in spots. Confirms multi-weapon (PP7/AK47/M16), multi-color (green/blue/white/orange) — consistent with the upstream model-TLUT-source theory (different trailing garbage per weapon/material) rather than one single fixed offset. **Scope clarified (M-87):** not just Bond's 1P viewmodel / 3P world model — **NPC-held weapon models (other characters' guns in third person) show the same glitched-color/white-missing texture symptom.** So the defect is in the shared weapon-model-DL/TLUT path used by *any* character holding that weapon, not something specific to Bond's model or the 1P viewmodel rig — widens the likely fix surface (weapon model loading in general) beyond "Bond's gun." **M-87 clarification:** not uniform across levels — severity and glitch *type* both vary per level (some levels show it lightly, some not at all in a given session, and the specific wrong-color/missing pLine truncated
|
| D217 | **1P weapon viewmodel + third-person Bond model textures render garbled — wrong/stale palette on model-DL CI textures (M-84, user QA; surfaced by D215 now that the viewmodel draws).** BUNKER1/Facility: the PP7 viewmodel has yellow+blue scrambled texels and a solid **green** patch by the hammer; Bond's in-game **watch face** shows the same green mismatch; user reports the **third-person Bond world model** does this "sometimes" too. Signature matches the **D161 family** — a CI-format tile drawn without the expected TLUT state, so fast3d's `import_texture` indexes a **stale `rdp.palette`**. D161 fixed the explicit `G_TT_NONE` case; this looks like model DLs not emitting `gDPLoadTLUT` through the port path, or the palette segment not resolving for weapon/character geometry. **Not a D215 regression** — D215 only un-gated the DL. | **FIXED (M-90/M-92/M-97; PR #47 texpool alignment + PR #49 CI-cache palette key) — the green-texture defect is fixed and merged, confirmed by two independent human playtests. The white missing-texture residual (AK47 / NPC-held world-model weapons) is split out as D228.** Status cell had gone stale at OPEN; corrected M-105. History: root cause characterised M-86; headless repro: `-level_34` FACILITY ~frame 300–400, PP7 **grip = flat green block** + green ring at the barrel joint + green-cast hand; barrel/slide upper correct black. Also green on `-level_30` DEPOT. **Clean on `-level_09` BUNKER1** (A/B control — same PP7, identical DL). **Two separate defects found:** ① **Wrong palette bound (the visible green — upstream of fast3d).** The grip is a CI4 solid-fill of palette index 0. Green `(24,65,0)` == RGBA5551 `0x1A00` == `rdp.palette[0]` at grip-import time. The grip's `count=1` model `G_LOADTLUT` is fed **different source bytes per level** for the same tile: BUNKER `00 01`→`PD_BE16`→`0x0001` (black ✓), FACILITY `1a 00`→`0x1A00` (green ✗). fast3d byteswaps + stores faithfully (proven correct by BUNKER) — the wrong bytes *arrive* from the model-DL / segment-address path. Strong tell: every *larger* model TLUT has `raw[0]==raw[1]==0x0000` with real colour from index 2 ⇒ GE's model-TLUT payload likely carries a **4-byte (2-entry) leading zero pad**, OR the port's model-TLUT source pointer is **4 bytes / 2 entries too low** (then the `count=1` grip load reads the tail of the preceding CI blob — BUNKER lucky, FACILITY not; fits "widespread across levels + weapons"). Ruled out this session: not tail-overflow (index 0 is in-range; matches M-85 `GE_D217Z`), not the fast3d byteswap. **Next:** dump the grip TLUT `G_SETTIMG` target vs the ROM/`pcmodels` sidecar PP7-grip material bytes; confirm the 4-byte offset vs N64; fix in model-DL segment resolution or `tools_pc/d43_emit.py` blob placement (sidecar regen). ② **CI texture-cache collision (proven, real, separate; fix landed).** `gfx_texture_cache_lookup` (`gfx_pc.cpp:522`) keyed CI textures on `{addr, palette_addrs[0/1], palidx, size_bytes}` with **no palette-content check**; GE reissues `gDPLoadTLUT` from repeated scratch-arena addresses with different content, so a later material could take a stale HIT and sample another material's decoded palette. **Fix on branch `fix/d217-model-texture-palette` (commit `f6fc0399`):** FNV-1a hash of the 512-byte `rdp.palette` computed only in `gfx_dp_load_tlut()`, added to the CI cache key (`port/fast3d/gfx_pc.{cpp,h}`, fast3d-internal, no `#ifdef`). Cheap, cannot worsen a decode; **owes a full 21-level `verify.sh sweep`** before merge (touches every palettized world texture's import path — cache-churn risk only). Does **not** fix defect ①. Screenshots: session scratchpad `d217/`, `fac_gun_crop.png`. Cross-ref `docs/dev/GRAPHICS-BACKLOG.md`. **User playtest matrix (M-87, raw notes — not yet triaged):** DAM/PPK: watch still green; AK47 viewmodel has white (missing-texture) patches on the magazine + barrel. FACILITY: PPK garbled **green AND blue**; 3rd-person Bond world model shows greenness on level load-in; AK47 has "weird blue / glitched" textures. JUNGLE: M16 viewmodel is orange in spots. Confirms multi-weapon (PP7/AK47/M16), multi-color (green/blue/white/orange) — consistent with the upstream model-TLUT-source theory (different trailing garbage per weapon/material) rather than one single fixed offset. **Scope clarified (M-87):** not just Bond's 1P viewmodel / 3P world model — **NPC-held weapon models (other characters' guns in third person) show the same glitched-color/white-missing texture symptom.** So the defect is in the shared weapon-model-DL/TLUT path used by *any* character holding that weapon, not something specific to Bond's model or the 1P viewmodel rig — widens the likely fix surface (weapon model loading in general) beyond "Bond's gun." **M-87 clarification:** not uniform across levels — severity and glitch *type* both vary per level (some levels show it lightly, some not at all in a given session, and the specific wrong-color/missing pLine truncated
|
||||||
| D218 | **Wide FOV exposes the per-level draw-distance / fog boundary — "blue artifacting" down long peripheral sightlines (M-84, user QA on the D211 FOV slider).** BUNKER→**Dam main tunnel** at `FovScale` ~150: geometry / backdrop past the level's fog-end distance shows at the widened frustum edges (blue = Dam's fog tint). Not a render bug — `fogLoadCurrentEnvironment` (`bgfog.c:305`) does `viSetZRange(Visibility.BlendMultiplier, Visibility.FarFog)`, so **`Visibility.FarFog` is both the far clip plane and the fog-saturation distance**; levels tune it for the stock ~60° FOV. | **Logged (M-84), fix proposed + parked.** Plan: `Video.DrawDistance` (percent, 100–400, default 100 ⇒ no-op) `#ifdef PORT` multiplier on `FarFog` at `bgfog.c:305` (+ the direct `arg0->Visibility.FarFog` reads just below, so fog stays consistent) plus `Video.DrawDistanceAutoFov` (0/1, default 1 — couples draw distance to `FovScale` unless `DrawDistance` is set explicitly). Clamp effective multiplier ≲2.5× (small znear ⇒ far-field z-fighting risk). **M-87: user confirms still occurring** at high FOV, and — reversing the earlier "not implemented by user request" — **now wants `Video.DrawDistance` implemented as a real QoL feature**, plus scope it to cover **LOD/detail-distance in general**, not just the fog/far-clip boundary this row root-caused. Open question for that broader ask: does GE have a *separate* geometry/object LOD-swap or draw-culling distance mechanism (distinct from `Visibility.FarFog`) worth exposing too, or is fog-distance the only meaningful "how far can you see" knob in this engine? Check for a per-object visibility-radius / simple-model-swap system before assuming `FarFog` is the whole story. |
|
| D218 | **Wide FOV exposes the per-level draw-distance / fog boundary — "blue artifacting" down long peripheral sightlines (M-84, user QA on the D211 FOV slider).** BUNKER→**Dam main tunnel** at `FovScale` ~150: geometry / backdrop past the level's fog-end distance shows at the widened frustum edges (blue = Dam's fog tint). Not a render bug — `fogLoadCurrentEnvironment` (`bgfog.c:305`) does `viSetZRange(Visibility.BlendMultiplier, Visibility.FarFog)`, so **`Visibility.FarFog` is both the far clip plane and the fog-saturation distance**; levels tune it for the stock ~60° FOV. | **Logged (M-84), fix proposed + parked.** Plan: `Video.DrawDistance` (percent, 100–400, default 100 ⇒ no-op) `#ifdef PORT` multiplier on `FarFog` at `bgfog.c:305` (+ the direct `arg0->Visibility.FarFog` reads just below, so fog stays consistent) plus `Video.DrawDistanceAutoFov` (0/1, default 1 — couples draw distance to `FovScale` unless `DrawDistance` is set explicitly). Clamp effective multiplier ≲2.5× (small znear ⇒ far-field z-fighting risk). **M-87: user confirms still occurring** at high FOV, and — reversing the earlier "not implemented by user request" — **now wants `Video.DrawDistance` implemented as a real QoL feature**, plus scope it to cover **LOD/detail-distance in general**, not just the fog/far-clip boundary this row root-caused. Open question for that broader ask: does GE have a *separate* geometry/object LOD-swap or draw-culling distance mechanism (distinct from `Visibility.FarFog`) worth exposing too, or is fog-distance the only meaningful "how far can you see" knob in this engine? Check for a per-object visibility-radius / simple-model-swap system before assuming `FarFog` is the whole story. |
|
||||||
| D219 | **PPK explosions render purple on Dam (M-87, user QA, "not 3D but noting").** Effect-color defect, not a texture-decode one — likely the D172 particle-color family (`gfx_lod_tile_offset` / `G_CC_INTERFERENCE` channel-order class) recurring on a different particle type/weapon than the D172 fix covered, or a fresh instance of the same class. | **OPEN — observed, not investigated.** Next: `GE_PCDUMP`/`GE_TEXDUMP` a Dam PPK explosion; check whether it's the same `gfx_lod_tile_offset` tile-sampling defect D172 fixed for blood, on an explosion DL D172's fix didn't touch. **Status: re-checked at runtime M-90 — mechanism covered by the D172 fix on current `main`; close pending a human by-eye pass (folds into PR #47 playtest).** **M-89 triage (local-Qwen, static analysis only, not yet runtime-confirmed):** the explosion DLs (`assets/oddtextures.c` `globalDL_0x078/0x120/0x1c8/0x270…`) use the same `G_CYC_2CYCLE`+`G_CC_INTERFERENCE,G_CC_MODULATEIA2`+`G_TL_TILE`, tile0=IA smoke/tile1=RGBA fire mechanism as the already-fixed D172 blood/spark records — no separate/unfixed code path found in `explosion.c` or the asset DLs. If still purple after D172's fix (which the user's Dam report postdates — needs re-confirming on current `main`), the next suspect is the GLSL combiner implementation of `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2` for cycle 2 specifically (`gfx_generate_cc`, `port/fast3d/gfx_pc.cpp:312-494`), not the tile-offset fix. **Next, concrete:** run Dam (`-level_33`) with `GE_D172=1` while triggering a PPK/grenade explosion and check for "BUG" lines in the log (silence = D172's fix already covers this DL and the row can likely close on a re-test; needs a scripted-fire repro, not yet built). **M-90 runtime re-check DONE (scripted-fire repro built with `GE_INPUTSCRIPT`): the D172 fix demonstrably covers Dam's particle DLs — row can close on a human by-eye pass.** Ran `-level_33` headless with `GE_D172=1`, 64 scripted Z-pulses (`GE_INPUTSCRIPT="200:Z;215:Z;..."`, ~15 controller-reads apart) and a `GE_PCDUMP` window over the same span. Result: **40/40 particle multitex tris log `SAMPLED1=tile1 (tmem=392)`** — the correct RGBA fire tile, i.e. exactly the post-D172-fix behaviour (pre-fix these read `SAMPLED1 = tile0 (tmem=0 fmt=IA)`, the smoke texture); **zero** "BUG"/unknown-GBI/D146-abort lines; **0** purple/magenta pixels (`r>120, b>120, g<90, r-g>50, b-g>50`) across all 28 captured frames. Caveat: the user's M-87 "purple" report may predate the D172 fix on their binary (playtests run on whatever build was current); this re-check proves the sampling path is correct on current `main`, which is as far as a headless pass can take it — a final by-eye look at a live Dam PPK firefight is the honest closer (cheap for the user, folds into the D217 PR #47 playtest). **M-108 — REOPENED, the by-eye closer FAILED.** User played a live Dam (`-level_33`) PPK firefight on the current `main` build (post-D233/D234 fixes, same session as D243) and confirmed explosions are **still blue/purple**, directly contradicting M-90's clean scripted-repro result (40/40 correct-tile samples, 0 purple pixels, 28 frames). This means the M-90 `GE_INPUTSCRIPT` scripted-fire repro was very likely **not exercising the same explosion instance/DL the live firefight hits** — plausible gaps: a different explosion variant (grenade vs. bullet-impact spark vs. enemy-death vs. PPK-specific muzzle/hit effect) using a DL the scripted test's Z-pulses didn't trigger, or a state/lighting/prim-color condition (M-89's alternate suspect: the `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2` cycle-2 GLSL combiner implementation, `gfx_generate_cc`, `port/fast3d/gfx_pc.cpp:312-494`) that only manifests under conditions the synthetic repro didn't reproduce. **Do not trust the M-90 all-clear for this finding going forward** — it proved one particle path clean, not that Dam's live-fire purple is fixed. Next: a live `GE_PCDUMP`/`GE_D172=1` capture during an actual purple explosion in real play (not a scripted approximation) is now the only way forward — need to pin down exactly what triggers it (PPK gunshots hitting a surface/enemy? a thrown/tossed explosive? which). **User has now confirmed (2026-09-12) this is NOT Dam-exclusive — game-wide, not just suspected.** Treat as confirmed scope, not pending a second-level check. **User wants this actively fixed, not just triaged** — next session should prioritize a live `GE_PCDUMP`/`GE_D172=1` capture during a real explosion (any level) over further static analysis, since the M-89/M-90 static passes are exhausted on this finding without producing a fix. **New HIGH-interest lead (M-110): `explosion.c` never clears `G_LIGHTING` before drawing any of the 15 particle DLs** (`globalDL_0x078` through `0x9a8` in `assets/oddtextures.c`, all structurally identical, all `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2`) — grepped every particle-draw call site in `explosion.c` (`gSPClearGeometryMode` calls at lines 883/1495/Line truncated
|
| D219 | **PPK explosions render purple on Dam (M-87, user QA, "not 3D but noting").** Effect-color defect, not a texture-decode one — likely the D172 particle-color family (`gfx_lod_tile_offset` / `G_CC_INTERFERENCE` channel-order class) recurring on a different particle type/weapon than the D172 fix covered, or a fresh instance of the same class. | **RESOLVED (M-115, 2026-09-12)** — byte-order fix in `src/game/image.c` scoped by pixel format (16-bit family bswap16-at-store, 32-bit identity); user playtest-confirmed. Superseded history below, which began: **OPEN — observed, not investigated.** Next: `GE_PCDUMP`/`GE_TEXDUMP` a Dam PPK explosion; check whether it's the same `gfx_lod_tile_offset` tile-sampling defect D172 fixed for blood, on an explosion DL D172's fix didn't touch. **Status: re-checked at runtime M-90 — mechanism covered by the D172 fix on current `main`; close pending a human by-eye pass (folds into PR #47 playtest).** **M-89 triage (local-Qwen, static analysis only, not yet runtime-confirmed):** the explosion DLs (`assets/oddtextures.c` `globalDL_0x078/0x120/0x1c8/0x270…`) use the same `G_CYC_2CYCLE`+`G_CC_INTERFERENCE,G_CC_MODULATEIA2`+`G_TL_TILE`, tile0=IA smoke/tile1=RGBA fire mechanism as the already-fixed D172 blood/spark records — no separate/unfixed code path found in `explosion.c` or the asset DLs. If still purple after D172's fix (which the user's Dam report postdates — needs re-confirming on current `main`), the next suspect is the GLSL combiner implementation of `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2` for cycle 2 specifically (`gfx_generate_cc`, `port/fast3d/gfx_pc.cpp:312-494`), not the tile-offset fix. **Next, concrete:** run Dam (`-level_33`) with `GE_D172=1` while triggering a PPK/grenade explosion and check for "BUG" lines in the log (silence = D172's fix already covers this DL and the row can likely close on a re-test; needs a scripted-fire repro, not yet built). **M-90 runtime re-check DONE (scripted-fire repro built with `GE_INPUTSCRIPT`): the D172 fix demonstrably covers Dam's particle DLs — row can close on a human by-eye pass.** Ran `-level_33` headless with `GE_D172=1`, 64 scripted Z-pulses (`GE_INPUTSCRIPT="200:Z;215:Z;..."`, ~15 controller-reads apart) and a `GE_PCDUMP` window over the same span. Result: **40/40 particle multitex tris log `SAMPLED1=tile1 (tmem=392)`** — the correct RGBA fire tile, i.e. exactly the post-D172-fix behaviour (pre-fix these read `SAMPLED1 = tile0 (tmem=0 fmt=IA)`, the smoke texture); **zero** "BUG"/unknown-GBI/D146-abort lines; **0** purple/magenta pixels (`r>120, b>120, g<90, r-g>50, b-g>50`) across all 28 captured frames. Caveat: the user's M-87 "purple" report may predate the D172 fix on their binary (playtests run on whatever build was current); this re-check proves the sampling path is correct on current `main`, which is as far as a headless pass can take it — a final by-eye look at a live Dam PPK firefight is the honest closer (cheap for the user, folds into the D217 PR #47 playtest). **M-108 — REOPENED, the by-eye closer FAILED.** User played a live Dam (`-level_33`) PPK firefight on the current `main` build (post-D233/D234 fixes, same session as D243) and confirmed explosions are **still blue/purple**, directly contradicting M-90's clean scripted-repro result (40/40 correct-tile samples, 0 purple pixels, 28 frames). This means the M-90 `GE_INPUTSCRIPT` scripted-fire repro was very likely **not exercising the same explosion instance/DL the live firefight hits** — plausible gaps: a different explosion variant (grenade vs. bullet-impact spark vs. enemy-death vs. PPK-specific muzzle/hit effect) using a DL the scripted test's Z-pulses didn't trigger, or a state/lighting/prim-color condition (M-89's alternate suspect: the `G_CC_INTERFERENCE`+`G_CC_MODULATEIA2` cycle-2 GLSL combiner implementation, `gfx_generate_cc`, `port/fast3d/gfx_pc.cpp:312-494`) that only manifests under conditions the synthetic repro didn't reproduce. **Do not trust the M-90 all-clear for this finding going forward** — it proved one particle path clean, not that Dam's live-fire purple is fixed. Next: a live `GE_PCDUMP`/`GE_D172=1` capture during an actual purple explosion in real play (not a scripted approximation) is now the only way forward — need to pin down exactly what triggers it (PPK gunshots hitting a surface/enemy? a thrown/tossed explosive? which). **User has now confirmed (2026-09-12) this is NOT Dam-exclusive — game-wide, not just suspected.** Treat as confirmed scope, not pending a second-level check. **User wants this actively fixed, not just triaged** — next session should prioritize a live `GE_PCDUMP`/`GE_D172=1` capture during a real explosion (any level) over further static analysis, since the M-89/M-90 static passes are exhausted on this finding without producing a fix. **New HIGH-interest lead (M-110): `explosion.c` never clears `G_LIGHTING` before drawing any of the 15 particle DLs** (`globalDL_0x078` through `0Line truncated
|
||||||
|
|
||||||
**M-111 (2026-09-12, live session, 6 build/relaunch cycles): G_LIGHTING lead RULED OUT; texture-cache-collision lead RULED OUT; ROOT CAUSE FOUND — R/B channel swap in the decoded fire particle texture data itself.** Live-captured 4 rounds against a real user playtest (not scripted) with the user confirming explosions were still blue/purple in every capture:
|
**M-111 (2026-09-12, live session, 6 build/relaunch cycles): G_LIGHTING lead RULED OUT; texture-cache-collision lead RULED OUT; ROOT CAUSE FOUND — R/B channel swap in the decoded fire particle texture data itself.** Live-captured 4 rounds against a real user playtest (not scripted) with the user confirming explosions were still blue/purple in every capture:
|
||||||
1. **G_LIGHTING probe fired 5000+ times across a full playtest with explosions triggered, 100% `LIGHTING=off`, 0 `D219 SUSPECT` hits.** (First attempt used the original 40-hit cap from the D172 probe, which silently exhausted on ordinary bullet-impact particles before any explosion — fixed to exempt the lit case from the cap and add an always-on periodic counter, confirming the code path really was being hit throughout, not just early. See `port/fast3d/gfx_pc.cpp` `gfx_sp_tri1`'s D172/D219 probe block.) **G_LIGHTING is conclusively not the mechanism.**
|
1. **G_LIGHTING probe fired 5000+ times across a full playtest with explosions triggered, 100% `LIGHTING=off`, 0 `D219 SUSPECT` hits.** (First attempt used the original 40-hit cap from the D172 probe, which silently exhausted on ordinary bullet-impact particles before any explosion — fixed to exempt the lit case from the cap and add an always-on periodic counter, confirming the code path really was being hit throughout, not just early. See `port/fast3d/gfx_pc.cpp` `gfx_sp_tri1`'s D172/D219 probe block.) **G_LIGHTING is conclusively not the mechanism.**
|
||||||
2. **Texture-cache-collision lead also ruled out**: extended the same probe to log the actual bound `TEXEL1` texture address/GL-texture-id/byte-count for every particle draw. 7 distinct 448-byte (16×14 RGBA16, matching the DL's own `CALC_LRS(16,14,...)`) addresses observed, each internally consistent — no address aliasing, no size mismatch, no stale-cache-slot signature.
|
2. **Texture-cache-collision lead also ruled out**: extended the same probe to log the actual bound `TEXEL1` texture address/GL-texture-id/byte-count for every particle draw. 7 distinct 448-byte (16×14 RGBA16, matching the DL's own `CALC_LRS(16,14,...)`) addresses observed, each internally consistent — no address aliasing, no size mismatch, no stale-cache-slot signature.
|
||||||
3. **Root cause: dumped the actual decoded fire textures with `GE_TEXDUMP=1`** (extended the existing dumper in `port/fast3d/gfx_opengl.cpp` to exempt 16×14 RGBA images from its own 400-image cap, same masking problem as the D172 cap) — captured all 15 `IMAGE_FIRE_0`..`14` variants as PPM after a live playtest with confirmed-purple explosions. **Every single one of the 15 decoded textures is blue/purple-dominant** (e.g. `fire000`: avg RGB `(102, 67, 138)` — B is the highest channel). Swapping the decoded R and B channels post-hoc on all 15 turns every one of them into a plausible warm fire palette (`fire000` swapped: `(138, 67, 102)`) — consistent across all 15, not a coincidence on one sample.
|
3. **Root cause: dumped the actual decoded fire textures with `GE_TEXDUMP=1`** (extended the existing dumper in `port/fast3d/gfx_opengl.cpp` to exempt 16×14 RGBA images from its own 400-image cap, same masking problem as the D172 cap) — captured all 15 `IMAGE_FIRE_0`..`14` variants as PPM after a live playtest with confirmed-purple explosions. **Every single one of the 15 decoded textures is blue/purple-dominant** (e.g. `fire000`: avg RGB `(102, 67, 138)` — B is the highest channel). Swapping the decoded R and B channels post-hoc on all 15 turns every one of them into a plausible warm fire palette (`fire000` swapped: `(138, 67, 102)`) — consistent across all 15, not a coincidence on one sample.
|
||||||
4. **This is NOT a bug in the shared RGBA16 decoder** (`import_texture_rgba16`, `port/fast3d/gfx_pc.cpp:653-681`) — that function's bit layout (`r=col16>>11, g=(col16>>6)&0x1f, b=(col16>>1)&0x1f, a=col16&1`) is standard N64 RGBA5551 and is proven correct by every other RGBA16 texture in the game rendering correctly; a bug in the shared function would break far more than 15 particle images. Also ruled out: the general C-array byte-order normalization (`gfx_tex_normalize_source`, D71) already runs upstream of every `import_texture_*` call including this one (`gfx_pc.cpp:1001-1008`), so it isn't a missing-normalize gap either. **The wrongness is in the decoded bytes themselves**, i.e. either (a) the ROM-resolved `Globalimagetable` entries for these 15 `IMAGE_FIRE_N` ids point at the wrong ROM offset/wrong data (an extraction/table-mapping bug, not a runtime rendering bug), or (b) these 15 images were authored/stored in a different sub-format than the rest of the game's RGBA16 assets that the shared decoder doesn't special-case. **Do NOT attempt a blanket R/B swap in `import_texture_rgba16`** — it's shared by the whole game and would break every correctly-rendering RGBA16 texture; whatever's wrong is specific to these 15 image ids. **Next, concrete:** audit how `IMAGE_FIRE_0`..`14`'s `Globalimagetable` entries resolve to ROM offsets (`port/src/gimgfixup.c`, `src/game/image.c`, `port/src/romassets_u.s`) and cross-reference against a real ROM dump / reference emulator screenshot of the same texture to confirm whether the extracted bytes are simply wrong-offset (fixable at the table/extraction level, in `port/`, no game-logic change) vs. a genuine N64-side sub-format quirk unique to `oddtextures.c`'s assets. Diagnostic infra left in tree (env-gated, inert by default): D172/D219 probe's uncapped lit-case + periodic summary counter (`gfx_sp_tri1`), and `GE_TEXDUMP`'s 16×14-exempt-from-cap fire dumper (`gfx_opengl_upload_texture`) — both safe to reuse for the follow-up. |
|
4. **This is NOT a bug in the shared RGBA16 decoder** (`import_texture_rgba16`, `port/fast3d/gfx_pc.cpp:653-681`) — that function's bit layout (`r=col16>>11, g=(col16>>6)&0x1f, b=(col16>>1)&0x1f, a=col16&1`) is standard N64 RGBA5551 and is proven correct by every other RGBA16 texture in the game rendering correctly; a bug in the shared function would break far more than 15 particle images. Also ruled out: the general C-array byte-order normalization (`gfx_tex_normalize_source`, D71) already runs upstream of every `import_texture_*` call including this one (`gfx_pc.cpp:1001-1008`), so it isn't a missing-normalize gap either. **The wrongness is in the decoded bytes themselves**, i.e. either (a) the ROM-resolved `Globalimagetable` entries for these 15 `IMAGE_FIRE_N` ids point at the wrong ROM offset/wrong data (an extraction/table-mapping bug, not a runtime rendering bug), or (b) these 15 images were authored/stored in a different sub-format than the rest of the game's RGBA16 assets that the shared decoder doesn't special-case. **Do NOT attempt a blanket R/B swap in `import_texture_rgba16`** — it's shared by the whole game and would break every correctly-rendering RGBA16 texture; whatever's wrong is specific to these 15 image ids. **Next, concrete:** audit how `IMAGE_FIRE_0`..`14`'s `Globalimagetable` entries resolve to ROM offsets (`port/src/gimgfixup.c`, `src/game/image.c`, `port/src/romassets_u.s`) and cross-reference against a real ROM dump / reference emulator screenshot of the same texture to confirm whether the extracted bytes are simply wrong-offset (fixable at the table/extraction level, in `port/`, no game-logic change) vs. a genuine N64-side sub-format quirk unique to `oddtextures.c`'s assets. Diagnostic infra left in tree (env-gated, inert by default): D172/D219 probe's uncapped lit-case + periodic summary counter (`gfx_sp_tri1`), and `GE_TEXDUMP`'s 16×14-exempt-from-cap fire dumper (`gfx_opengl_upload_texture`) — both safe to reuse for the follow-up.
|
||||||
|
|
||||||
|
**M-113 (2026-09-12, interactive session, ~10 build/run cycles): FIXED — genuine byte-order (endianness) bug in `src/game/image.c`'s runtime texture decompressor, not a data/extraction/combiner issue. Verified with real decoded-texture PPM captures (visual + numeric).** Continuation of the M-112 subagent's dead-end: that session's own two probes (`GE_D219RAW` in `texInflateNonZlib`/`import_texture_rgba16`) never fired because their filter assumed the GBI tile's declared width (16) rather than the bitstream's own native width (14) — `IMAGE_FIRE_N`'s real decoded shape is **14×14**, only the GBI's TMEM tile load rounds up to 16 for row alignment. Broadening the filter and getting the probes' env vars to actually reach the process (the M-112 "no stdout" blocker was a red herring — `timeout` was stripping the child's env in that shell; a plain `&` background + `taskkill //IM` works fine, at least in this interactive session) unblocked everything:
|
||||||
|
1. **Root cause:** `src/game/image.c`'s runtime bitstream decoder (`texReadUncompressed`, `texChannelsToPixels`, `texBuildLookup`, `texInflateLookup`, `texInflateLookupFromBuffer` — ground-truth decomp, unmodified) reconstructs each "wide" (>1 byte/pixel) pixel as a plain shifted-OR integer (e.g. RGBA16 `= R<<11|G<<6|B<<1|A`) and commits it with a **native machine store** (`dst16[x] = value`). On the N64 (big-endian) that store lays the bytes down exactly as the RSP/TMEM expects. `port/fast3d/gfx_pc.cpp import_texture_rgba16` (the shared, unmodified-by-this-fix consumer) reconstructs the same value by **manually reading the byte pair as big-endian** (`addr[0]<<8 | addr[1]`) — matching the N64 layout exactly. On this little-endian x86-64 host, the native store lays down the *opposite* byte order, so every wide-format pixel decoded through these five functions comes out with its bytes reversed — for RGBA5551 specifically, byte-reversing a `RRRRRGGG|GGBBBBBA` word swaps which bits land in which output channel, which manifests as roughly an R/B swap (exactly M-111's empirical finding). **Why only fire, not every RGBA16 texture:** single-byte formats (CI4/CI8/I4/I8/IA4/IA8 — nearly everything else in the game) have no byte order to get wrong; static, compile-time `assets/*.c` C-array textures are separately normalized by `gfx_tex_normalize_source` (D71) before `import_texture_*` ever sees them. `IMAGE_FIRE_0..14` appear to be one of the only asset sets in the game that are simultaneously (a) genuinely 16-bit-per-pixel (RGBA16, not IA8/CI) **and** (b) runtime-decompressed through `texLoad`'s bitstream path rather than a static compiled array — exposing a byte-order gap that was always latent in these five functions but had no other victim on hand to surface it.
|
||||||
|
2. **Confirmed empirically, not just by inspection:** dumped raw decoded RGBA5551 words at the exact point `texChannelsToPixels`/`texInflateLookup` write them (`GE_D219RAW=1`, Dam `-level_33`, scripted PPK fire via `GE_INPUTSCRIPT`) — e.g. pre-fix texnum 2111 decoded word `0xf1c2` (R=30,G=3,B=1 under standard RGBA5551 — briefly looks *plausible* red until you realize this is the value BEFORE the byte-swap fast3d applies; the two-stage mis-cancellation is what produced the reported blue/purple, not a one-step swap — see the fix verification below for the real signal). The conclusive test is the rendered/uploaded texture, not the intermediate value in isolation.
|
||||||
|
3. **Fix (`src/game/image.c`, `#ifdef PORT`/`#else` macro, no `src/` logic changed):** added `PORT_PIXEL16`/`PORT_PIXEL32` — `__builtin_bswap16`/`32` under `#ifdef PORT`, identity under `#else` (so the N64 build is byte-for-byte unaffected, verified by inspection: the macro is a no-op expansion, not a code path change) — and wrapped every wide-pixel (`RGBA32`/`RGB24`/`RGBA16`/`IA16`/`RGB15`) store in the five functions listed above. CI/I4/I8/IA4/IA8 branches (single-byte, no byte order) are untouched. This is the D3x/AGENTS.md#2 narrow ABI/byte-layout exception class: mechanical, semantics-preserving (same numeric color, corrected byte order for the LE host), scoped to exactly the format cases that need it.
|
||||||
|
4. **Verified, numerically and visually:** `GE_TEXDUMP=1` PPM capture of the 15 `IMAGE_FIRE_N` textures post-fix — average RGB flipped from B-dominant (M-111: `fire000 (102,67,138)`) to R-dominant (post-fix `fire000 (141,127,53)`, `fire003 (147,81,24)`, `fire005 (47,10,2)`); a 10x-upscaled render of one frame and a 6-frame animation strip (frames 000-006, session scratchpad) show a clean, unambiguous fire-sprite animation — bright yellow-white core cooling through orange to red/black ash, exactly the expected in-game fire look, no purple/blue anywhere. Sanity-checked a montage of 12 ordinary (non-fire) world textures from the same Dam capture (clouds, hazard stripes, pipes, grating, concrete) — all render correctly, no regression from the format-gated change. `-level_09` (BUNKER1) and `-level_33` (Dam) both boot and render crash-free post-fix (10s+ each, no `ge007.crash.log`). **Not yet done:** a full 21-level `verify.sh sweep` and a genuine live human playtest confirming the fix in real (non-scripted) gameplay — the scripted-fire + texdump evidence here is strong but the row should stay open until a human eyeballs a real firefight on this build. `docs/porting-notes.md` gets a new entry for this bug class (runtime-decoded multi-byte pixel formats need explicit byte-order handling; static C-array textures already get this via D71/`gfx_tex_normalize_source`, runtime-decompressed ones did not). **M-114 — REGRESSION, reverted.** User playtest after M-113 landed reported 1P muzzle flash rendering blue and the ammo HUD counter rendering pink — colors inverted on textures that were fine before. Root cause: M-113's `PORT_PIXEL16`/`PORT_PIXEL32` swap is applied unconditionally to every call site in `texReadUncompressed`, `texChannelsToPixels`, `texInflateLookup`, and `texInflateLookupFromBuffer` — i.e. to every RGBA16/RGB15/IA16/RGBA32/RGB24 texture decoded through these shared functions, not just `IMAGE_FIRE_N`. This is the exact failure mode already logged above against the sibling function ("do NOT attempt a blanket R/B swap in `import_texture_rgba16` — it's shared by the whole game and would break every correctly-rendering RGBA16 texture"), just repeated one file over. Muzzle flash and the ammo HUD icon evidently decode through this same shared path without needing the swap. **Fix applied:** `PORT_PIXEL16`/`PORT_PIXEL32` reverted to identity (no-op) in `src/game/image.c`; the macro/call-site wrapping is left in place as scaffolding. **D219 is OPEN again** — the fire-particle color bug is unfixed. **Next, concrete, not yet done:** find the actual property that distinguishes `IMAGE_FIRE_N`'s decode from a working same-format sibling (M-112 already narrowed one candidate: FIRE is baked directly into a DL rather than going through the `sImageTableEntry` table that flare/ammo/impact sprites use — confirm whether that DL-embedded path is what needs the swap, or whether it's specifically `compmethod`, per M-112's still-open next step) and scope the macro to that property, not to a hardcoded texnum range. Not runtime-verified this session (static revert only, done to stop an active regression fast) — a human should confirm muzzle flash/ammo colors are back to normal on this build. **M-115 (2026-09-12, delegated session, 3 build/run cycles): fix RE-APPLIED, scoped by GBI pixel format — the M-114 regression resolved by construction, fire fixed again.** Per M-114's brief, found the property that distinguishes `IMAGE_FIRE_N`'s decode from working same-format siblings and scoped the macros to it. (1) **The discriminator is the decoded pixel FORMAT, not compmethod or texnum.** Full offline census of all 2698 ROM image-table entries (`scratch/d219_headers.py`, parsing real bitstream headers with the same MSB-first `texReadBits` contract as `src/game/image_bank.c`; zlib images skipped after the 2-byte `rz` preamble per `decompressdata`): only 94 non-zlib wide-pixel images exist — RGBA16=19, RGB15=14, IA16=1, RGBA32=60. The fire frames span compmethods RLE(4)/LOOKUP(5)/RLELOOKUP(7)/HUFFMANBLUR(8) (confirmed offline AND at runtime), so compmethod cannot scope the fix; a texnum range is a hardcoded landmine. Format IS structural: every 16-bit GBI-size format (RGBA16/RGB15/IA16) is consumed by importers that read the pool as **manually big-endian bytes** (`import_texture_rgba16`: `col16=(addr[0]<<8)|addr[1]`; `import_texture_ia16`: `intensity=addr[0], alpha=addr[1]`), while every 32-bit format (RGBA32/RGB24) is consumed by `import_texture_rgba32`, which does `PD_BE32()` on a **native** u32 load — it expects host-order words. So bswap-at-store is required for exactly the 16-bit family and must be identity for the 32-bit family; M-113's blanket swap hit both (M-114), M-114's full revert broke the 16-bit family again. (2) **Change (`src/game/image.c`, `#ifdef PORT` only, no call-site edits):** `PORT_PIXEL16(x)` = `__builtin_bswap16((unsigned short)(x))`; `PORT_PIXEL32(x)` = `(x)`. Every existing call site Line truncated
|
||||||
| D220 | **1P muzzle flash position shifts upward at higher `Video.FovScale` (M-87, user QA, Jungle/M16).** Likely shares D211's noted residual: the viewmodel draws through `c_perspfovy`/unscaled fovy while the world view is FovScale-scaled (`fr.c:737`), so a flash sprite anchored to viewmodel-space vs. screen-space math can drift as the two diverge more at higher scale. | **ANALYZED (M-91) — the suspected unscaled-viewmodel root does NOT hold; this is correct perspective behavior, likely cosmetic/QoL.** The 1P hand + flash are model nodes in ONE gun DL (`gunfire.c` builds `rwmtx[1..4]` for hammer/flash/billboard nodes) drawn through the single per-frame projection from `viSetupCurrentPlayerView` — which IS FovScale-scaled on PC. So the flash and the muzzle it attaches to share projection and matrix chain: their relative position is geometrically preserved. What moves is the whole camera-anchored viewmodel: under perspective, screen offset from center ∝ 1/tan(fovy/2), so widening the FOV migrates every camera-space point (gun body included) toward screen center — a muzzle sitting slightly below/right of center visibly drifts up-left at FovScale 150. Same family as the M-85 accepted "viewmodel scales down, stays bottom-right anchored". `c_perspfovy`/`currentPlayerSetCameraScale` do stay unscaled (the D211 note) but that feeds frustum-cull/screen↔world math (→ D222), not the flash's render path. **Disposition: human call** — if it bothers at high FovScale, the fix is a QoL viewmodel re-anchor/scale-at-FOV (VIEWMODEL-RESEARCH.md family), not a fidelity bug; no game-logic change either way. Optional headless confirm: Jungle M16 fire at FovScale 100 vs 150, measure flash centroid vs muzzle in PPM pairs (expected: both shift toward center together). |
|
| D220 | **1P muzzle flash position shifts upward at higher `Video.FovScale` (M-87, user QA, Jungle/M16).** Likely shares D211's noted residual: the viewmodel draws through `c_perspfovy`/unscaled fovy while the world view is FovScale-scaled (`fr.c:737`), so a flash sprite anchored to viewmodel-space vs. screen-space math can drift as the two diverge more at higher scale. | **ANALYZED (M-91) — the suspected unscaled-viewmodel root does NOT hold; this is correct perspective behavior, likely cosmetic/QoL.** The 1P hand + flash are model nodes in ONE gun DL (`gunfire.c` builds `rwmtx[1..4]` for hammer/flash/billboard nodes) drawn through the single per-frame projection from `viSetupCurrentPlayerView` — which IS FovScale-scaled on PC. So the flash and the muzzle it attaches to share projection and matrix chain: their relative position is geometrically preserved. What moves is the whole camera-anchored viewmodel: under perspective, screen offset from center ∝ 1/tan(fovy/2), so widening the FOV migrates every camera-space point (gun body included) toward screen center — a muzzle sitting slightly below/right of center visibly drifts up-left at FovScale 150. Same family as the M-85 accepted "viewmodel scales down, stays bottom-right anchored". `c_perspfovy`/`currentPlayerSetCameraScale` do stay unscaled (the D211 note) but that feeds frustum-cull/screen↔world math (→ D222), not the flash's render path. **Disposition: human call** — if it bothers at high FovScale, the fix is a QoL viewmodel re-anchor/scale-at-FOV (VIEWMODEL-RESEARCH.md family), not a fidelity bug; no game-logic change either way. Optional headless confirm: Jungle M16 fire at FovScale 100 vs 150, measure flash centroid vs muzzle in PPM pairs (expected: both shift toward center together). |
|
||||||
| D85 | room primary/secondary DL → `texLoadFromGdl` garbage | OPEN (safety-netted; widen/pool fixes landed, geometry now renders) |
|
| D85 | room primary/secondary DL → `texLoadFromGdl` garbage | OPEN (safety-netted; widen/pool fixes landed, geometry now renders) |
|
||||||
| D86 · D87 | modelInitRwData truncated ptr · attract-demo BE `ramromfilestructure` | resolved |
|
| D86 · D87 | modelInitRwData truncated ptr · attract-demo BE `ramromfilestructure` | resolved |
|
||||||
|
|||||||
@@ -298,6 +298,33 @@ through a converter or a runtime bswap fixup reads scrambled.
|
|||||||
(`GE_IS_NULL()`) so the guard survives. Same class as the D143 textRender
|
(`GE_IS_NULL()`) so the guard survives. Same class as the D143 textRender
|
||||||
NULL guards. Audit any hand-rolled libc primitive in `src/` that a port
|
NULL guards. Audit any hand-rolled libc primitive in `src/` that a port
|
||||||
NULL can reach.
|
NULL can reach.
|
||||||
|
- **D219 (M-112; the finding itself resolved in M-115 — kept as a triage lesson) — "the shared texture decoder is proven correct" is
|
||||||
|
a claim about *pixel format*, not *compression method*, and the two are
|
||||||
|
independent knobs on the same runtime `texLoad`.** `src/game/image.c`'s
|
||||||
|
non-zlib texture decompressor reads a per-image 4-bit `TEXCOMPMETHOD_*`
|
||||||
|
from the bitstream (uncompressed / huffman / huffman-per-channel / RLE /
|
||||||
|
lookup / huffman-lookup / RLE-lookup / huffman-blur / RLE-blur) independent
|
||||||
|
of the 4-bit pixel-`format` (RGBA32/16, IA16, I8/4, CI8/4 …). Two textures
|
||||||
|
sharing a pixel format can still take completely different code paths:
|
||||||
|
`_HUFFMAN`/`_HUFFMANPERHCHANNEL`/`_RLE` go through `texChannelsToPixels`
|
||||||
|
(per-channel planes in R,G,B,A order), while `_LOOKUP`/`_HUFFMANLOOKUP`/
|
||||||
|
`_RLELOOKUP` go through `texBuildLookup`+`texInflateLookup*` (a packed
|
||||||
|
color table, bits read directly in final pixel layout). "Every other
|
||||||
|
RGBA16 texture in the game renders correctly, so the decoder can't be the
|
||||||
|
bug" is **not** a valid inference unless you've confirmed the *comparison*
|
||||||
|
texture used the *same* `TEXCOMPMETHOD_*` as the suspect one — they can
|
||||||
|
silently diverge into different functions despite an identical
|
||||||
|
`G_IM_FMT_RGBA,G_IM_SIZ_16b` DL declaration. Also: whether a texture's
|
||||||
|
`G_SETTIMG` marker is baked directly into a compiled display list
|
||||||
|
(`gsDPSetTextureImage(...IMAGESEG(id))` in `assets/*.c`, fixed up by
|
||||||
|
`gimgFixupGlobalimagetable`'s DL-bswap pass) vs. synthesized at runtime
|
||||||
|
from an `sImageTableEntry`'s `index` field (`texSelect`,
|
||||||
|
`src/game/othermodemicrocode.c`) is a real, checkable difference in
|
||||||
|
*resolution* path worth ruling in/out early — but both funnel into the
|
||||||
|
same `texLoadFromDisplayList`→`texLoad` call, so it is a weaker lead than
|
||||||
|
the compression-method one. See `docs/dev/findings.md` D219 for the full
|
||||||
|
writeup and an env-gated (`GE_D219RAW`) diagnostic left in the tree to
|
||||||
|
check this on the next session that can capture runtime output.
|
||||||
- **D157 — a small value in the LOW BYTE of a BE `s32` word, read as `s8` at
|
- **D157 — a small value in the LOW BYTE of a BE `s32` word, read as `s8` at
|
||||||
the word's last offset, reads 0 on LE after the converter byte-swaps it.**
|
the word's last offset, reads 0 on LE after the converter byte-swaps it.**
|
||||||
Same family as D151/D139. `struct objective_entry.difficulty` is `s8` at
|
Same family as D151/D139. `struct objective_entry.difficulty` is `s8` at
|
||||||
@@ -660,6 +687,46 @@ through a converter or a runtime bswap fixup reads scrambled.
|
|||||||
noise data is flat (~3.7) at every pitch. That three-way split settles
|
noise data is flat (~3.7) at every pitch. That three-way split settles
|
||||||
decode-vs-pitch-vs-source-data in one pass with no rebuild.
|
decode-vs-pitch-vs-source-data in one pass with no rebuild.
|
||||||
|
|
||||||
|
- **D219 — a runtime bitstream decoder that stores a "wide" (>1 byte/pixel)
|
||||||
|
reconstructed value via a native machine write needs an explicit
|
||||||
|
byte-order fixup on PC; a compile-time C-array asset does not. And the
|
||||||
|
fixup is per *importer family*, not per texture: scope it by pixel
|
||||||
|
format, never by texnum range or compmethod (M-113/M-114/M-115).**
|
||||||
|
`src/game/image.c`'s texture decompressor (`texReadUncompressed`,
|
||||||
|
`texChannelsToPixels`, `texBuildLookup`, `texInflateLookup`,
|
||||||
|
`texInflateLookupFromBuffer`) builds each wide pixel as a shifted-OR
|
||||||
|
integer and does a native store — correct on N64 (native == big-endian),
|
||||||
|
byte-reversed on x86-64. This is the **opposite** of the usual "BE ROM
|
||||||
|
data read as LE" direction in this section: here the PORT *host* writes
|
||||||
|
native-endian and a downstream *shared, otherwise-correct* consumer has
|
||||||
|
opinions about byte order. The trap M-113→M-114 hit: the fast3d importer
|
||||||
|
family is NOT uniform. `import_texture_rgba16`/`_ia16` (G_IM_SIZ_16b:
|
||||||
|
RGBA16, RGB15, IA16) read the pool as **manually big-endian bytes**
|
||||||
|
(`(addr[0]<<8)|addr[1]`) → the decoder must store `bswap16(value)`;
|
||||||
|
`import_texture_rgba32` (G_IM_SIZ_32b: RGBA32, RGB24) does `PD_BE32()`
|
||||||
|
on a **native** u32 load → it wants host-order words and a store-time
|
||||||
|
swap *inverts* them (that was the muzzle-flash-blue/ammo-pink
|
||||||
|
regression). So the correct scope is the decoded `TEXFORMAT_*` — which
|
||||||
|
every call site already switches on — not compmethod (the fire frames
|
||||||
|
span LOOKUP/RLE/HUFFMANBLUR/RLELOOKUP) and not a hardcoded texnum range.
|
||||||
|
Static `assets/*.c` C-array textures don't hit this because
|
||||||
|
`gfx_tex_normalize_source` (D71) already normalizes them upstream of
|
||||||
|
`import_texture_*`; only assets that are BOTH genuinely multi-byte-per-
|
||||||
|
pixel AND decompressed at runtime through `texLoad`'s bitstream path are
|
||||||
|
exposed. Offline ROM census (M-115): 94 such images — the 16-bit family
|
||||||
|
is exactly `IMAGE_FIRE_0..14` + texnum 1198–1201/2430/2510–2523, and
|
||||||
|
every ammo/flare/crosshair/muzzle-flash wide-pixel image is RGBA32.
|
||||||
|
General rule for this bug class: before "fixing" a shared decoder's
|
||||||
|
byte order, enumerate **every consumer** of the buffer and check which
|
||||||
|
endianness each one assumes — the fix belongs at the store site, keyed
|
||||||
|
on the format each consumer family expects.
|
||||||
|
Final state (M-115, resolved): `PORT_PIXEL16` = bswap16 under `#ifdef PORT`
|
||||||
|
(16-bit family only), `PORT_PIXEL32` = identity, wrapped around every
|
||||||
|
wide-pixel store in the five functions above; CI/I4/I8/IA4/IA8 (single
|
||||||
|
byte, no byte order) untouched. The intermediate history — M-113's blanket
|
||||||
|
swap regressing muzzle flash/ammo HUD (M-114), reverted to identity, then
|
||||||
|
M-115's format-scoped re-application — is in `docs/dev/findings.md` D219.
|
||||||
|
|
||||||
## D2. The HUD/model "X-mirror" (D114/D116) — RESOLVED: it was an upside-down capture
|
## D2. The HUD/model "X-mirror" (D114/D116) — RESOLVED: it was an upside-down capture
|
||||||
|
|
||||||
**M-33 (finding D168).** There was no mirror. `gfx_opengl_dump_bound_fbo`
|
**M-33 (finding D168).** There was no mirror. `gfx_opengl_dump_bound_fbo`
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
== census (kind, format) -> count ==
|
||||||
|
('NONZLIB', 'I4'): 363
|
||||||
|
('NONZLIB', 'I8'): 231
|
||||||
|
('NONZLIB', 'IA16'): 1
|
||||||
|
('NONZLIB', 'IA4'): 74
|
||||||
|
('NONZLIB', 'IA8'): 192
|
||||||
|
('NONZLIB', 'RGB15'): 14
|
||||||
|
('NONZLIB', 'RGBA16'): 19
|
||||||
|
('NONZLIB', 'RGBA32'): 60
|
||||||
|
('ZLIB', 'IA16_CI4'): 15
|
||||||
|
('ZLIB', 'IA16_CI8'): 91
|
||||||
|
('ZLIB', 'RGBA16_CI4'): 83
|
||||||
|
('ZLIB', 'RGBA16_CI8'): 1555
|
||||||
|
|
||||||
|
== non-zlib WIDE-pixel images: 94 ==
|
||||||
|
texnum= 0 IMAGE_COPYICON RGBA32 comp=7 32x28 lod=0
|
||||||
|
texnum= 2 IMAGE_DELICON RGBA32 comp=8 32x28 lod=0
|
||||||
|
texnum= 437 _image437_ID RGBA32 comp=8 32x16 lod=6
|
||||||
|
texnum= 439 _image439_ID RGBA32 comp=8 32x16 lod=6
|
||||||
|
texnum= 884 _image884_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=1198 _image1198_ID RGBA16 comp=6 64x17 lod=6
|
||||||
|
texnum=1199 _image1199_ID RGBA16 comp=6 64x17 lod=6
|
||||||
|
texnum=1200 _image1200_ID RGBA16 comp=6 64x17 lod=6
|
||||||
|
texnum=1201 _image1201_ID RGBA16 comp=6 64x17 lod=6
|
||||||
|
texnum=2085 IMAGE_FIRE_0 RGBA16 comp=5 14x14 lod=0
|
||||||
|
texnum=2087 IMAGE_FIRE_1 RGBA16 comp=8 14x14 lod=0
|
||||||
|
texnum=2089 IMAGE_FIRE_2 RGBA16 comp=4 14x14 lod=0
|
||||||
|
texnum=2091 IMAGE_FIRE_3 RGBA16 comp=8 14x14 lod=0
|
||||||
|
texnum=2093 IMAGE_FIRE_4 RGBA16 comp=8 14x14 lod=0
|
||||||
|
texnum=2095 IMAGE_FIRE_5 RGBA16 comp=7 14x14 lod=0
|
||||||
|
texnum=2097 IMAGE_FIRE_6 RGBA16 comp=7 14x14 lod=0
|
||||||
|
texnum=2099 IMAGE_FIRE_7 RGBA16 comp=4 14x14 lod=0
|
||||||
|
texnum=2101 IMAGE_FIRE_8 RGBA16 comp=4 14x14 lod=0
|
||||||
|
texnum=2103 IMAGE_FIRE_9 RGBA16 comp=8 14x14 lod=0
|
||||||
|
texnum=2105 IMAGE_FIRE_10 RGBA16 comp=8 14x14 lod=0
|
||||||
|
texnum=2107 IMAGE_FIRE_11 RGBA16 comp=4 14x14 lod=0
|
||||||
|
texnum=2109 IMAGE_FIRE_12 RGBA16 comp=8 14x14 lod=0
|
||||||
|
texnum=2111 IMAGE_FIRE_13 RGBA16 comp=8 14x14 lod=0
|
||||||
|
texnum=2113 IMAGE_FIRE_14 RGBA16 comp=8 14x14 lod=0
|
||||||
|
texnum=2114 _image2114_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2115 _image2115_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2116 _image2116_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2117 _image2117_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2119 _image2119_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2120 _image2120_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2121 _image2121_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2122 _image2122_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2123 _image2123_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2124 _image2124_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2125 _image2125_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2126 _image2126_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2127 _image2127_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2128 IMAGE_FLAREORANGELINE RGBA32 comp=4 16x32 lod=6
|
||||||
|
texnum=2134 IMAGE_FLAREBLUEROUND RGBA32 comp=8 32x32 lod=0
|
||||||
|
texnum=2135 IMAGE_FLAREBLUELINE RGBA32 comp=8 16x32 lod=6
|
||||||
|
texnum=2137 _image2137_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2138 _image2138_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2139 _image2139_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2140 _image2140_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2141 _image2141_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2142 _image2142_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2143 _image2143_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2144 _image2144_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2145 _image2145_ID RGBA32 comp=8 32x32 lod=0
|
||||||
|
texnum=2146 _image2146_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2147 _image2147_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2148 _image2148_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2149 _image2149_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2150 _image2150_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2151 _image2151_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2152 _image2152_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2153 _image2153_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2154 _image2154_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2155 _image2155_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2156 _image2156_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2157 _image2157_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2158 _image2158_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2159 _image2159_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2160 _image2160_ID RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2161 IMAGE_ROCKETAMMO RGBA32 comp=4 7x22 lod=0
|
||||||
|
texnum=2162 IMAGE_PLAINMINEAMMO RGBA32 comp=4 14x14 lod=0
|
||||||
|
texnum=2163 IMAGE_GRENADEAMMO RGBA32 comp=4 14x18 lod=0
|
||||||
|
texnum=2164 IMAGE_MAGAMMO RGBA32 comp=7 5x15 lod=0
|
||||||
|
texnum=2165 IMAGE_GLAMMO RGBA32 comp=4 8x21 lod=0
|
||||||
|
texnum=2166 IMAGE_KNIFEAMMO RGBA32 comp=4 6x24 lod=0
|
||||||
|
texnum=2167 IMAGE_SHOTAMMO RGBA32 comp=4 6x20 lod=0
|
||||||
|
texnum=2231 IMAGE_9MMAMMO RGBA32 comp=4 5x12 lod=0
|
||||||
|
texnum=2232 IMAGE_RIFLEAMMO RGBA32 comp=4 5x28 lod=0
|
||||||
|
texnum=2233 IMAGE_GGAMMO RGBA32 comp=4 5x12 lod=0
|
||||||
|
texnum=2234 IMAGE_MINEAMMO RGBA32 comp=4 14x14 lod=0
|
||||||
|
texnum=2235 IMAGE_PROXAMMO RGBA32 comp=4 14x14 lod=0
|
||||||
|
texnum=2236 IMAGE_CROSSHAIR1 RGBA32 comp=4 32x32 lod=0
|
||||||
|
texnum=2237 IMAGE_CROSSHAIR2 RGBA32 comp=7 32x32 lod=0
|
||||||
|
texnum=2238 IMAGE_TIMEAMMO RGBA32 comp=4 14x14 lod=0
|
||||||
|
texnum=2430 _image2430_ID IA16 comp=4 64x32 lod=1
|
||||||
|
texnum=2510 _image2510_ID RGB15 comp=7 128x12 lod=2
|
||||||
|
texnum=2511 _image2511_ID RGB15 comp=8 128x12 lod=2
|
||||||
|
texnum=2512 _image2512_ID RGB15 comp=8 128x12 lod=2
|
||||||
|
texnum=2513 _image2513_ID RGB15 comp=6 128x12 lod=2
|
||||||
|
texnum=2514 _image2514_ID RGB15 comp=6 128x12 lod=2
|
||||||
|
texnum=2515 _image2515_ID RGB15 comp=6 128x12 lod=2
|
||||||
|
texnum=2516 _image2516_ID RGB15 comp=8 128x12 lod=2
|
||||||
|
texnum=2517 _image2517_ID RGB15 comp=8 128x12 lod=2
|
||||||
|
texnum=2518 _image2518_ID RGB15 comp=8 128x12 lod=2
|
||||||
|
texnum=2519 _image2519_ID RGB15 comp=8 128x12 lod=2
|
||||||
|
texnum=2520 _image2520_ID RGB15 comp=8 128x12 lod=2
|
||||||
|
texnum=2521 _image2521_ID RGB15 comp=8 128x12 lod=3
|
||||||
|
texnum=2522 _image2522_ID RGB15 comp=8 128x12 lod=3
|
||||||
|
texnum=2523 _image2523_ID RGB15 comp=8 128x12 lod=3
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""D219 offline probe: parse the non-zlib/zlib texture bitstream headers
|
||||||
|
straight out of the ROM images segment, replicating texLoad/texInflateNonZlib
|
||||||
|
header reads (src/game/image.c) so we can compare TEXCOMPMETHOD/TEXFORMAT for
|
||||||
|
IMAGE_FIRE_N vs muzzle-flash/ammo/flare/impact siblings WITHOUT running the game.
|
||||||
|
|
||||||
|
ROM: data/ge007.ntsc-final.z64
|
||||||
|
Images segment cart start 0x108F7DF0 (port/src/romassets_u.s) -> file offset 0x8F7DF0.
|
||||||
|
Offsets are cumulative sizes from assets/images.def (image_entries_load, no align).
|
||||||
|
"""
|
||||||
|
import re, sys
|
||||||
|
|
||||||
|
SEG = 0x8F7DF0
|
||||||
|
ROM = open('data/ge007.ntsc-final.z64','rb').read()
|
||||||
|
|
||||||
|
# ---- enum name -> index from image_externs.h ----
|
||||||
|
enummap = {}
|
||||||
|
_in_enum = False
|
||||||
|
_v = 0
|
||||||
|
for line in open('assets/image_externs.h', encoding='utf-8', errors='replace'):
|
||||||
|
if 'typedef enum IMAGEIDS' in line:
|
||||||
|
_in_enum = True; continue
|
||||||
|
if _in_enum and line.strip().startswith('}'):
|
||||||
|
break
|
||||||
|
m = re.match(r'\s*([A-Za-z_][A-Za-z0-9_]*)\s*,?\s*(//.*)?$', line)
|
||||||
|
if m and not line.strip().startswith('//'):
|
||||||
|
enummap[m.group(1)] = _v; _v += 1
|
||||||
|
|
||||||
|
# ---- parse images.def: field1 is a bare index or an enum name minus IMAGE_ ----
|
||||||
|
entries = []
|
||||||
|
for line in open('assets/images.def', encoding='utf-8', errors='replace'):
|
||||||
|
m = re.match(r'\s*IMAGE\(\s*([A-Za-z0-9_]+)\s*,\s*(0x[0-9A-Fa-f]+|\d+)\s*,', line)
|
||||||
|
if not m: continue
|
||||||
|
f1, sz = m.group(1), int(m.group(2), 0)
|
||||||
|
if f1.isdigit():
|
||||||
|
idx = int(f1); name = f"_image{idx}_ID"
|
||||||
|
else:
|
||||||
|
key = 'IMAGE_' + f1 if not f1.startswith('IMAGE_') else f1
|
||||||
|
idx = enummap.get(key)
|
||||||
|
name = key
|
||||||
|
if idx is None:
|
||||||
|
# named in images.def but left _imageN_ID in the decomp enum --
|
||||||
|
# trust position
|
||||||
|
idx = len(entries)
|
||||||
|
print(f"# note: {key} not in enum, using positional index {idx}", file=sys.stderr)
|
||||||
|
assert idx == len(entries), f"images.def out of order at {name} ({idx} vs {len(entries)})"
|
||||||
|
entries.append((name, sz))
|
||||||
|
print(f"# {len(entries)} image entries", file=sys.stderr)
|
||||||
|
|
||||||
|
# cumulative offsets (image_entries_load)
|
||||||
|
offs = []
|
||||||
|
o = 0
|
||||||
|
for name, sz in entries:
|
||||||
|
offs.append(o)
|
||||||
|
o += sz
|
||||||
|
total = o
|
||||||
|
print(f"# images segment total size per g_Textures: {total:#x} (segment room to 0xC00000-0x8F7DF0 = {0xC00000-SEG:#x})", file=sys.stderr)
|
||||||
|
|
||||||
|
class Bits:
|
||||||
|
def __init__(self, buf, pos):
|
||||||
|
self.buf = buf; self.pos = pos; self.bit = 0
|
||||||
|
def read(self, n):
|
||||||
|
v = 0
|
||||||
|
for _ in range(n):
|
||||||
|
byte = self.buf[self.pos]
|
||||||
|
v = (v << 1) | ((byte >> (7 - self.bit)) & 1)
|
||||||
|
self.bit += 1
|
||||||
|
if self.bit == 8:
|
||||||
|
self.bit = 0; self.pos += 1
|
||||||
|
return v
|
||||||
|
|
||||||
|
NAMES = {n:i for i,(n,_) in enumerate(entries)}
|
||||||
|
|
||||||
|
def dump(i, name):
|
||||||
|
off = offs[i]
|
||||||
|
base = SEG + off
|
||||||
|
b0 = ROM[base]
|
||||||
|
u, z, lod = (b0>>7)&1, (b0>>6)&1, b0 & 0x3f
|
||||||
|
out = [f"texnum={i:4d} {name:28s} off={off:#08x} byte0={b0:#04x} u={u} z={z} lod={lod}"]
|
||||||
|
if z:
|
||||||
|
# zlib path: format(8), numcolours(8)+1, palette... then per-image w/h
|
||||||
|
b = Bits(ROM, base+1)
|
||||||
|
fmt = b.read(8); nc = b.read(8)+1
|
||||||
|
pal = [b.read(16) for _ in range(nc)]
|
||||||
|
out.append(f" ZLIB format={fmt} numcolours={nc} pal0={pal[0]:#05x}")
|
||||||
|
w = b.read(8); h = b.read(8)
|
||||||
|
out.append(f" image0 {w}x{h} (zlib payload follows -- indices only)")
|
||||||
|
else:
|
||||||
|
b = Bits(ROM, base+1)
|
||||||
|
fmt = b.read(4); w = b.read(8); h = b.read(8); cm = b.read(4)
|
||||||
|
out.append(f" NONZLIB format={fmt} width={w} height={h} compmethod={cm}")
|
||||||
|
return ' '.join(out)
|
||||||
|
|
||||||
|
FMT = {0:'RGBA32',1:'RGBA16',2:'RGB24',3:'RGB15',4:'IA16',5:'IA8',6:'IA4',7:'I8',8:'I4',9:'RGBA16_CI8',10:'RGBA16_CI4',11:'IA16_CI8',12:'IA16_CI4'}
|
||||||
|
CM = {0:'UNCOMPRESSED0',1:'UNCOMPRESSED1',2:'HUFFMAN',3:'HUFFMANPERHCHANNEL',4:'RLE',5:'LOOKUP',6:'HUFFMANLOOKUP',7:'RLELOOKUP',8:'HUFFMANBLUR',9:'RLEBLUR'}
|
||||||
|
|
||||||
|
def dump2(i, name):
|
||||||
|
off = offs[i]
|
||||||
|
base = SEG + off
|
||||||
|
b0 = ROM[base]
|
||||||
|
z = (b0>>6)&1; lod = b0 & 0x3f
|
||||||
|
line = f"texnum={i:4d} {name:28s} off={off:#08x} byte0={b0:#04x} z={z} lod={lod}"
|
||||||
|
if z:
|
||||||
|
b = Bits(ROM, base+1)
|
||||||
|
fmt = b.read(8); nc = b.read(8)+1
|
||||||
|
pal = [b.read(16) for _ in range(nc)]
|
||||||
|
w = b.read(8); h = b.read(8)
|
||||||
|
line += f" ZLIB fmt={FMT.get(fmt,fmt)}({fmt}) ncols={nc} pal0={pal[0]:#05x} img0={w}x{h}"
|
||||||
|
else:
|
||||||
|
b = Bits(ROM, base+1)
|
||||||
|
fmt = b.read(4); w = b.read(8); h = b.read(8); cm = b.read(4)
|
||||||
|
line += f" NONZLIB fmt={FMT.get(fmt,fmt)}({fmt}) {w}x{h} comp={CM.get(cm,cm)}({cm})"
|
||||||
|
return line
|
||||||
|
|
||||||
|
targets = []
|
||||||
|
# fire + smoke particle range
|
||||||
|
for i in range(2084, 2116):
|
||||||
|
targets.append(i)
|
||||||
|
# flares
|
||||||
|
for n in ['IMAGE_FLAREORANGELINE','IMAGE_FLAREBLUEROUND','IMAGE_FLAREBLUELINE','IMAGE_FLAREWHITEROUND']:
|
||||||
|
targets.append(NAMES[n])
|
||||||
|
# ammo HUD icons
|
||||||
|
for n in ['IMAGE_ROCKETAMMO','IMAGE_PLAINMINEAMMO','IMAGE_GRENADEAMMO','IMAGE_MAGAMMO','IMAGE_GLAMMO','IMAGE_KNIFEAMMO','IMAGE_SHOTAMMO']:
|
||||||
|
targets.append(NAMES[n])
|
||||||
|
# impact sprites
|
||||||
|
for n in ['IMAGE_IMPACT1','IMAGE_IMPACT2','IMAGE_IMPACT3','IMAGE_IMPACT4','IMAGE_IMPACTMULTI','IMAGE_IMPACTREDBRICK2','IMAGE_IMPACTBRICK2','IMAGE_IMPACTBRICK3']:
|
||||||
|
targets.append(NAMES[n])
|
||||||
|
# smoke sprites
|
||||||
|
for n in ['IMAGE_SMOKE1','IMAGE_SMOKE2','IMAGE_SMOKEBALLS1']:
|
||||||
|
targets.append(NAMES[n])
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
for i in targets:
|
||||||
|
if i in seen or i >= len(entries): continue
|
||||||
|
seen.add(i)
|
||||||
|
print(dump2(i, entries[i][0]))
|
||||||
+89
-22
@@ -10,6 +10,40 @@
|
|||||||
|
|
||||||
#define TEX_ALPHA_WEIGHT 961
|
#define TEX_ALPHA_WEIGHT 961
|
||||||
|
|
||||||
|
/* D219 (M-115, see docs/dev/findings.md): the two fast3d importer families
|
||||||
|
* disagree about the byte order of the texpool buffer, and the split is keyed
|
||||||
|
* on the GBI texture SIZE -- which corresponds 1:1 to the pixel format
|
||||||
|
* decoded at each call site below:
|
||||||
|
*
|
||||||
|
* G_IM_SIZ_16b (RGBA16, RGB15-via-RGBA, IA16): import_texture_rgba16 /
|
||||||
|
* import_texture_ia16 read the pool as manually big-endian bytes
|
||||||
|
* ((addr[0]<<8)|addr[1]; intensity=addr[0], alpha=addr[1]). A native u16
|
||||||
|
* store on this little-endian host is therefore read back byte-swapped,
|
||||||
|
* so the decoder must store bswap16(value). This is what IMAGE_FIRE_0..14
|
||||||
|
* (RGBA16) needed: without it, fire particles render blue/purple (M-87/
|
||||||
|
* M-108); with it, GE_TEXDUMP PPM captures went from B-dominant to
|
||||||
|
* R-dominant with a clean fire animation (M-113).
|
||||||
|
*
|
||||||
|
* G_IM_SIZ_32b (RGBA32, RGB24): import_texture_rgba32 does PD_BE32() on a
|
||||||
|
* NATIVE u32 load -- it expects the pool to hold native-order words. A
|
||||||
|
* store-time swap inverts those colors instead (M-114 regression: muzzle
|
||||||
|
* flash blue, ammo HUD pink).
|
||||||
|
*
|
||||||
|
* compmethod is NOT a distinguisher (the fire frames span LOOKUP/RLE/
|
||||||
|
* HUFFMANBLUR/RLELOOKUP); format is, and every call site already routes by
|
||||||
|
* TEXFORMAT_*, so the scoping lives here. Offline ROM census
|
||||||
|
* (scratch/d219_census.txt) confirms the non-zlib 16-bit wide-pixel images
|
||||||
|
* are exactly IMAGE_FIRE_0..14 + texnum 1198-1201 (RGBA16), 2430 (IA16),
|
||||||
|
* 2510-2523 (RGB15); every ammo/flare/crosshair/muzzle-flash wide-pixel
|
||||||
|
* image is RGBA32. The blanket M-113 swap hit both families and caused
|
||||||
|
* M-114; this format-scoped version fixes the 16-bit family only. */
|
||||||
|
#ifdef PORT
|
||||||
|
#define PORT_PIXEL16(x) __builtin_bswap16((unsigned short)(x))
|
||||||
|
#else
|
||||||
|
#define PORT_PIXEL16(x) (x)
|
||||||
|
#endif
|
||||||
|
#define PORT_PIXEL32(x) (x) /* 32b importer expects native words -- see above */
|
||||||
|
|
||||||
// bss
|
// bss
|
||||||
//8008C720
|
//8008C720
|
||||||
#ifdef PORT
|
#ifdef PORT
|
||||||
@@ -924,6 +958,20 @@ s32 texInflateNonZlib(u8 *src, u8 *dst, s32 arg2, s32 forcenumimages, struct tex
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PORT
|
||||||
|
/* D219 (docs/dev/findings.md): diagnostic-only, inert unless
|
||||||
|
* GE_D219RAW is set. Log the bitstream header for every non-zlib
|
||||||
|
* wide-pixel image (RGBA32/RGBA16/RGB24/RGB15/IA16) so a session can
|
||||||
|
* cross-reference any suspect texture's format/compmethod against the
|
||||||
|
* offline ROM census (scratch/d219_census.txt) without a texnum-range
|
||||||
|
* filter. No behavior change -- print only. */
|
||||||
|
if (getenv("GE_D219RAW") && format <= TEXFORMAT_IA16)
|
||||||
|
{
|
||||||
|
osSyncPrintf("D219RAW texnum=%d format=%d width=%d height=%d compmethod=%d i=%d\n",
|
||||||
|
(int)g_TexNumToLoad, (int)format, (int)width, (int)height, (int)compmethod, (int)i);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
switch (compmethod)
|
switch (compmethod)
|
||||||
{
|
{
|
||||||
case TEXCOMPMETHOD_UNCOMPRESSED0:
|
case TEXCOMPMETHOD_UNCOMPRESSED0:
|
||||||
@@ -1018,6 +1066,24 @@ s32 texInflateNonZlib(u8 *src, u8 *dst, s32 arg2, s32 forcenumimages, struct tex
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PORT
|
||||||
|
/* D219: dump the first row of decoded texels exactly as stored in the
|
||||||
|
* pool (post-PORT_PIXEL16, i.e. byte-swapped on PORT for the 16-bit
|
||||||
|
* family -- the bytes import_texture_rgba16/ia16 read back). Covers
|
||||||
|
* every 16-bit wide-pixel format, no texnum filter. Diagnostic-only. */
|
||||||
|
if (getenv("GE_D219RAW") &&
|
||||||
|
(format == TEXFORMAT_RGBA16 || format == TEXFORMAT_RGB15 || format == TEXFORMAT_IA16))
|
||||||
|
{
|
||||||
|
u16 *p16 = (u16 *)&dst[totalbytesout];
|
||||||
|
s32 dbgk;
|
||||||
|
osSyncPrintf("D219RAW decoded texnum=%d format=%d compmethod=%d width=%d height=%d pixels:",
|
||||||
|
(int)g_TexNumToLoad, (int)format, (int)compmethod, (int)width, (int)height);
|
||||||
|
for (dbgk = 0; dbgk < 12 && dbgk < width * height; dbgk++)
|
||||||
|
osSyncPrintf(" %04x", (unsigned)p16[dbgk]);
|
||||||
|
osSyncPrintf("\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (arg2 == 1 && forcenumimages > 0)
|
if (arg2 == 1 && forcenumimages > 0)
|
||||||
{
|
{
|
||||||
texSwapAltRowBytes(&dst[totalbytesout], width, height, format);
|
texSwapAltRowBytes(&dst[totalbytesout], width, height, format);
|
||||||
@@ -1683,8 +1749,9 @@ s32 texReadUncompressed(u8 *dst, s32 width, s32 height, s32 format)
|
|||||||
case TEXFORMAT_RGBA32:
|
case TEXFORMAT_RGBA32:
|
||||||
for (y = 0; y < height; y++) {
|
for (y = 0; y < height; y++) {
|
||||||
for (x = 0; x < width; x++) {
|
for (x = 0; x < width; x++) {
|
||||||
dst32[x] = texReadBits(16) << 16;
|
u32 px = texReadBits(16) << 16;
|
||||||
dst32[x] |= texReadBits(16);
|
px |= texReadBits(16);
|
||||||
|
dst32[x] = PORT_PIXEL32(px);
|
||||||
}
|
}
|
||||||
|
|
||||||
dst32 += (width + 3) & 0xffc;
|
dst32 += (width + 3) & 0xffc;
|
||||||
@@ -1694,7 +1761,7 @@ s32 texReadUncompressed(u8 *dst, s32 width, s32 height, s32 format)
|
|||||||
case TEXFORMAT_RGB24:
|
case TEXFORMAT_RGB24:
|
||||||
for (y = 0; y < height; y++) {
|
for (y = 0; y < height; y++) {
|
||||||
for (x = 0; x < width; x++) {
|
for (x = 0; x < width; x++) {
|
||||||
dst32[x] = texReadBits(24) << 8 | 0xff;
|
dst32[x] = PORT_PIXEL32(texReadBits(24) << 8 | 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
dst32 += (width + 3) & 0xffc;
|
dst32 += (width + 3) & 0xffc;
|
||||||
@@ -1705,7 +1772,7 @@ s32 texReadUncompressed(u8 *dst, s32 width, s32 height, s32 format)
|
|||||||
case TEXFORMAT_IA16:
|
case TEXFORMAT_IA16:
|
||||||
for (y = 0; y < height; y++) {
|
for (y = 0; y < height; y++) {
|
||||||
for (x = 0; x < width; x++) {
|
for (x = 0; x < width; x++) {
|
||||||
dst16[x] = texReadBits(16);
|
dst16[x] = PORT_PIXEL16(texReadBits(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
dst16 += (width + 3) & 0xffc;
|
dst16 += (width + 3) & 0xffc;
|
||||||
@@ -1715,7 +1782,7 @@ s32 texReadUncompressed(u8 *dst, s32 width, s32 height, s32 format)
|
|||||||
case TEXFORMAT_RGB15:
|
case TEXFORMAT_RGB15:
|
||||||
for (y = 0; y < height; y++) {
|
for (y = 0; y < height; y++) {
|
||||||
for (x = 0; x < width; x++) {
|
for (x = 0; x < width; x++) {
|
||||||
dst16[x] = texReadBits(15) << 1 | 1;
|
dst16[x] = PORT_PIXEL16(texReadBits(15) << 1 | 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
dst16 += (width + 3) & 0xffc;
|
dst16 += (width + 3) & 0xffc;
|
||||||
@@ -1776,7 +1843,7 @@ s32 texChannelsToPixels(u8 *src, s32 width, s32 height, u8 *dst, s32 format)
|
|||||||
{
|
{
|
||||||
for (x = 0; x < width; x++)
|
for (x = 0; x < width; x++)
|
||||||
{
|
{
|
||||||
dst32[x] = src[pos] << 24 | src[pos + mult] << 16 | src[pos + mult * 2] << 8 | src[pos + mult * 3];
|
dst32[x] = PORT_PIXEL32(src[pos] << 24 | src[pos + mult] << 16 | src[pos + mult * 2] << 8 | src[pos + mult * 3]);
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1790,7 +1857,7 @@ s32 texChannelsToPixels(u8 *src, s32 width, s32 height, u8 *dst, s32 format)
|
|||||||
{
|
{
|
||||||
for (x = 0; x < width; x++)
|
for (x = 0; x < width; x++)
|
||||||
{
|
{
|
||||||
dst32[x] = src[pos] << 24 | src[pos + mult] << 16 | src[pos + mult * 2] << 8 | 0xff;
|
dst32[x] = PORT_PIXEL32(src[pos] << 24 | src[pos + mult] << 16 | src[pos + mult * 2] << 8 | 0xff);
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1804,7 +1871,7 @@ s32 texChannelsToPixels(u8 *src, s32 width, s32 height, u8 *dst, s32 format)
|
|||||||
{
|
{
|
||||||
for (x = 0; x < width; x++)
|
for (x = 0; x < width; x++)
|
||||||
{
|
{
|
||||||
dst16[x] = src[pos] << 11 | src[pos + mult] << 6 | src[pos + mult * 2] << 1 | src[pos + mult * 3];
|
dst16[x] = PORT_PIXEL16(src[pos] << 11 | src[pos + mult] << 6 | src[pos + mult * 2] << 1 | src[pos + mult * 3]);
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1818,7 +1885,7 @@ s32 texChannelsToPixels(u8 *src, s32 width, s32 height, u8 *dst, s32 format)
|
|||||||
{
|
{
|
||||||
for (x = 0; x < width; x++)
|
for (x = 0; x < width; x++)
|
||||||
{
|
{
|
||||||
dst16[x] = src[pos] << 8 | src[pos + mult];
|
dst16[x] = PORT_PIXEL16(src[pos] << 8 | src[pos + mult]);
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1832,7 +1899,7 @@ s32 texChannelsToPixels(u8 *src, s32 width, s32 height, u8 *dst, s32 format)
|
|||||||
{
|
{
|
||||||
for (x = 0; x < width; x++)
|
for (x = 0; x < width; x++)
|
||||||
{
|
{
|
||||||
dst16[x] = src[pos] << 11 | src[pos + mult] << 6 | src[pos + mult * 2] << 1 | 1;
|
dst16[x] = PORT_PIXEL16(src[pos] << 11 | src[pos + mult] << 6 | src[pos + mult * 2] << 1 | 1);
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1940,7 +2007,7 @@ s32 texInflateLookup(s32 width, s32 height, u8 *dst, u8 *lookup, s32 numcolours,
|
|||||||
case TEXFORMAT_RGBA32:
|
case TEXFORMAT_RGBA32:
|
||||||
for (y = 0; y < height; y++) {
|
for (y = 0; y < height; y++) {
|
||||||
for (x = 0; x < width; x++) {
|
for (x = 0; x < width; x++) {
|
||||||
dst32[x] = lookup32[texReadBits(bitspercolour)];
|
dst32[x] = PORT_PIXEL32(lookup32[texReadBits(bitspercolour)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
dst32 += (width + 3) & 0xffc;
|
dst32 += (width + 3) & 0xffc;
|
||||||
@@ -1950,7 +2017,7 @@ s32 texInflateLookup(s32 width, s32 height, u8 *dst, u8 *lookup, s32 numcolours,
|
|||||||
case TEXFORMAT_RGB24:
|
case TEXFORMAT_RGB24:
|
||||||
for (y = 0; y < height; y++) {
|
for (y = 0; y < height; y++) {
|
||||||
for (x = 0; x < width; x++) {
|
for (x = 0; x < width; x++) {
|
||||||
dst32[x] = lookup32[texReadBits(bitspercolour)] << 8;
|
dst32[x] = PORT_PIXEL32(lookup32[texReadBits(bitspercolour)] << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
dst32 += (width + 3) & 0xffc;
|
dst32 += (width + 3) & 0xffc;
|
||||||
@@ -1961,7 +2028,7 @@ s32 texInflateLookup(s32 width, s32 height, u8 *dst, u8 *lookup, s32 numcolours,
|
|||||||
case TEXFORMAT_IA16:
|
case TEXFORMAT_IA16:
|
||||||
for (y = 0; y < height; y++) {
|
for (y = 0; y < height; y++) {
|
||||||
for (x = 0; x < width; x++) {
|
for (x = 0; x < width; x++) {
|
||||||
dst16[x] = lookup16[texReadBits(bitspercolour)];
|
dst16[x] = PORT_PIXEL16(lookup16[texReadBits(bitspercolour)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
dst16 += (width + 3) & 0xffc;
|
dst16 += (width + 3) & 0xffc;
|
||||||
@@ -1971,7 +2038,7 @@ s32 texInflateLookup(s32 width, s32 height, u8 *dst, u8 *lookup, s32 numcolours,
|
|||||||
case TEXFORMAT_RGB15:
|
case TEXFORMAT_RGB15:
|
||||||
for (y = 0; y < height; y++) {
|
for (y = 0; y < height; y++) {
|
||||||
for (x = 0; x < width; x++) {
|
for (x = 0; x < width; x++) {
|
||||||
dst16[x] = lookup16[texReadBits(bitspercolour)] << 1 | 1;
|
dst16[x] = PORT_PIXEL16(lookup16[texReadBits(bitspercolour)] << 1 | 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
dst16 += (width + 3) & 0xffc;
|
dst16 += (width + 3) & 0xffc;
|
||||||
@@ -2060,11 +2127,11 @@ s32 texInflateLookupFromBuffer(u8 *src, s32 width, s32 height, u8 *dst, u8 *look
|
|||||||
{
|
{
|
||||||
if (numcolours <= 256)
|
if (numcolours <= 256)
|
||||||
{
|
{
|
||||||
dst32[x] = lookup32[src8[x]];
|
dst32[x] = PORT_PIXEL32(lookup32[src8[x]]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dst32[x] = lookup32[src16[x]];
|
dst32[x] = PORT_PIXEL32(lookup32[src16[x]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2082,11 +2149,11 @@ s32 texInflateLookupFromBuffer(u8 *src, s32 width, s32 height, u8 *dst, u8 *look
|
|||||||
{
|
{
|
||||||
if (numcolours <= 256)
|
if (numcolours <= 256)
|
||||||
{
|
{
|
||||||
dst32[x] = lookup32[src8[x]] << 8 | 0xff;
|
dst32[x] = PORT_PIXEL32(lookup32[src8[x]] << 8 | 0xff);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dst32[x] = lookup32[src16[x]] << 8 | 0xff;
|
dst32[x] = PORT_PIXEL32(lookup32[src16[x]] << 8 | 0xff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2105,11 +2172,11 @@ s32 texInflateLookupFromBuffer(u8 *src, s32 width, s32 height, u8 *dst, u8 *look
|
|||||||
{
|
{
|
||||||
if (numcolours <= 256)
|
if (numcolours <= 256)
|
||||||
{
|
{
|
||||||
dst16[x] = lookup16[src8[x]];
|
dst16[x] = PORT_PIXEL16(lookup16[src8[x]]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dst16[x] = lookup16[src16[x]];
|
dst16[x] = PORT_PIXEL16(lookup16[src16[x]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2127,11 +2194,11 @@ s32 texInflateLookupFromBuffer(u8 *src, s32 width, s32 height, u8 *dst, u8 *look
|
|||||||
{
|
{
|
||||||
if (numcolours <= 256)
|
if (numcolours <= 256)
|
||||||
{
|
{
|
||||||
dst16[x] = lookup16[src8[x]] << 1 | 1;
|
dst16[x] = PORT_PIXEL16(lookup16[src8[x]] << 1 | 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dst16[x] = lookup16[src16[x]] << 1 | 1;
|
dst16[x] = PORT_PIXEL16(lookup16[src16[x]] << 1 | 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user