Cloud tile is s_skywaterimages[0] = 64x64 IA8, G_TX_WRAP -> it tiles. GE's screen-space textured-quad idiom is tc = width<<5 (glass2.c:678), so skyPortRenderPoly's *32.0f scale was correct; the bug was (s16) overflow on the +/-20k-texel horizon S/T (unk20/unk24 are texel coords, not fractions). Fix: fold every vertex of one primitive by the same multiple of the 64-texel tile period before scaling. Inter-vertex deltas (hence the interpolated cloud texture) are preserved; magnitudes stay in s16 range. Exact for any triangle whose S/T span is < ~1000 texels -- everything except tris straddling the horizon line, which are Defect 2 (adaptive tessellation). sky.c #ifdef PORT only; N64 path unchanged. Builds clean; bunker1 + statue verify PASS, no crash. UNVERIFIED VISUALLY -- owed: one -level_22 screenshot (D176a-SKY-NOTES.md 'Verification ask'). Also: rebased onto main (porting-notes.md conflict resolved).
6.9 KiB
D176(a) Sky — consolidated research notes (read-only)
Companion to GRAPHICS-BACKLOG.md D176(a) and findings.md M-37/M-47.
Purpose: make the next implementation session mechanical. No code changed by
this doc.
State of play
- Root cause (M-37):
skyRenderTri/skyRenderFullemit geometry only asG_RDPHALF_*immediates;gfx_pc.cpp:2901-2903deliberately no-ops them (same in the PD port) → solid black sky on every cloud-sky level. Clean headless repro:-level_22(Statue), frame ~360. - M-47 draft (Path B) —
scratchpad/sky.patch:#ifdef PORThooks in both functions re-emit the sky as ordinary screen-space GBI viaskyPortRenderPoly(ortho pixel projection +gSPVertex/triangles). Geometry reaches GL and textures (top-half luma 0.1 → ~174 on-level_22). Two open defects (below). Draft, not shippable. - Path A (decode RDPHALF in fast3d) is the backlog's original suggestion; the wire format is now fully decoded (next section), so it is a viable fallback if Path B's units can't be pinned.
RDPHALF wire format (decoded from src/game/sky.c + include/PR/gbi.h)
- Opcodes (
gbi.h:102-164,216-223):G_RDPHALF_1 = 0xe1,G_RDPHALF_2 = 0xf1(final word of a stream),G_RDPHALF_CONT(=G_IMMFIRST-13). Tri ops:G_TRI_FILL 0xc8,G_TRI_FILL_ZBUFF 0xc9,G_TRI_SHADE_TXTR 0xce,G_TRI_SHADE_TXTR_ZBUFF 0xcf. - All payload values are S15.1 fixed point — converter is
sub_GAME_7F094298(sky.c:218):clamp(x, ±32767.9) × 65536, u32. - Packing: each
(G_RDPHALF_1, G_RDPHALF_CONT)pair carries four halfwords = two S15.1 values:valueA = (w1.hi16, w2.lo16),valueB = (w1.lo16, w2.hi16). Multi-value groups are streamed in two passes — all high halves first, then all low halves (see the explicit(x & 0xffff0000) | (y & 0xffff0000) >> 16packing atsky.c:1854-1870). skyRenderTriheader (sky.c:1725-1728): first word =(op << 24) | (winding/sign flag bit23) | lo16(first value); the stream then carries screen-space positions (unk28×0.25= pixel X,unk2c= Y×4 family) plus per-vertex attributes. The RSP ucode reassembles and DMAs a real edge-walked RDP tri command (per the M-46 comment in sky.patch).- Vertex attribute vectors are 8 components (
sky.c:1759-1776):(r, g, b, a, S·w′, T·w′, 32767·w′, z)with perspective weightw′ = unk34 × min(unk0c)/2— i.e. color + perspective-corrected S/T + depth.SkyRelated38fields:unk28 = screenX×4,unk2c = screenY×4,unk30 = z (0..0x7fff),unk34 = 1/w,unk20/unk24 = S/T. - GE emits RDPHALF nowhere except sky.c — the command surface is closed and small.
Defect 1 — texcoord shear (clouds smeared into streaks)
skyPortRenderPoly uses tc = unk20 × 32.0f — a magic number.
M-82 update — the scale is right, the magnitude is the bug
- Cloud texture is
s_skywaterimages[0]=IMAGE_CLOUDS_GRAYSCALE,0x40 × 0x40(64×64),G_IM_FMT_IA/G_IM_SIZ_8b,G_TX_WRAP/G_TX_WRAP(assets/oddtextures.c:646). Not a fraction-indexed texture — it tiles. - GE's own screen-space textured-quad idiom is
tc = width << 5(src/game/glass2.c:678,689—sImageTableEntry.width << 5/.height << 5).<< 5= ×32 = the S10.5 texel fixed-point fast3d expects, and it dividestcbytex_widthbefore GL_REPEAT. So the patch's× 32.0fis the correct scale — providedunk20/unk24are already in texel units (they are: the cloud-tiling math insub_GAME_7F097388/CloudRepeat(skyheight)=10000produces texel coords in the thousands, and M-47 measuredunk20reaching ±20 000 near the horizon). - The bug:
(s16)(20000 * 32)overflows.Vtx.tciss16(±32767); a horizon vertex needstc≈ 640 000. It wraps/saturates → the streak. - Fix (no fast3d change, no grid needed for the near/mid sky): phase-fold
every vertex of one primitive by the same multiple of the tile period
(64 texels), so inter-vertex deltas are preserved but magnitudes stay in
s16 range:
This is exact wherever the S/T span across a single triangle is < ~1000 texels (s16 range ÷ 32). It holds for the corner-quad tessellation the current patch emits everywhere except triangles that straddle the horizon line, where the span diverges — those still need Defect-2's finer/adaptive tessellation. Land the fold first (fixes most of the frame), keep the grid as the horizon-band follow-up.
s32 foldS = (s32)floorf(vmin_unk20 / 64.0f) * 64; // vmin over this prim's verts s32 foldT = (s32)floorf(vmin_unk24 / 64.0f) * 64; tc[0] = (s16)((v[i]->unk20 - foldS) * 32.0f); tc[1] = (s16)((v[i]->unk24 - foldT) * 32.0f);
Defect 2 — vertical coverage (band fills only top ~25%)
Leading hypothesis, from reading the N64 path: the quad is not a plain
corner quad. The N64 stream's four vertices are perspective-interpolated
points computed in the big interpolation block (sky.c:1797-1813, the
sp210/sp290/sp2b0/sp230 vectors), not the raw TL/TR/BL/BR corners that
Path B's hook passes through. If those interpolated y-values differ from the
corner ys (they will, wherever w varies across the quad), the re-emitted
quad lands short.
Next steps (one focused session):
- Per-call probe on
skyPortRenderPoly: log nverts + each vertex's (x, y) + the tri op-header word; A/B against N64 reference footage of-level_22/-level_36. - If confirmed, either (a) replicate the interpolation block under PORT and emit the 4 computed vertices, or (b) drop to Path A with the decoder spec above (the ucode's own reassembly is then irrelevant — we decode the same values directly).
Recommendation
Finish Path B first — geometry already works; both defects are unit/ vertex-set questions answerable in one render-iterate session. Keep Path A as fallback; it is now fully specified by this doc if B stalls.
Verification ask (M-82 — for the next session with a display)
The Defect-1 phase-fold is committed to PR #18 but unverified visually (this session had no display). One check settles it:
./build-pc/ge007.x86_64.exe -level_22(Statue, night). At the intro the camera faces the horizon within a few seconds.- Look at the upper half of the frame:
- Black → Path B still not emitting (regression from the rebase —
check
skyPortRenderPolyis reached;GE_D176=1). - Clouds, but smeared into horizontal streaks → the fold didn't take;
re-check
unk20/unk24really are texels (probe their range). - Recognisable tiled cloud texture, correct near/mid, ragged or missing right at the horizon line → fold works; that ragged band is Defect 2 → adaptive tessellation is the next task.
- Looks right → close Defect 1, mark PR #18 ready pending a
-level_36/-29/Dam-exterior spot check.
- Black → Path B still not emitting (regression from the rebase —
check
- A
GE_PCDUMP="330-390:30"capture of that run attached to PR #18 is enough for a remote reviewer to make the same call.