Files
llamamod/notes/mod-mirror.md
T

71 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mod "Mirror mode" (`mods.mirror.MirrorMod`, id `mirror`, GAMEPLAY)
Mirrors every playable level left-right (x → W − x on the W×H tile grid), like the mirror mode of
racing games. Controls are unchanged (the world is mirrored, not the input).
Code: `mods/mirror/Mirror.java` (pure byte transform), `MirrorMod.java`; test `MirrorTest`.
## Options
| Key | Default | Meaning |
|---|---|---|
| `worlds` | `all` | `all` or world numbers, e.g. `1,3` (9 = TestLevel / Test World) |
| `readableTextures` | false | on: face flag 0x20 (mirror U) is toggled so writing reads normally, but textures that continue across faces get seams (fountain water rim in the village); off: true mirror image |
| `objects` | false | also mirror rigid STPC object models (verts, face planes, collision); skinned characters never |
`order()` = 75: after `testworld` (50, so the Test World is mirrored too) and before
`debug-wireframe` (100, so the overlay is built from the mirrored collision and lines up).
Files: `wads/t1l<w>m<c>.wad` only (front end `t0*`, map `t6l6*` stay).
## What is transformed (all chunk sizes unchanged → STPC absolute offsets stay valid)
| Data | Transform |
|---|---|
| TRAK models (all, shared by tiles) | vertex x, normal x, face plane nx negated; bbox centre x negated, corner pairs (−x/+x) swapped + x negated; collision: i8 nx of all 5 planes negated (d unchanged; −128 → 127) |
| Face winding | **kept**. groove.exe sets `D3DRENDERSTATE_CULLMODE = D3DCULL_NONE` once (decomp ~line 31587, Device3 vtable +0x58) and culls in software with the stored face plane. Reordering vertices would break the fixed UV corner patterns (pattern 0x800+0x10 has no odd permutation that keeps the UVs). |
| MAP tiles (float A) | x → W − x, rot → (4096 − rot) & 0xfff |
| MAP tiles (fixed B) | x → W·4096 − x, rot as above (A and B stay exactly consistent) |
| Region grid, tile-swap block (cell x) | columns mirrored (cell → W − 1 − cell) |
| Strats | x → W·4096 − 1 − x (keeps `floor(x)` in the mirrored cell: LevelDoor compares cells), ry and rz → −angle, rx kept |
| Waypoints / camera nodes | same as strats (x, ry, rz) |
| 90-byte "env zones" | really **teleport/respawn entries** (FUN_0054d560 / FUN_0054de00): f0..2 = trigger cell (x,y,z), f3..5 = target position (fixed), f7 bits 4..11 = facing angle (bits 0/1 = y compare flags). Mirrored: cell x, position x, angle. Default entry (pos 1,1,1) untouched. f11 (+0x2c) = sky model yaw offset, kept. |
| LGHT (FUN_0042c180) | u32 n; per light u8 type, r, g, b; type 1 directional: 3 f32 → x negated; types 2/4 point: 3 f32 + 2 f32 + u8 → x → W − x |
| STPC (option) | rigid models (nBones = 0) incl. WFPC&0x100 extra models, same as TRAK models |
| Vertex colours | unchanged (vertex order unchanged) |
Rotation rule: mirroring x conjugates rotations; about y and z they become −angle, about x
unchanged. 0 = facing +z, so facing (sin a, cos a) → (−sin a, cos a) = angle −a.
Mirroring twice gives the original WAD byte for byte (except LGHT float rounding and −128 normals) –
checked in `MirrorTest`, together with: every render vertex and collision corner of every tile of
t1l1m001 lands exactly at the mirrored world position, region per tile cell preserved.
## Not mirrored / known limits
* Strat bytecode is untouched: hard-coded offsets/directions in scripts (if any) are not mirrored.
Parameter-pool values are not interpreted (could contain coordinates/angles for some types).
* Object models stay unmirrored by default (placed and turned mirrored, so their collision matches
their own look). Skinned/animated characters are never mirrored (bone matrices and compressed
vertex deltas of the anim sets would need conjugation).
* Sky/background models (STPC, drawn camera-relative) are not mirrored.
* Attract-mode demos (`*.DEM`, recorded input) desync in mirrored levels (harmless).
* Textures are mirrored by default (true mirror); `readableTextures` flips them back per face,
which breaks textures that are laid out across several faces.
* **River intro fly-by (t1l3m001)**: the camera partly shows an empty grey view and the intro
dialog starts earlier than in the original. No crash; after the intro the river plays normally.
Most likely a hard-coded lateral camera offset in strat code (camera ends up in a cell whose
visibility zone hides the river). The village fly-by is correct.
## In-game verification (apptest, display :7, 2026-11-06)
* **Village t1l1m001**: same key sequence on original and mirrored – a perfect mirror image
(house/fountain swapped sides), llama stands on the ground, walls/fountain rim block, coins
collect (counter matches the original run), Pacha dialog triggers, camera rotate (A) works,
intro fly-by (not skipped) correct. With `debug-wireframe` + `objects=true`: overlay lines up
with the mirrored geometry, no crash.
* **River t1l3m001**: loads, raft/tree floats on the mirrored river, auto-scroll and steering work,
coins collected, wireframe lines up; fly-by see limits.
* **City t1l6m001**: loads, intro cutscene with Pacha (cutscene cameras) fine, walls and crates
block, wireframe lines up.
* **Test World** (`testworld,mirror`, world 9): mirrored island (parkour stairs on the right),
walking/jumping fine.
* Images: `notes/img/mods/mirror_01_village.jpg` (original vs. mirror), `mirror_02_wireframe.jpg`
(village + city with the debug wireframe), `mirror_03_river_testworld.jpg`.