translated the remaining german notes, python comments, help and messages to english (in-game strings, parsed report lines and cli values stay german)
This commit is contained in:
1 parent
86fe26d786
commit
526f6072f6
36 files changed
+1553
-1544
No files matched your search
+168
-168
@@ -1,203 +1,203 @@
|
||||
# Level-Geometrie (TRAK / MAP / STPC / TEXT) – Ein Königreich für ein Lama (PC)
|
||||
# Level geometry (TRAK / MAP / STPC / TEXT) – Ein Königreich für ein Lama (PC)
|
||||
|
||||
Stand: 2026-11-06. Quelle: groove.exe (Ghidra-Dekompilat `re/groove_decomp.c`, Capstone) +
|
||||
Datenanalyse aller 34 Level-WADs. Tools: `tools/geomlib.py`, `tools/geom_export.py`,
|
||||
As of: 2026-11-06. Source: groove.exe (Ghidra decompilation `re/groove_decomp.c`, Capstone) +
|
||||
data analysis of all 34 level WADs. Tools: `tools/geomlib.py`, `tools/geom_export.py`,
|
||||
`tools/geom_import.py`, `tools/geom_demo.py`, `tools/disx86.py`.
|
||||
Konfidenz: **[sicher]** = aus Code + Daten verifiziert (Round-Trip byte-identisch / Rendering),
|
||||
**[wahrsch.]** = aus Daten plausibel, **[?]** = unbekannt.
|
||||
Confidence: **[certain]** = verified from code + data (byte-identical round trip / rendering),
|
||||
**[likely]** = plausible from data, **[?]** = unknown.
|
||||
|
||||
## 1. Überblick: welcher Chunk ist was
|
||||
## 1. Overview: which chunk is what
|
||||
|
||||
Chunk-Dispatcher: `FUN_00558xxx` bei VA 0x55899e (Vergleich auf Tag-Konstanten, Tag als u32 LE
|
||||
gelesen, z. B. 'CPTS' im File = 0x53545043).
|
||||
Chunk dispatcher: `FUN_00558xxx` at VA 0x55899e (compares against tag constants, tag read as u32 LE,
|
||||
e.g. 'CPTS' in the file = 0x53545043).
|
||||
|
||||
| Chunk | Loader (VA) | Inhalt |
|
||||
| Chunk | Loader (VA) | Contents |
|
||||
|---|---|---|
|
||||
| WFPC | inline → `DAT_006da330` | u32 Feature-Flags des Levels (steuern MAP/STPC-Aufbau) **[sicher]** |
|
||||
| TEXT | 0x4067b0 / 0x406d30 | Texturseiten (256x256, 16 Bit) + Texturtabelle (UV-Rechtecke) **[sicher]** |
|
||||
| SMPC / AMPC / SRPC | 0x558d30 / 0x558d70 / 0x545350 | Sound (SMPC: 'cvg ' VAG-Samples; AMPC: 'ambi'/'VABp'; SRPC Sound-Referenzen) – **keine Geometrie** |
|
||||
| **TRAK** | 0x42aac0 → 0x5563f0 | **Welt-Geometrie**: Kachel-Modelle (Render-Dreiecke + Kollisionspolygone) → globales Array `DAT_005846ec` **[sicher]** |
|
||||
| **STPC** | 0x42ab50 | Objekt-("Strat")-Modelle (gleiches Modellformat), Skelett-Animationen, danach Strat-Skriptdaten (Namen wie "SinkingStatuePad") **[sicher für Modelle]** |
|
||||
| **MAP** | 0x42ac50 | Kachel-Raster: Platzierung der TRAK-Modelle + Vertexfarben + Zonen-PVS (+ Strats/Pfade/etc., siehe objects.md) **[sicher]** |
|
||||
| LGHT / LNFO / LGPC | 0x42c180 / 0x42ab10 / 0x558db0 | Lichter / Level-Info / ? – nicht Geometrie |
|
||||
| WFPC | inline → `DAT_006da330` | u32 feature flags of the level (control MAP/STPC layout) **[certain]** |
|
||||
| TEXT | 0x4067b0 / 0x406d30 | texture pages (256x256, 16 bit) + texture table (UV rectangles) **[certain]** |
|
||||
| SMPC / AMPC / SRPC | 0x558d30 / 0x558d70 / 0x545350 | sound (SMPC: 'cvg ' VAG samples; AMPC: 'ambi'/'VABp'; SRPC sound references) – **no geometry** |
|
||||
| **TRAK** | 0x42aac0 → 0x5563f0 | **world geometry**: tile models (render triangles + collision polygons) → global array `DAT_005846ec` **[certain]** |
|
||||
| **STPC** | 0x42ab50 | object ("strat") models (same model format), skeletal animations, then strat script data (names like "SinkingStatuePad") **[certain for models]** |
|
||||
| **MAP** | 0x42ac50 | tile grid: placement of the TRAK models + vertex colours + zone PVS (+ strats/paths/etc., see objects.md) **[certain]** |
|
||||
| LGHT / LNFO / LGPC | 0x42c180 / 0x42ab10 / 0x558db0 | lights / level info / ? – not geometry |
|
||||
|
||||
**Die Welt ist ein Kachelsystem (Croc-Erbe):** TRAK enthält N "Kachel"-Modelle in lokalen
|
||||
Koordinaten (ungefähr −0.5..0.5 in x/z, können aber bis ±2.4 hinausragen). MAP platziert sie
|
||||
auf einem W×H-Raster (1 Einheit = 1 Zelle) mit Höhe y und Drehung um y. Es gibt **keinen BSP**;
|
||||
die Raumaufteilung ist das Raster selbst (Zell-Listen) + eine 32-Zonen-Sichtbarkeitstabelle.
|
||||
**The world is a tile system (inherited from Croc):** TRAK contains N "tile" models in local
|
||||
coordinates (roughly −0.5..0.5 in x/z, but they can stick out up to ±2.4). MAP places them
|
||||
on a W×H grid (1 unit = 1 cell) with height y and rotation around y. There is **no BSP**;
|
||||
the spatial partition is the grid itself (cell lists) + a 32-zone visibility table.
|
||||
|
||||
## 2. Koordinatenräume [sicher]
|
||||
## 2. Coordinate spaces [certain]
|
||||
|
||||
* **Exportraum** (= MAP-Floats "A", = Vertexdaten von TRAK/STPC): rechtshändig, **y oben**,
|
||||
Rasterzellen liegen bei x ∈ [0,W), **z ∈ (−H, 0]**. OBJ-Export benutzt diesen Raum.
|
||||
* **Spielraum** (MAP-Fixpunkt "B", Kollision, Strats): (x, y, −z), 20.12-Fixpunkt (4096 = 1).
|
||||
Der Renderer rechnet B zurück: pos = (Bx, By, −Bz)/4096, Winkel = rot·2π/4096.
|
||||
* Kachel-Transformation (Vertex v lokal → Welt, Exportraum), a = rot/4096·2π:
|
||||
* **Export space** (= MAP floats "A", = vertex data of TRAK/STPC): right-handed, **y up**,
|
||||
grid cells lie at x ∈ [0,W), **z ∈ (−H, 0]**. The OBJ export uses this space.
|
||||
* **Game space** (MAP fixed point "B", collision, strats): (x, y, −z), 20.12 fixed point (4096 = 1).
|
||||
The renderer converts B back: pos = (Bx, By, −Bz)/4096, angle = rot·2π/4096.
|
||||
* Tile transform (local vertex v → world, export space), a = rot/4096·2π:
|
||||
`X = x + vx·cos a − vz·sin a ; Y = y + vy ; Z = z + vx·sin a + vz·cos a`
|
||||
(verifiziert: 93 % aller Kachel-Randvertices treffen exakt einen Nachbarvertex; die drei
|
||||
anderen Vorzeichen-/Spiegelvarianten liefern 65–70 %).
|
||||
* **Kollisionsdaten liegen im Spielraum** (z gespiegelt gegenüber den Render-Vertices desselben
|
||||
Modells). Beim Dekodieren nz negieren (92 % der rekonstruierten Kollisionsecken liegen
|
||||
< 0.02 neben einem Render-Vertex).
|
||||
* rx/rz der Kacheln sind in allen Levels 0; nur Drehung um y (praktisch 0/1024/2048/3072).
|
||||
(verified: 93 % of all tile edge vertices hit a neighbour vertex exactly; the three
|
||||
other sign/mirror variants give 65–70 %).
|
||||
* **Collision data is in game space** (z mirrored relative to the render vertices of the same
|
||||
model). Negate nz when decoding (92 % of the reconstructed collision corners lie
|
||||
< 0.02 from a render vertex).
|
||||
* rx/rz of the tiles are 0 in all levels; only rotation around y (in practice 0/1024/2048/3072).
|
||||
|
||||
## 3. Modellformat (TRAK und STPC) [sicher]
|
||||
## 3. Model format (TRAK and STPC) [certain]
|
||||
|
||||
Fixup-Funktionen: TRAK `FUN_005563f0(&ptr, n, &out)` (Header 0x84),
|
||||
STPC `FUN_0041f770(&ptr, n, &out)` (Header 0x8c). Datei-Layout: **alle Header eines Aufrufs
|
||||
hintereinander, dann die Daten der Modelle in Reihenfolge** (TRAK: ein Aufruf mit n=Anzahl;
|
||||
STPC: n Aufrufe mit je 1 Modell → Header/Daten abwechselnd). Zeigerfelder sind in der Datei 0
|
||||
und werden beim Laden gesetzt.
|
||||
Fixup functions: TRAK `FUN_005563f0(&ptr, n, &out)` (header 0x84),
|
||||
STPC `FUN_0041f770(&ptr, n, &out)` (header 0x8c). File layout: **all headers of one call
|
||||
in a row, then the data of the models in order** (TRAK: one call with n=count;
|
||||
STPC: n calls with 1 model each → header/data alternating). Pointer fields are 0 in the file
|
||||
and are set on load.
|
||||
|
||||
### Header
|
||||
|
||||
| Off | Typ | Bedeutung |
|
||||
| Off | Type | Meaning |
|
||||
|---|---|---|
|
||||
| 0x00 | 3×f32 | Mittelpunkt der Bounding-Box |
|
||||
| 0x0c | 8×3 f32 | Ecken der Box, Reihenfolge (−x−y+z)(+x−y+z)(−x+y+z)(+x+y+z)(−x−y−z)(+x−y−z)(−x+y−z)(+x+y−z). Etwas größer als die Vertices (Rand nicht konstant, ~0.004–0.014; vermutl. aus Originalmesh). Für Frustum-Culling (`FUN_00402840`). |
|
||||
| 0x00 | 3×f32 | centre of the bounding box |
|
||||
| 0x0c | 8×3 f32 | box corners, order (−x−y+z)(+x−y+z)(−x+y+z)(+x+y+z)(−x−y−z)(+x−y−z)(−x+y−z)(+x+y−z). Slightly larger than the vertices (margin not constant, ~0.004–0.014; probably from the original mesh). Used for frustum culling (`FUN_00402840`). |
|
||||
| 0x6c | u16 | nVerts |
|
||||
| 0x6e | u16 | nFaces |
|
||||
| 0x70 | u32 | → Verts (Datei: 0) |
|
||||
| 0x74 | u32 | → Faces (Datei: 0) |
|
||||
| 0x78 / 0x7a / 0x7c | 3×u16 | Anzahl Kollisionspolygone in 3 Gruppen (Code summiert immer alle drei; Gruppenbedeutung [?]) |
|
||||
| 0x70 | u32 | → verts (file: 0) |
|
||||
| 0x74 | u32 | → faces (file: 0) |
|
||||
| 0x78 / 0x7a / 0x7c | 3×u16 | number of collision polygons in 3 groups (code always sums all three; group meaning [?]) |
|
||||
| 0x7e | u16 | 0 |
|
||||
| 0x80 | u32 | → Kollision (Datei: 0) |
|
||||
| *nur STPC* 0x84 | u16 | = nVerts bei starren Modellen, 0 bei Skin-Modellen |
|
||||
| *nur STPC* 0x86 | u16 | nBones (Skin-Modelle) – davor in den Daten nBones×u16 = Vertexzahl je Bone (Summe = nVerts, verifiziert) |
|
||||
| *nur STPC* 0x88 | u32 | → Bone-Tabelle (Datei: 0) |
|
||||
| 0x80 | u32 | → collision (file: 0) |
|
||||
| *STPC only* 0x84 | u16 | = nVerts for rigid models, 0 for skinned models |
|
||||
| *STPC only* 0x86 | u16 | nBones (skinned models) – preceded in the data by nBones×u16 = vertex count per bone (sum = nVerts, verified) |
|
||||
| *STPC only* 0x88 | u32 | → bone table (file: 0) |
|
||||
|
||||
### Daten (in dieser Reihenfolge)
|
||||
1. (nur STPC) nBones × u16
|
||||
2. **Vertex** 24 Byte: `f32 x,y,z ; f32 nx,ny,nz` (Normalen geglättet; Lichtwerte kommen aus MAP-Vertexfarben)
|
||||
3. **Face** 28 Byte (nur Dreiecke):
|
||||
`u16 flags ; u16 v0,v1,v2 ; u16 tex ; u16 0 ; f32 nx,ny,nz,d` mit n = normalize((v1−v0)×(v2−v0)), d = n·v0
|
||||
(verifiziert für alle Faces). Beim Laden wird `tex` durch Zeiger auf Texturtabelleneintrag
|
||||
(`DAT_00581154 + tex*20`) ersetzt. Ebene dient dem Backface-Culling (Kamera·n − d ≥ ε).
|
||||
4. **Kollisionspolygon** 32 Byte, nGruppeA+B+C Stück:
|
||||
`u8 typ ; u8 material ; 5 × { i8 nx, i8 ny, i8 nz, u8 0, i16 d }`
|
||||
* Ebene 0 = Fläche, Ebenen 1..4 = Kanten (Normalen **nach innen**, Punkt drin ⇔ n·p − d ≥ −Toleranz).
|
||||
* Normalen ×127 (Code rechnet ×32 → 4096-Skala), d ×4096 (Fixpunkt) → lokale Koordinaten nur ±8 Einheiten!
|
||||
* typ Bit0 = Viereck (4 Kanten), sonst Dreieck (3 Kanten, 5. Ebene = 0). Bit2 ≈ Normale zeigt nach unten (ny<0; Decken/Überhänge) [wahrsch.].
|
||||
* material: Untergrund-Typ (0 = normal; 0x11/0x12 werden je nach WFPC 0x8000000 ignoriert, 0x1e = Wasser-/Todesebene [wahrsch.]; Wert landet im Spieler-Bodenstatus).
|
||||
* Kollisionsabfragen: `FUN_004036d0` (Boden/Decke), `FUN_00403ad0` (Wände), `FUN_004042f0`, `FUN_004046e0`. Getestet werden **nur die Kacheln der Rasterzelle, in der der Spieler steht** (Zellliste aus MAP), Punkt relativ zu Kachel-B-Position, um −rot gedreht.
|
||||
### Data (in this order)
|
||||
1. (STPC only) nBones × u16
|
||||
2. **Vertex** 24 bytes: `f32 x,y,z ; f32 nx,ny,nz` (smoothed normals; lighting comes from MAP vertex colours)
|
||||
3. **Face** 28 bytes (triangles only):
|
||||
`u16 flags ; u16 v0,v1,v2 ; u16 tex ; u16 0 ; f32 nx,ny,nz,d` with n = normalize((v1−v0)×(v2−v0)), d = n·v0
|
||||
(verified for all faces). On load `tex` is replaced by a pointer to the texture table entry
|
||||
(`DAT_00581154 + tex*20`). The plane is used for backface culling (camera·n − d ≥ ε).
|
||||
4. **Collision polygon** 32 bytes, nGroupA+B+C of them:
|
||||
`u8 type ; u8 material ; 5 × { i8 nx, i8 ny, i8 nz, u8 0, i16 d }`
|
||||
* plane 0 = surface, planes 1..4 = edges (normals pointing **inwards**, point inside ⇔ n·p − d ≥ −tolerance).
|
||||
* normals ×127 (code multiplies by 32 → 4096 scale), d ×4096 (fixed point) → local coordinates only ±8 units!
|
||||
* type bit0 = quad (4 edges), otherwise triangle (3 edges, 5th plane = 0). Bit2 ≈ normal points down (ny<0; ceilings/overhangs) [likely].
|
||||
* material: ground type (0 = normal; 0x11/0x12 are ignored depending on WFPC 0x8000000, 0x1e = water/death plane [likely]; the value ends up in the player's ground state).
|
||||
* Collision queries: `FUN_004036d0` (floor/ceiling), `FUN_00403ad0` (walls), `FUN_004042f0`, `FUN_004046e0`. Only **the tiles of the grid cell the player stands in** are tested (cell list from MAP), point relative to the tile's B position, rotated by −rot.
|
||||
|
||||
### Face-Flags (u16)
|
||||
| Bit | Bedeutung | Konfidenz |
|
||||
### Face flags (u16)
|
||||
| Bit | Meaning | Confidence |
|
||||
|---|---|---|
|
||||
| 0x0008 | neuer Render-Batch: Renderzustand (Seite, Blendmode) aus diesem Face neu laden, vorherigen Batch zeichnen. Sortierung nach Seite/Zustand + Bit am Wechsel. | sicher (Code) |
|
||||
| 0x0010, 0x0800, 0x0020 | UV-Ecken-Auswahl (s. u.) | sicher |
|
||||
| 0x0400 | kein Backface-Culling (doppelseitig) | sicher |
|
||||
| 0x4000 | Vertexfarben ignorieren (weiß) | sicher |
|
||||
| 0x0001, 0x0002 | Sonder-Rendermodi (Sortierung/Transparenz; zusammen mit Globalflags 0x5f6ee8) | wahrsch. |
|
||||
| 0x8000, 0x1000, 0x2000, 0x0200, 0x0100, 0x0004 | in Daten vorhanden, Renderer (0x556510) wertet sie nicht aus | ? (vermutl. PS1-Reste) |
|
||||
| 0x0008 | new render batch: reload render state (page, blend mode) from this face, draw the previous batch. Sorted by page/state + bit at each change. | certain (code) |
|
||||
| 0x0010, 0x0800, 0x0020 | UV corner selection (see below) | certain |
|
||||
| 0x0400 | no backface culling (double-sided) | certain |
|
||||
| 0x4000 | ignore vertex colours (white) | certain |
|
||||
| 0x0001, 0x0002 | special render modes (sorting/transparency; together with global flags 0x5f6ee8) | likely |
|
||||
| 0x8000, 0x1000, 0x2000, 0x0200, 0x0100, 0x0004 | present in the data, the renderer (0x556510) does not evaluate them | ? (probably PS1 leftovers) |
|
||||
|
||||
### UVs – die Engine hat KEINE freien UVs [sicher]
|
||||
Jedes Dreieck zeigt eine Hälfte eines **Texturrechtecks** (Eintrag der TEXT-Tabelle).
|
||||
UV-Rechteck: U0=u0/256, U1=(u1+1)/256, V0=v0/256, V1=(v1+1)/256. Mit m = Flag 0x20 (spiegelt U),
|
||||
a = m?U0:U1, b = m?U1:U0 bekommen die Ecken (v0,v1,v2):
|
||||
* ohne 0x800: (a,V1) (b,V1) (a,V0)
|
||||
* 0x800, ohne 0x10: (a,V1) (b,V1) (b,V0) ← erste Hälfte eines Vierecks
|
||||
* 0x800 + 0x10: (b,V0) (a,V0) (a,V1) ← zweite Hälfte
|
||||
(Renderer `FUN_00556510` ab Zeile "if ((uVar6 & 0x800) == 0)"). Texturen mit Flag Bit0 sind
|
||||
Flachfarben (u0,u1,v0 = R,G,B). 96 % der Welt-Faces haben 0x800.
|
||||
### UVs – the engine has NO free UVs [certain]
|
||||
Every triangle shows one half of a **texture rectangle** (entry of the TEXT table).
|
||||
UV rectangle: U0=u0/256, U1=(u1+1)/256, V0=v0/256, V1=(v1+1)/256. With m = flag 0x20 (mirrors U),
|
||||
a = m?U0:U1, b = m?U1:U0 the corners (v0,v1,v2) get:
|
||||
* without 0x800: (a,V1) (b,V1) (a,V0)
|
||||
* 0x800, without 0x10: (a,V1) (b,V1) (b,V0) ← first half of a quad
|
||||
* 0x800 + 0x10: (b,V0) (a,V0) (a,V1) ← second half
|
||||
(renderer `FUN_00556510` from the line "if ((uVar6 & 0x800) == 0)"). Textures with flag bit0 are
|
||||
flat colours (u0,u1,v0 = R,G,B). 96 % of world faces have 0x800.
|
||||
|
||||
## 4. TRAK-Chunk [sicher]
|
||||
`u32 n ; n × Header(0x84) ; Daten Modell 0..n−1`. Alle 34 Level-WADs parsen exakt bis
|
||||
Chunkende; `geomlib.build_trak(parse_trak(x)) == x` byte-identisch für alle Levels.
|
||||
Typisch 100–650 Modelle, max. 422 Verts / 798 Faces pro Modell. Modell 0 ist in vielen Levels
|
||||
eine unsichtbare Ebene (4 Verts, 0 Faces, 1 Kollisionspolygon, material 0x1e) bei y=−2.75 –
|
||||
die Wasser-/Absturzebene unter den Inseln.
|
||||
## 4. TRAK chunk [certain]
|
||||
`u32 n ; n × header(0x84) ; data of model 0..n−1`. All 34 level WADs parse exactly up to the
|
||||
end of the chunk; `geomlib.build_trak(parse_trak(x)) == x` byte-identical for all levels.
|
||||
Typically 100–650 models, max. 422 verts / 798 faces per model. Model 0 is, in many levels,
|
||||
an invisible plane (4 verts, 0 faces, 1 collision polygon, material 0x1e) at y=−2.75 –
|
||||
the water/fall plane below the islands.
|
||||
|
||||
## 5. MAP-Chunk – Geometrieteile (Loader FUN_0042ac50) [sicher]
|
||||
(Strat/Pfad/Zonen-Blöcke: siehe notes/objects.md; hier nur, was die Geometrie betrifft.)
|
||||
Reihenfolge in der Datei:
|
||||
## 5. MAP chunk – geometry parts (loader FUN_0042ac50) [certain]
|
||||
(Strat/path/zone blocks: see notes/objects.md; only what concerns geometry here.)
|
||||
Order in the file:
|
||||
|
||||
1. `u32 N (Kacheln), u32 W, u32 H` (Raster)
|
||||
2. **A**: N × `{f32 x, f32 y, f32 z, u32 rot, u32 model, u32 animFlag}` (Exportraum; x,z = Zellmitte .5).
|
||||
Nur zum Einsortieren in die Zelllisten benutzt: Zelle = floor(x) + floor(−z)·W.
|
||||
3. u32 n + n×u32 (Liste, von Strats referenziert)
|
||||
4. u32 n + n×90 Byte (Lichter/Kameras? – objects.md)
|
||||
5. u32 n + n×34 Byte (Pfadknoten)
|
||||
6. **Zonentabelle** 256 Byte = 32 × `{u32 Sichtmaske, i32 Höhenwert}` → `DAT_006da350`
|
||||
7. **Zonenraster** W·H × u32 (Zonennummer 0..31 je Zelle) → world+0x48
|
||||
8. **B**: N × `{i32 rx, i32 ry, i32 rz, i32 x, i32 y, i32 z}` 20.12-Fixpunkt, Spielraum (z = −A.z), ry = A.rot.
|
||||
**Wird zum Rendern und für Kollision benutzt.** A und B sind in allen Levels konsistent.
|
||||
9. **Modellindex**: N × u32 (= A.model)
|
||||
10. falls WFPC & 0x10000: `u32 n ; n × {u32 cellX, u32 y, u32 cellZ, u32 modelA, u32 modelB}` –
|
||||
Kachel-Wechsel (Türen/Brücken o. Ä.), Eintrag k gehört zur k-ten Kachel mit animFlag≠0.
|
||||
11. **Vertexfarben** je Kachel i: nVerts(model) × u32 RGBA (0x80 = 1.0, Spiel ×2 mit Clamp);
|
||||
Byte3 des ersten Eintrags = Anzahl Farbsätze (0/1 = einer; ≥2 → weitere nVerts×u32 folgen,
|
||||
zweiter Satz wird bei `DAT_005865d4==1` benutzt). Für animFlag-Kacheln danach dieselbe
|
||||
Struktur für modelB. Zeigertabelle world+0x58 (bei WFPC 0x10000000 doppelt groß).
|
||||
12. Rest (u32 n2,n3 + 58-Byte-Strat-Einträge, optional Pfade (0x200000), Sichtweite (0x10), u16) – objects.md.
|
||||
1. `u32 N (tiles), u32 W, u32 H` (grid)
|
||||
2. **A**: N × `{f32 x, f32 y, f32 z, u32 rot, u32 model, u32 animFlag}` (export space; x,z = cell centre .5).
|
||||
Only used to sort into the cell lists: cell = floor(x) + floor(−z)·W.
|
||||
3. u32 n + n×u32 (list, referenced by strats)
|
||||
4. u32 n + n×90 bytes (lights/cameras? – objects.md)
|
||||
5. u32 n + n×34 bytes (path nodes)
|
||||
6. **Zone table** 256 bytes = 32 × `{u32 visibility mask, i32 height value}` → `DAT_006da350`
|
||||
7. **Zone grid** W·H × u32 (zone number 0..31 per cell) → world+0x48
|
||||
8. **B**: N × `{i32 rx, i32 ry, i32 rz, i32 x, i32 y, i32 z}` 20.12 fixed point, game space (z = −A.z), ry = A.rot.
|
||||
**Used for rendering and collision.** A and B are consistent in all levels.
|
||||
9. **Model index**: N × u32 (= A.model)
|
||||
10. if WFPC & 0x10000: `u32 n ; n × {u32 cellX, u32 y, u32 cellZ, u32 modelA, u32 modelB}` –
|
||||
tile swaps (doors/bridges etc.), entry k belongs to the k-th tile with animFlag≠0.
|
||||
11. **Vertex colours** per tile i: nVerts(model) × u32 RGBA (0x80 = 1.0, game ×2 with clamp);
|
||||
byte3 of the first entry = number of colour sets (0/1 = one; ≥2 → another nVerts×u32 follow,
|
||||
the second set is used when `DAT_005865d4==1`). For animFlag tiles the same
|
||||
structure follows for modelB. Pointer table world+0x58 (twice as large with WFPC 0x10000000).
|
||||
12. Rest (u32 n2,n3 + 58-byte strat entries, optional paths (0x200000), view distance (0x10), u16) – objects.md.
|
||||
|
||||
`geomlib.build_map_tiles(..., zones='keep', anim=orig)` reproduziert alle MAP-Chunks byte-identisch.
|
||||
`geomlib.build_map_tiles(..., zones='keep', anim=orig)` reproduces all MAP chunks byte-identically.
|
||||
|
||||
### Raster / Sichtbarkeit (kein BSP) [sicher]
|
||||
* Zelllisten: world+0x40 (W·H Zeiger, verkettete Listen von Kachelindizes), aus A gebaut.
|
||||
* Rendern (`FUN_0042bf40`): Rechteck um die Kamera (Sichtweite) über das Raster; für jede Zelle
|
||||
alle Kacheln der Liste, aber **nur wenn Bit zone[Zelle] in der Maske der Kamerazone gesetzt ist**
|
||||
(`DAT_0058642c` = Zonentabelle[zone[Kamerazelle]].maske, `FUN_0041e020`). Zone 0 hat im
|
||||
Dorf-Level Maske 0x1. → Neue Geometrie muss passende Zonen bekommen, sonst unsichtbar.
|
||||
`geom_import --zones flat` setzt Raster=0 und Maske(Zone 0)=0xffffffff (alles sichtbar).
|
||||
* Kollision: nur Kacheln der Spielerzelle (s. o.).
|
||||
### Grid / visibility (no BSP) [certain]
|
||||
* Cell lists: world+0x40 (W·H pointers, linked lists of tile indices), built from A.
|
||||
* Rendering (`FUN_0042bf40`): rectangle around the camera (view distance) over the grid; for every cell
|
||||
all tiles in the list, but **only if bit zone[cell] is set in the mask of the camera zone**
|
||||
(`DAT_0058642c` = zonetable[zone[camera cell]].mask, `FUN_0041e020`). Zone 0 has mask 0x1 in the
|
||||
village level. → New geometry must get matching zones, otherwise it is invisible.
|
||||
`geom_import --zones flat` sets grid=0 and mask(zone 0)=0xffffffff (everything visible).
|
||||
* Collision: only tiles of the player's cell (see above).
|
||||
|
||||
## 6. STPC-Chunk (Objekte) [sicher bis Animationen]
|
||||
`u32 n1 ; n1 × (Header 0x8c + Daten)` statische/geskinnte Objektmodelle (33 im Dorf) ;
|
||||
`u32 n2 ; n2 × Animation` (`FUN_0041f8b0`, Header 32 Byte: +0 nA (nA×4 Byte), +8 nFrames,
|
||||
+0xc (0 → nFrames×8 Byte), +0x10 nDelta (Frame 0: nDelta×8, sonst nDelta×2 (+2 wenn ungerade)
|
||||
Byte komprimierte Vertexdeltas), +0x18 nBones (je Frame nBones×64 Byte = 4×4-Matrix)) ;
|
||||
falls WFPC & 0x100: `u32 n3 ; n3 × {16-Byte-Kopf (+4 Anzahl, +8 → n×8 Byte), Modelle}` ;
|
||||
danach Strat-Daten (Skripte, Namen) – nicht vom Lader geparst, über Offsets (`DAT_006d9dbc+off`)
|
||||
aus MAP referenziert. `geom_export --objects` exportiert die n1 Modelle nebeneinander.
|
||||
## 6. STPC chunk (objects) [certain up to animations]
|
||||
`u32 n1 ; n1 × (header 0x8c + data)` static/skinned object models (33 in the village) ;
|
||||
`u32 n2 ; n2 × animation` (`FUN_0041f8b0`, header 32 bytes: +0 nA (nA×4 bytes), +8 nFrames,
|
||||
+0xc (0 → nFrames×8 bytes), +0x10 nDelta (frame 0: nDelta×8, otherwise nDelta×2 (+2 if odd)
|
||||
bytes of compressed vertex deltas), +0x18 nBones (per frame nBones×64 bytes = 4×4 matrix)) ;
|
||||
if WFPC & 0x100: `u32 n3 ; n3 × {16-byte head (+4 count, +8 → n×8 bytes), models}` ;
|
||||
then strat data (scripts, names) – not parsed by the loader, referenced from MAP via offsets
|
||||
(`DAT_006d9dbc+off`). `geom_export --objects` exports the n1 models side by side.
|
||||
|
||||
## 7. TEXT – soweit für Geometrie nötig [sicher]
|
||||
## 7. TEXT – as far as needed for geometry [certain]
|
||||
`u32 nPal ; nPal×{u32, u32 n, n×u16, n×u8}` ; `u32 nPages ; nPages×{u32 flags, u32 w, u32 h,
|
||||
(flags&0x80: u32 len + RLE16) | (flags≠0: w·h·2) | w·h}` ; `u32 nTex ; nTex × 8 Byte` ; Rest (40 Byte im Dorf) [?].
|
||||
Textureintrag: `u16 flags, i8 page, i8 page2(-1), u8 u0, u8 u1, u8 v0, u8 v1` (u1/v1 inklusiv).
|
||||
Flags: Bit0 Flachfarbe, Bit2 1-Pixel-Rand, Bit3/Bit4 Breite/Höhe ×2, Bits 12–14 Blendmodus, Bits 8–10 weiterer Modus.
|
||||
Pixel: ABGR1555 (R = Bits 0–4), 0 = transparent. Details: notes/textures.md (anderer Agent).
|
||||
(flags&0x80: u32 len + RLE16) | (flags≠0: w·h·2) | w·h}` ; `u32 nTex ; nTex × 8 bytes` ; rest (40 bytes in the village) [?].
|
||||
Texture entry: `u16 flags, i8 page, i8 page2(-1), u8 u0, u8 u1, u8 v0, u8 v1` (u1/v1 inclusive).
|
||||
Flags: bit0 flat colour, bit2 1-pixel border, bit3/bit4 width/height ×2, bits 12–14 blend mode, bits 8–10 another mode.
|
||||
Pixels: ABGR1555 (R = bits 0–4), 0 = transparent. Details: notes/textures.md (other agent).
|
||||
|
||||
## 8. Neue Geometrie schreiben – was konsistent sein muss
|
||||
Umgesetzt in `tools/geom_import.py` (OBJ → TRAK + MAP-Kachelteil, Rest des WADs unverändert):
|
||||
1. **TRAK**: u32 Anzahl; Header mit korrekten nVerts/nFaces/nKoll; Box+Mittelpunkt (für Culling,
|
||||
wird neu berechnet); Face-Ebenen aus Vertices; Vertexnormalen. Keine Prüfsummen.
|
||||
2. **Face-Texturindex** < Anzahl Texturtabelleneinträge; UVs nur über Rechteck-Ecken (+Flags).
|
||||
Bit 0x8 am Batch-Anfang (Seiten-/Zustandswechsel, spätestens alle 256 Faces).
|
||||
3. **Limits**: ≤ 8000 Vertices pro Modell (Transformpuffer 0x58b0f0..0x5d15f8, 36 Byte/Vertex),
|
||||
Batch-Ausgabepuffer 0x5d15f8 ≈ 4800 Vertices; Kollisions-d als i16/4096 → Kollisionspolygone
|
||||
nur innerhalb ±8 Einheiten vom Kachelursprung. u16-Indizes.
|
||||
4. **MAP**: N in Kopf, A (Floats) und B (Fixpunkt, z negiert) identisch halten, Modellindizes
|
||||
< TRAK-Anzahl, Kachelzellen innerhalb W×H, **Vertexfarben-Block exakt nVerts(Modell) pro
|
||||
Kachel** (sonst verschiebt sich der Rest von MAP = Strats!), Animationsblock (WFPC 0x10000)
|
||||
passend zu den Flags (Import setzt ihn auf 0 Einträge), Zonen (s. o.).
|
||||
5. Kollision gehört zur Zelle: ein Polygon wirkt nur, wenn es in der Kachelliste der Zelle steht,
|
||||
in der der Spieler steht → Import trägt jedes Polygon in jede überdeckte Zelle ein
|
||||
(auch als reine Kollisionskachel ohne Faces, wie Original-Modell 0).
|
||||
6. WAD-Gesamtlänge (wadtool/geomlib schreiben sie neu). Keine weiteren Prüfsummen gefunden.
|
||||
7. Strats (Startpunkt, Gegner, Kamera-Pfade) liegen weiter an ihren alten Positionen (objects.md).
|
||||
## 8. Writing new geometry – what must be consistent
|
||||
Implemented in `tools/geom_import.py` (OBJ → TRAK + MAP tile part, rest of the WAD unchanged):
|
||||
1. **TRAK**: u32 count; header with correct nVerts/nFaces/nColl; box+centre (for culling,
|
||||
recomputed); face planes from vertices; vertex normals. No checksums.
|
||||
2. **Face texture index** < number of texture table entries; UVs only via rectangle corners (+flags).
|
||||
Bit 0x8 at the start of a batch (page/state change, at the latest every 256 faces).
|
||||
3. **Limits**: ≤ 8000 vertices per model (transform buffer 0x58b0f0..0x5d15f8, 36 bytes/vertex),
|
||||
batch output buffer 0x5d15f8 ≈ 4800 vertices; collision d as i16/4096 → collision polygons
|
||||
only within ±8 units of the tile origin. u16 indices.
|
||||
4. **MAP**: keep N in the header, A (floats) and B (fixed point, z negated) identical, model indices
|
||||
< TRAK count, tile cells within W×H, **vertex colour block exactly nVerts(model) per
|
||||
tile** (otherwise the rest of MAP = strats shifts!), animation block (WFPC 0x10000)
|
||||
matching the flags (import sets it to 0 entries), zones (see above).
|
||||
5. Collision belongs to the cell: a polygon only takes effect if it is in the tile list of the cell
|
||||
the player stands in → import enters every polygon into every cell it covers
|
||||
(also as a pure collision tile without faces, like original model 0).
|
||||
6. Total WAD length (wadtool/geomlib rewrite it). No other checksums found.
|
||||
7. Strats (start point, enemies, camera paths) remain at their old positions (objects.md).
|
||||
|
||||
### Tests (ohne Spielstart)
|
||||
* Round-Trip TRAK und MAP aller 34 WADs byte-identisch.
|
||||
* Export → Import (`--coll-obj`) → Export t1l1m001: Rendering identisch (img/), 26435/26442
|
||||
Dreiecke (7 entartete fallen weg), Bodenhöhe an 2800 Zufallspunkten: 99 % < 5 mm Abweichung,
|
||||
10 Punkte anders (Polygone jetzt auch in Nachbarzellen eingetragen).
|
||||
* Unabhängiger Gegencheck: `tools/mapchunk.py` (anderer Agent) parst die erzeugten MAPs vollständig.
|
||||
* Noch NICHT im Spiel getestet: Demo-WADs in $CLAUDE_JOB_DIR/tmp: `demo_add.wad` (Original +
|
||||
2 Blöcke + Rampe, Zonen original), `demo.wad` (alles ersetzt durch Bodenplatte y=0 + Blöcke).
|
||||
### Tests (without starting the game)
|
||||
* Round trip TRAK and MAP of all 34 WADs byte-identical.
|
||||
* Export → import (`--coll-obj`) → export t1l1m001: rendering identical (img/), 26435/26442
|
||||
triangles (7 degenerate ones dropped), floor height at 2800 random points: 99 % < 5 mm deviation,
|
||||
10 points differ (polygons now also entered in neighbouring cells).
|
||||
* Independent cross-check: `tools/mapchunk.py` (other agent) parses the generated MAPs completely.
|
||||
* NOT yet tested in game: demo WADs in $CLAUDE_JOB_DIR/tmp: `demo_add.wad` (original +
|
||||
2 blocks + ramp, original zones), `demo.wad` (everything replaced by a floor plate y=0 + blocks).
|
||||
|
||||
## 9. Offen / unbekannt
|
||||
* Bedeutung der drei Kollisionsgruppen (Summe wird immer zusammen benutzt) und vollständige Materialliste.
|
||||
* Face-Flags 0x8000/0x1000/0x2000/0x200/0x100/0x4; genaue Wirkung 0x1/0x2.
|
||||
* Exakte Herkunft des Box-Randes im Header (Neu-Berechnung mit 1/128 Rand reicht vermutlich).
|
||||
* Zonentabelle 2. Wert (Höhe, `DAT_00586424`) – evtl. Wasser-/Nebelhöhe.
|
||||
* STPC-Animation: Bedeutung von nA-Block und Delta-Kompression im Detail (`FUN_0041fa30`).
|
||||
* Ob der Spieler-Startpunkt/Kamera an Geometrie gekoppelt ist (objects.md).
|
||||
## 9. Open / unknown
|
||||
* Meaning of the three collision groups (the sum is always used together) and the complete material list.
|
||||
* Face flags 0x8000/0x1000/0x2000/0x200/0x100/0x4; exact effect of 0x1/0x2.
|
||||
* Exact origin of the box margin in the header (recomputing with a 1/128 margin is probably enough).
|
||||
* Zone table 2nd value (height, `DAT_00586424`) – possibly water/fog height.
|
||||
* STPC animation: meaning of the nA block and the delta compression in detail (`FUN_0041fa30`).
|
||||
* Whether the player start point/camera is coupled to geometry (objects.md).
|
||||
|
||||
## 10. Werkzeuge
|
||||
* `geom_export.py WAD OUT [--collision] [--objects]` → level.obj/mtl (UV, Vertexfarben, ein
|
||||
Objekt pro Kachel), page_NN.png, collision.obj (Gruppen matNN_grpG), objects.obj, preview_top/persp.png
|
||||
(texturierter Software-Renderer).
|
||||
* `geom_import.py BASIS.wad MESH.obj NEU.wad [--coll-obj C.obj] [--keep-tiles] [--zones flat|keep] [--default-tex N]`
|
||||
* `geom_demo.py BASIS.wad demo.obj [--plaza]` – Beispielgeometrie aus Texturkachel-Vierecken.
|
||||
* `geomlib.py` – Parser/Builder (TRAK, STPC-Modelle, MAP-Kachelteil, TEXT-Tabelle/Seiten, Kollision).
|
||||
## 10. Tools
|
||||
* `geom_export.py WAD OUT [--collision] [--objects]` → level.obj/mtl (UV, vertex colours, one
|
||||
object per tile), page_NN.png, collision.obj (groups matNN_grpG), objects.obj, preview_top/persp.png
|
||||
(textured software renderer).
|
||||
* `geom_import.py BASE.wad MESH.obj NEW.wad [--coll-obj C.obj] [--keep-tiles] [--zones flat|keep] [--default-tex N]`
|
||||
* `geom_demo.py BASE.wad demo.obj [--plaza]` – example geometry made of texture-tile quads.
|
||||
* `geomlib.py` – parser/builder (TRAK, STPC models, MAP tile part, TEXT table/pages, collision).
|
||||
Reference in new issue
Block a user