Symptom
With `GE_INJECT_PERSPECTIVE=1`, perspective math produces NaN for every triangle:
```
[drawTri #1] visible=1 v0=(23.3,120.0,1.011) v1=(inf,-inf,-inf) v2=(nan,nan,-inf)
```
Root cause
Perspective formula has `m[2][3] = -1` so `w_out = -z_in`. Vertices with `z=0` produce `w=0` → division by zero in clip-space → NaN.
GE's vertex data has many vertices at z=0 (flat-floor scenes). Without modelview translation, identity MV preserves z=0 → NaN.
Try next
- Inject modelview with `translate(0, 0, -K)` where K > max world-space z (try K=2000 or 5000)
- Use `GE_MV_TZ=2000` env var (already implemented in `src/main/rt64_render_context.cpp`)
- Tune `GE_PERSP_NEAR`, `GE_PERSP_FAR` to bracket the translated z range
- Verify malformed-matrix detector at `rt64_rsp.cpp::matrix` accepts the perspective (m[3][3]=0, m[2][3]=-1 case)
Files
- `src/main/rt64_render_context.cpp` (perspective injection at line ~470)
- `src/main/rt64_render_context.cpp` (GE_MV_TZ at modelview injection)
- `lib/rt64/src/hle/rt64_rsp.cpp::matrix` (malformed detector at line 195)
Acceptance criteria
With `GE_INJECT_PERSPECTIVE=1 GE_MV_TZ=N` for some N, drawTri produces no NaN coords AND screen Y values vary by triangle (depth-based projection working).
Symptom
With `GE_INJECT_PERSPECTIVE=1`, perspective math produces NaN for every triangle:
```
[drawTri #1] visible=1 v0=(23.3,120.0,1.011) v1=(inf,-inf,-inf) v2=(nan,nan,-inf)
```
Root cause
Perspective formula has `m[2][3] = -1` so `w_out = -z_in`. Vertices with `z=0` produce `w=0` → division by zero in clip-space → NaN.
GE's vertex data has many vertices at z=0 (flat-floor scenes). Without modelview translation, identity MV preserves z=0 → NaN.
Try next
Files
Acceptance criteria
With `GE_INJECT_PERSPECTIVE=1 GE_MV_TZ=N` for some N, drawTri produces no NaN coords AND screen Y values vary by triangle (depth-based projection working).