16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-21 15:59:18 +02:00

EU7 scenery format and models streaming

This commit is contained in:
maj00r
2026-06-13 15:43:44 +02:00
parent f07c8fdcd2
commit 876a4c7c18
171 changed files with 27924 additions and 281 deletions

951
docs/eu7-format.md Normal file
View File

@@ -0,0 +1,951 @@
# Format pliku EU7B (`.eu7`)
> Dokumentacja oparta na kodzie źródłowym z:
> - `scene/eu7/` — runtime reader/loader (maszyna-fresh)
> - `parser/include/eu07/scene/binary/` — writer/reader (narzędzie CLI)
>
> Stan: **czerwiec 2026**, obejmuje wersje v4v8.
---
## Spis treści
1. [Przegląd ogólny](#1-przegląd-ogólny)
2. [Nagłówek pliku i wersje](#2-nagłówek-pliku-i-wersje)
3. [Struktura chunku](#3-struktura-chunku)
4. [Kolejność i reguły chunkowania](#4-kolejność-i-reguły-chunkowania)
5. [Typy podstawowe i kodowanie](#5-typy-podstawowe-i-kodowanie)
6. [Slim Node — wspólny nagłówek węzła](#6-slim-node--wspólny-nagłówek-węzła)
7. [Transform Context](#7-transform-context)
8. [Packed vertex — pakowany wierzchołek](#8-packed-vertex--pakowany-wierzchołek)
9. [Lighting block — blok oświetlenia](#9-lighting-block--blok-oświetlenia)
10. [Catalog chunkowy — zestawienie](#10-catalog-chunkowy--zestawienie)
11. [STRS — tablica łańcuchów](#11-strs--tablica-łańcuchów)
12. [INCL — referencje do podmodułów](#12-incl--referencje-do-podmodułów)
13. [TRAK — tory](#13-trak--tory)
14. [TRAC — przewody trakcyjne](#14-trac--przewody-trakcyjne)
15. [PWRS — źródła zasilania trakcji](#15-pwrs--źródła-zasilania-trakcji)
16. [TERR — teren (terrain shapes)](#16-terr--teren-terrain-shapes)
17. [MESH — kształty geometryczne](#17-mesh--kształty-geometryczne)
18. [LINE — linie](#18-line--linie)
19. [MODL — instancje modeli 3D](#19-modl--instancje-modeli-3d)
20. [MEMC — komórki pamięci](#20-memc--komórki-pamięci)
21. [LAUN — wyzwalacze zdarzeń](#21-laun--wyzwalacze-zdarzeń)
22. [DYNM — pojazdy dynamiczne](#22-dynm--pojazdy-dynamiczne)
23. [SOND — dźwięki otoczenia](#23-sond--dźwięki-otoczenia)
24. [TRSE — zestawy wagonowe](#24-trse--zestawy-wagonowe)
25. [EVNT — zdarzenia](#25-evnt--zdarzenia)
26. [FINT — licznik first-init](#26-fint--licznik-first-init)
27. [PLAC — parametry umieszczenia include](#27-plac--parametry-umieszczenia-include)
28. [PIDX — indeks sekcji PACK (v7+)](#28-pidx--indeks-sekcji-pack-v7)
29. [PACK — instancje modeli per sekcja 1 km (v7+)](#29-pack--instancje-modeli-per-sekcja-1-km-v7)
30. [PROT — prototypy modeli (v8+)](#30-prot--prototypy-modeli-v8)
31. [Semantyka strumieniowania PACK w runtime](#31-semantyka-strumieniowania-pack-w-runtime)
32. [Historia wersji](#32-historia-wersji)
33. [Ścieżki plików i konwencje nazewnictwa](#33-ścieżki-plików-i-konwencje-nazewnictwa)
---
## 1. Przegląd ogólny
Plik `.eu7` (EU7B — **EU7 Binary**) to binarny format zapisu skompilowanego modułu scenerii symulatora EU07. Zastępuje tekst source (`*.scm`, `*.inc`, `*.scn`) jednym zoptymalizowanym plikiem binarnym gotowym do szybkiego wczytania przez silnik.
**Kluczowe cechy:**
- **Little-endian** dla wszystkich liczb całkowitych i zmiennoprzecinkowych
- **Chunki samoopisowe** — nieznane chunki mogą być pomijane (`seekg` po `chunk_size`)
- **Tablica łańcuchów (STRS)** — wszystkie stringi współdzielone przez indeks `uint32`
- **Strumieniowanie sekcji 1 km** — w plikach głównych scenariusza modele są dzielone na kafelki 1 km × 1 km (chunki PIDX + PACK), wczytywane asynchronicznie
- **Format skaluje się** od małych modułów include (kilka KB) do głównych scenariuszy (setki MB w PACK)
---
## 2. Nagłówek pliku i wersje
### Layout nagłówka
```
Offset Rozmiar Typ Opis
------ ------- -------- -------------------------
0 4 char[4] Magic = 'EU7B' (0x45 0x55 0x37 0x42)
4 4 uint32 Version (patrz tabela poniżej)
8 ... chunks Strumień chunkowy (do EOF)
```
Nie ma pola "łączny rozmiar pliku" w nagłówku. Po nagłówku następują chunki aż do EOF.
### Tabela wersji
| Wartość | Stała (maszyna-fresh) | Stała (parser CLI) | Opis |
|---------|-------------------------|---------------------------|----------------------------------------------------------------|
| `1` | — | `kVersionLegacy` | Legacy — **nieobsługiwane** w CLI ani runtime |
| `2` | — | `kVersionRuntimeF64` | Skalary i Vec3 jako `double` (float64) na dysku |
| `3` | — | `kVersionRuntimeF32` | Skalary i Vec3 jako `float32` na dysku |
| `4` | `kEu7VersionV4` | `kVersionRuntimeV4` | Slim node + packed mesh (snorm16 normy + half-float UV) |
| `5` | `kEu7VersionV5` | `kVersionRuntimeV5` | Adds TERR z batched terrain per sekcja 1 km |
| `6` | `kEu7VersionV6` | `kVersionRuntime` | Adds site transform w rekordach INCL |
| `7` | `kEu7VersionV7` | `kVersionRuntimeV7` | Adds PIDX + PACK — modele scenerii per sekcja 1 km (streaming) |
| `8` | `kEu7VersionV8` | — | Adds PROT (prototypy) + nowy format sekcji PACK v8 |
**Runtime maszyna-fresh obsługuje wersje 48.** Wersje 13 nie są obsługiwane przez żaden z dostępnych loaderów.
---
## 3. Struktura chunku
```
Offset Rozmiar Typ Opis
------ ------- ------- -----------------------------------------
0 4 uint32 Chunk ID — FourCC little-endian (4 znaki ASCII)
4 4 uint32 Chunk total size — ŁĄCZNIE z nagłówkiem (≥ 8)
8 ... bytes Payload (rozmiar = chunk_total_size - 8)
```
> **Uwaga:** pole `chunk_total_size` **wlicza** 8 bajtów własnego nagłówka. Aby obliczyć rozmiar payloadu: `payload_size = chunk_total_size - 8`.
Strumień chunkowy nie ma terminator record. Parsowanie trwa do momentu napotkania EOF.
### FourCC — sposób kodowania
ID chunku jest 4-znakowym ASCII wpisanym w kolejności little-endian do `uint32`:
```cpp
constexpr uint32_t make_id4(char a, char b, char c, char d) {
return (uint32(d) << 24) | (uint32(c) << 16) | (uint32(b) << 8) | uint32(a);
}
// Np. kChunkPack = make_id4('P','A','C','K')
// Bytes w pliku: 50 41 43 4B → odczytane jako uint32 LE = 0x4B434150
```
Przy odczycie człowiek widzi w hex-dumpie bajty w kolejności `P A C K`, co odpowiada oczytaniu `'PACK'` wprost.
---
## 4. Kolejność i reguły chunkowania
1. **STRS** musi wystąpić **przed** wszystkimi chunkiami używającymi indeksów string (w praktyce jest zawsze pierwszym chunkiem).
2. **PACK** jest **pomijany przy pełnym parse** — runtime zapamiętuje jedynie offset początku payloadu i rozmiar. Faktyczne dane są czytane on-demand przez `read_pack_section()`.
3. **PROT** musi wystąpić **przed** PACK (prototypy muszą być załadowane do pamięci zanim sekcje PACK będą rozwijane).
4. **PIDX** musi wystąpić przed próbą streamingu (runtime czyta PIDX przy załadowaniu modułu).
5. Nierozpoznane chunki są **ignorowane** — reader skacze do `chunk_start + payload_size`.
---
## 5. Typy podstawowe i kodowanie
Wszystkie typy całkowite i zmiennoprzecinkowe — **little-endian**.
| Typ w pliku | C++ typ docelowy | Rozmiar | Opis |
|-------------|------------------|---------|---------------------------------------------------|
| `uint8` | `uint8_t` | 1 B | Bajt bez znaku |
| `int16` | `int16_t` | 2 B | LE, ze znakiem |
| `uint16` | `uint16_t` | 2 B | LE, bez znaku |
| `int32` | `int32_t` | 4 B | LE, ze znakiem |
| `uint32` | `uint32_t` | 4 B | LE, bez znaku |
| `uint64` | `uint64_t` | 8 B | LE, bez znaku (lo-word najpierw) |
| `float32` | `float` | 4 B | IEEE 754 single precision |
| `f64_disk` | `double` | 4 B | **float32 na dysku** — wczytywany jako double w RAM |
| `Vec3` | `glm::dvec3` | 12 B | 3× `f64_disk` (= 3× float32, razem 12 B) |
| `snorm16` | `float` | 2 B | int16 / 32767.0 → zakres [-1, +1] |
| `half16` | `float` | 2 B | IEEE 754 half precision |
| `string_id` | `uint32_t` | 4 B | Indeks do tablicy STRS; `0xFFFFFFFF` = pusty |
> **`f64_disk`**: Od wersji v3 wzwyż skalary (dystanse, kąty, napięcia itp.) i współrzędne Vec3 są zapisywane jako **float32** (4 B), mimo że runtime przechowuje je jako `double`. Patrz: `io::writeF64()` w `io.hpp` — wewnętrznie rzutuje do `float` przed zapisem.
---
## 6. Slim Node — wspólny nagłówek węzła
Każdy obiekt sceny (tor, model, dźwięk itp.) poprzedzony jest **slim node** — skompresowanym nagłówkiem z opcjonalnymi polami sterowanymi bajtem flag.
### Layout
```
Offset Rozmiar Typ Opis
------ ------- --------- -------------------------------------------
0 1 uint8 flags (bitmaska, patrz poniżej)
--- poniższe pola są OPCJONALNE, zależne od flags ---
+0 4 string_id name (jeśli bit 0 set)
+0 4 f64_disk range_squared_min (jeśli bit 1 set)
+0 4 f64_disk range_squared_max (jeśli bit 2 set; domyślnie +∞)
+0 16 Vec3+f32 bounding sphere: center(Vec3) + radius(float32) (jeśli bit 3)
+0 4 uint32 group_handle (jeśli bit 4 set)
+0 ... Transform transform context (jeśli bit 5 set)
```
### Bitmaska `flags`
| Bit | Stała | Znaczenie |
|-----|--------------------------|-----------------------------------------------------|
| 0 | `kNodeFlagHasName` | pole `name` (string_id) jest obecne |
| 1 | `kNodeFlagHasRangeMin` | pole `range_squared_min` (f64_disk) jest obecne |
| 2 | `kNodeFlagHasRangeMax` | pole `range_squared_max` jest obecne (inaczej +∞) |
| 3 | `kNodeFlagHasBounds` | bounding sphere: Vec3 center + float32 radius |
| 4 | `kNodeFlagHasGroup` | pole `group_handle` (uint32) jest obecne |
| 5 | `kNodeFlagHasTransform` | transform context jest obecny (patrz sekcja 7) |
| 6 | `kNodeFlagNotVisible` | węzeł domyślnie niewidoczny (`visible = false`) |
### Bounding sphere
```
Vec3 center (12 B)
float32 radius (4 B)
```
Łącznie 16 B, obecne tylko gdy bit 3 set.
---
## 7. Transform Context
Kontekst transformacji lokacji węzła — stos offsetów i skal + obrót.
```
Offset Rozmiar Typ Opis
------ ---------------- -------- ----------------------------------------
0 1 uint8 origin_count (maks. 255)
1 12 × origin_count Vec3[] origin_stack (offsety translacji)
+ 1 uint8 scale_count (maks. 255)
+ 12 × scale_count Vec3[] scale_stack (skala)
+ 12 Vec3 rotation (kąty Eulera w stopniach, XYZ)
+ 1 uint8 group_depth
```
Kolejność stosowania transformacji (w writer: `transform_point()`):
1. Obrót Y (o `rotation.y` stopni)
2. Skalowanie (`scale_stack.back()`)
3. Translacja (`origin_stack.back()`)
---
## 8. Packed vertex — pakowany wierzchołek
Używany w chunkach MESH i TERR (od v4).
```
Offset Rozmiar Typ Opis
------ ------- ------- ------------------------------------------
0 4 float32 position.x
4 4 float32 position.y
8 4 float32 position.z
12 2 int16 normal.x jako snorm16 (/ 32767)
14 2 int16 normal.y jako snorm16
16 2 int16 normal.z jako snorm16
18 2 uint16 u jako half-float (IEEE 754 fp16)
20 2 uint16 v jako half-float
```
Łącznie: **22 bajty** na wierzchołek.
W chunkach LINE wierzchołki to **tylko pozycja** (Vec3 = 12 B; brak normalnych i UV).
---
## 9. Lighting block — blok oświetlenia
Opcjonalny blok oświetlenia materiału węzła (48 bajtów).
```
Offset Rozmiar Typ Opis
------ ------- ------- ------------------
0 16 4×f32 diffuse RGBA
16 16 4×f32 ambient RGBA
32 16 4×f32 specular RGBA
```
Domyślne wartości (gdy brak bloku):
- `diffuse = (0.8, 0.8, 0.8, 1.0)`
- `ambient = (0.2, 0.2, 0.2, 1.0)`
- `specular = (0.0, 0.0, 0.0, 1.0)`
Blok jest poprzedzony znacznikiem `uint8` (`0` = brak, `!0` = blok obecny) w rekordach MESH i LINE.
---
## 10. Catalog chunkowy — zestawienie
| FourCC | Stała C++ | Wersja min | Opis |
|---------|------------------|------------|------------------------------------------------|
| `STRS` | `kChunkStrs` | v4 | Tablica shared strings (musi być pierwsza) |
| `INCL` | `kChunkIncl` | v4 | Referencje do submodułów `.eu7` |
| `TRAK` | `kChunkTrak` | v4 | Tory (Track) |
| `TRAC` | `kChunkTrac` | v4 | Przewody trakcyjne (Traction) |
| `PWRS` | `kChunkPwrs` | v4 | Źródła zasilania trakcji |
| `TERR` | `kChunkTerr` | v5 | Teren (batched triangles per sekcja) |
| `MESH` | `kChunkMesh` | v4 | Kształty geometryczne (triangles/strip/fan) |
| `LINE` | `kChunkLine` | v4 | Linie (lines/strip/loop) |
| `MODL` | `kChunkModl` | v4 | Instancje modeli 3D (flat list) |
| `MEMC` | `kChunkMemc` | v4 | Komórki pamięci (MemCell) |
| `LAUN` | `kChunkLaun` | v4 | Wyzwalacze zdarzeń (EventLauncher) |
| `DYNM` | `kChunkDynm` | v4 | Pojazdy dynamiczne (Dynamic) |
| `SOND` | `kChunkSond` | v4 | Dźwięki otoczenia (Sound) |
| `TRSE` | `kChunkTrset` | v4 | Zestawy wagonowe (Trainset) |
| `EVNT` | `kChunkEvnt` | v4 | Zdarzenia (Event) |
| `FINT` | `kChunkFint` | v4 | Licznik obiektów first-init |
| `PLAC` | `kChunkPlac` | v4 | Parametry umieszczenia include |
| `PIDX` | `kChunkPidx` | **v7** | Indeks sekcji 1 km → offset w PACK |
| `PACK` | `kChunkPack` | **v7** | Strumień modeli per sekcja 1 km |
| `PROT` | `kChunkProt` | **v8** | Prototypy modeli (współdzielone definicje) |
---
## 11. STRS — tablica łańcuchów
Wszystkie łańcuchy tekstu w pliku są przechowywane w jednej tablicy i referowane przez `string_id` (indeks `uint32`). Specjalny indeks `0xFFFFFFFF` oznacza pusty string.
```
uint32 count
Powtórzone count razy:
uint32 length (w bajtach, bez null-terminatora)
char[] data (length bajtów UTF-8/ASCII, bez null)
```
Każdy kolejny string ma numer indeksu 0, 1, 2, …
---
## 12. INCL — referencje do podmodułów
Przechowuje listę `include` wskazujących na inne pliki `.eu7` — submoduły scenerii.
```
uint32 count
Powtórzone count razy:
uint32 source_line — numer linii w pliku tekstowym źródłowym
string_id source_path — ścieżka do pliku tekstowego (.scm/.inc)
string_id binary_path — ścieżka do pliku .eu7
uint32 param_count
string_id[] parameters — param_count × string_id (parametry include)
[v6+] TransformContext site_transform (nieobecne w v4/v5)
```
`site_transform` przechowuje pełny kontekst transformacji miejsca osadzenia (origin/scale/rotation) i jest używany do złożenia transformacji przy nakładaniu submodułu.
---
## 13. TRAK — tory
```
uint32 count
Powtórzone count razy:
SlimNode node
uint8 track_type — patrz enum Eu7TrackType
uint8 category — patrz enum Eu7TrackCategory
float32 length [m]
float32 track_width [m]
float32 friction
float32 sound_distance
int32 quality_flag
int32 damage_flag
uint8 environment — wartość enum + 1 (Unknown = 0, Flat = 1, ...)
uint8 has_visibility — 0 = brak, ≠0 = blok widoczności następuje
[jeśli has_visibility]:
string_id material1
float32 tex_length
string_id material2
float32 tex_height1
float32 tex_width
float32 tex_slope
uint32 path_count (maks. 65536)
Powtórzone path_count razy:
Vec3 p_start
f64_disk roll_start [stopnie]
Vec3 cp_out — punkt kontrolny wyjściowy Béziera
Vec3 cp_in — punkt kontrolny wejściowy Béziera
Vec3 p_end
f64_disk roll_end
f64_disk radius [m], 0 = prosta
uint32 tail_count (maks. 256)
Powtórzone tail_count razy:
uint8 code — 118 = predefiniowane kw., 255 = custom
[jeśli code==255]: string_id custom_key
string_id value
```
### Enum Eu7TrackType (uint8)
| Wartość | Nazwa |
|---------|-------------|
| 0 | Unknown |
| 1 | Normal |
| 2 | Switch |
| 3 | Table |
| 4 | Cross |
| 5 | Tributary |
### Enum Eu7TrackCategory (uint8)
| Wartość | Nazwa |
|---------|-------|
| 1 | Rail |
| 2 | Road |
| 4 | Water |
### Enum Eu7TrackEnvironment (uint8 w pliku = enum + 1)
| Wartość w pliku | Enum | Nazwa |
|-----------------|-------|-----------|
| 0 | -1 | Unknown |
| 1 | 0 | Flat |
| 2 | 1 | Mountains |
| 3 | 2 | Canyon |
| 4 | 3 | Tunnel |
| 5 | 4 | Bridge |
| 6 | 5 | Bank |
### Kody tail keywords (uint8)
| Kod | Słowo kluczowe |
|-----|----------------|
| 1 | `event0` |
| 2 | `eventall0` |
| 3 | `event1` |
| 4 | `eventall1` |
| 5 | `event2` |
| 6 | `eventall2` |
| 7 | `velocity` |
| 8 | `isolated` |
| 9 | `overhead` |
| 10 | `vradius` |
| 11 | `railprofile` |
| 12 | `trackbed` |
| 13 | `friction` |
| 14 | `fouling1` |
| 15 | `fouling2` |
| 16 | `sleepermodel` |
| 17 | `angle1` |
| 18 | `angle2` |
| 255 | *custom* |
---
## 14. TRAC — przewody trakcyjne
```
uint32 count
Powtórzone count razy:
SlimNode node
string_id power_supply_name
uint8 material — Eu7TractionWireMaterial (0=None,1=Copper,2=Aluminium)
float32 nominal_voltage [V]
float32 max_current [A]
float32 resistivity_ohm_per_m
f64_disk resistivity_legacy
string_id material_raw — oryginalny string materiału ("cu", "al", ...)
float32 wire_thickness [m]
int32 damage_flag
Vec3 wire_p1
Vec3 wire_p2
Vec3 wire_p3
Vec3 wire_p4
f64_disk min_height [m]
f64_disk segment_length [m]
int32 wire_count
float32 wire_offset
uint8 has_parallel — 0 = brak, ≠0 = nazwa równoległego sekcji
[jeśli has_parallel]:
string_id parallel_name
```
---
## 15. PWRS — źródła zasilania trakcji
```
uint32 count
Powtórzone count razy:
SlimNode node
Vec3 position
float32 nominal_voltage [V]
float32 voltage_frequency [Hz]
f64_disk internal_resistance_legacy
float32 internal_resistance [Ω]
float32 max_output_current [A]
float32 fast_fuse_timeout [s]
float32 fast_fuse_repetition [s]
float32 slow_fuse_timeout [s]
uint8 modifier — Eu7PowerSourceModifier (0=None,1=Recuperation,2=Section)
```
---
## 16. TERR — teren (terrain shapes)
Chunk TERR przechowuje trójkąty terenu. Może się pojawić wielokrotnie (po jednym per materiał lub per sekcja).
```
uint8 flags — bitmaska:
bit 0: translucent
bit 1: non-default lighting (blok lighting poniżej)
bit 2: batched (grupowany per sekcja 1km)
string_id material
[jeśli bit 1 set]:
LightingBlock lighting (48 B)
[jeśli bit 2 set — tryb batched]:
uint32 batch_count
Powtórzone batch_count razy:
int32 section_x_coord — współrzędna X sekcji (odczytane, ale ignorowane przez runtime)
int32 section_z_coord
uint32 vertex_count — musi być wielokrotnością 3
PackedVertex[] vertices — vertex_count wierzchołków
[else — tryb legacy]:
uint32 count — liczba trójkątów
Powtórzone count razy:
PackedVertex[3] vertices — dokładnie 3 wierzchołki (1 trójkąt)
```
> **Uwaga:** tryb batched (bit 2) jest charakterystyczny dla v5+. Pola `section_x_coord` i `section_z_coord` są odczytywane przez reader, ale ich wartości są ignorowane w runtime maszyna-fresh (batche traktowane jako płaskie listy kształtów).
---
## 17. MESH — kształty geometryczne
```
uint32 count
Powtórzone count razy:
uint8 subtype — 0=triangles, 1=triangle_strip, 2=triangle_fan
SlimNode node — typ węzła wyznaczony przez subtype
uint8 translucent — 0/1
string_id material_path
uint8 has_lighting — 0/1
[jeśli has_lighting]:
LightingBlock lighting
Vec3 origin — lokalny punkt odniesienia wierzchołków
uint32 vertex_count
PackedVertex[] vertices
```
Nazwa węzła (`node.node_type`) wyznaczona przez subtype: `"triangles"`, `"triangle_strip"`, `"triangle_fan"`.
---
## 18. LINE — linie
```
uint32 count
Powtórzone count razy:
uint8 subtype — 0=lines, 1=line_strip, 2=line_loop
SlimNode node
uint8 has_lighting — 0/1
[jeśli has_lighting]:
LightingBlock lighting
float32 line_width
Vec3 origin
uint32 vertex_count
Powtórzone vertex_count razy:
Vec3 position — TYLKO pozycja (brak normalnych/UV)
```
---
## 19. MODL — instancje modeli 3D
Używane w submodułach include. W plikach głównych z PACK chunk, MODL z podmodułów jest zazwyczaj pomijane (sterowane flagą `pack_scenery_active()`).
```
uint32 count
Powtórzone count razy:
SlimNode node
uint8 is_terrain — 0/1
uint8 transition — 0/1 (animowany przejazd)
Vec3 location — world-space, 3×float32
Vec3 angles — kąty Eulera (stopnie), 3×float32
Vec3 scale — skala XYZ, domyślnie (1,1,1)
string_id model_file — ścieżka do pliku .e3d
string_id texture_file — override tekstury (opcjonalnie pusty)
uint32 light_count
float32[] light_states — light_count wartości
uint32 color_count
uint32[] light_colors — color_count kolorów RGBA packed
```
### Aplikacja w runtime (maszyna-fresh)
Rekord MODL/PACK nie trafia przez parser tekstowy SCM. Ścieżka **instancingu GPU** (nie per-model drip):
1. `preload_unique_pack_meshes()` — jeden `GetModel()` na unikalny plik `.e3d` w całej sekcji
2. Dla każdego rekordu: `TAnimModel` + `LoadEu7()` (cache hit → szybkie `Init()`)
3. `update_instanceable_flag()` — modele bez świateł/animacji → `m_instanceable = true`
4. `Region->insert()``m_instancebuckets_opaque` → renderer: **`Render_Instanced()`**
PACK **nie** rejestruje instancji w `Instances` / `Hierarchy` (anonimowa sceneria, bez eventów po nazwie).
Deduplikacja meshów: **`TModelsManager`** (współdzielony `TModel3d`). GPU instancing: **`opengl33_renderer::Render_Instanced`** — jeden draw na submodel × N instancji tego samego mesha.
---
## 20. MEMC — komórki pamięci
```
uint32 count
Powtórzone count razy:
SlimNode node
string_id text
f64_disk value1
f64_disk value2
string_id track_name — 0xFFFFFFFF jeśli brak
```
---
## 21. LAUN — wyzwalacze zdarzeń
```
uint32 count
Powtórzone count razy:
SlimNode node
Vec3 location
f64_disk radius_squared [m²]
string_id activation_key_raw
int32 activation_key
f64_disk delta_time [s], -1 = bez limitu
string_id event1_name
string_id event2_name
int32 launch_hour — -1 = bez warunku godzinowego
int32 launch_minute
uint8 has_condition — 0/1
[jeśli has_condition]:
string_id memcell_name
string_id compare_text
f64_disk compare_value1
f64_disk compare_value2
int32 check_mask
uint8 train_triggered — 0/1
```
---
## 22. DYNM — pojazdy dynamiczne
```
uint32 count
Powtórzone count razy:
SlimNode node
string_id data_folder
string_id skin_file
string_id mmd_file
string_id track_name
string_id driver_type
string_id load_type
string_id coupling_params
string_id coupling_raw — tekstowa reprezentacja sprzęgu (np. "3.automat")
f64_disk offset [m od początku toru], -1 = auto
int32 coupling — wartość sprzęgu (0-3)
int32 load_count
float32 velocity [km/h]
uint8 has_destination — 0/1
[jeśli has_destination]:
string_id destination
uint8 has_trainset_index — 0/1
[jeśli has_trainset_index]:
uint32 trainset_index
```
---
## 23. SOND — dźwięki otoczenia
```
uint32 count
Powtórzone count razy:
SlimNode node
Vec3 location
string_id wav_file
```
---
## 24. TRSE — zestawy wagonowe
```
uint32 count
Powtórzone count razy:
string_id name
string_id track
float32 offset [m]
float32 velocity [km/h]
uint32 assignment_count
Powtórzone assignment_count razy:
string_id key
string_id value
uint32 vehicle_count
uint32[] vehicle_indices — indeksy do tablicy DYNM w module
uint32 coupling_count
int32[] couplings
uint32 driver_index — indeks pojazdu-prowadzącego
```
---
## 25. EVNT — zdarzenia
```
uint32 count
Powtórzone count razy:
string_id name
uint8 type — Eu7EventType (patrz tabela)
f64_disk delay [s]
uint32 target_count
string_id[] targets
f64_disk delay_random [s]
f64_disk delay_departure [s]
uint8 ignored — 0/1
uint8 passive — 0/1
uint32 payload_count
Powtórzone payload_count razy:
string_id key
string_id value
```
### Enum Eu7EventType (uint8)
| Wartość | Nazwa |
|---------|---------------|
| 0 | AddValues |
| 1 | UpdateValues |
| 2 | CopyValues |
| 3 | GetValues |
| 4 | PutValues |
| 5 | Whois |
| 6 | LogValues |
| 7 | Multiple |
| 8 | Switch |
| 9 | TrackVel |
| 10 | Sound |
| 11 | Texture |
| 12 | Animation |
| 13 | Lights |
| 14 | Voltage |
| 15 | Visible |
| 16 | Friction |
| 17 | Message |
| 18 | Unknown |
---
## 26. FINT — licznik first-init
Prosty licznik — ile obiektów wymaga obsługi pierwszego inicjowania.
```
uint32 first_init_count
```
---
## 27. PLAC — parametry umieszczenia include
Mapowanie parametrów wywołania include na składowe transformacji.
```
uint8 origin_x_param — numer parametru dla origin.x (0 = brak)
uint8 origin_y_param — numer parametru dla origin.y
uint8 origin_z_param — numer parametru dla origin.z
uint8 rotation_y_param — numer parametru dla rotation.y
```
Gdy wszystkie cztery pola są `0`, plik nie eksponuje parametryzowanego umieszczenia.
---
## 28. PIDX — indeks sekcji PACK (v7+)
Katalog mapujący sekcje 1 km × 1 km na offsety w payloadzie chunku PACK.
```
uint32 count
Powtórzone count razy:
uint16 row — indeks wiersza sekcji (siatka regionu)
uint16 column — indeks kolumny sekcji
uint32 model_count — liczba modeli w tej sekcji
uint64 pack_offset — offset w bajtach od początku payloadu PACK
```
**Uwagi:**
- `pack_offset` jest **względny** od początku payloadu PACK (nie od początku pliku).
- Sekcje bez modeli mogą mieć `model_count = 0` i być pominięte w PIDX.
- Indeksowanie sekcji: `row/column` odpowiadają siatce `kRegionSideSectionCount × kRegionSideSectionCount`, centrum siatki to `(0, 0)` w world-space.
- Runtime oblicza absolutny offset jako: `pack_payload_offset + entry.pack_offset`.
---
## 29. PACK — instancje modeli per sekcja 1 km (v7+)
Payload PACK jest **zbiorem sekcji** ustawionych sekwencyjnie wg offsetów z PIDX. Chunk PACK jest pomijany przy pełnym wczytaniu — reader zapamiętuje jedynie `pack_payload_offset`.
### Format sekcji — v7 (domyślny)
Każda sekcja to `model_count` rekordów `RuntimeModelInstance` (identyczny format jak rekordy MODL):
```
Powtórzone model_count razy:
(pełny rekord RuntimeModelInstance — patrz sekcja 19 MODL)
```
Transformacja węzła (`node.transform`) jest **zerowana** po wczytaniu — dane w PACK są zawsze w world-space (transform był bake'owany przy generacji).
### Format sekcji — v8 (`kPackSectionFormatV8 = 1`)
```
uint8 section_format — musi być = 1
uint32 solo_count — modele pełne (bez prototypu)
uint32 inst_count — instancje odwołujące się do PROT
Powtórzone solo_count razy:
(pełny rekord RuntimeModelInstance, transformacja zerowana)
Powtórzone inst_count razy:
uint32 proto_id — indeks do tablicy PROT
Vec3 location
Vec3 angles
Vec3 scale
string_id name
```
Instancje v8 są rozwijane przez `expand_prototype_instance()` łącząc dane z PROT z lokalizacją instancji.
---
## 30. PROT — prototypy modeli (v8+)
Wspólne definicje modeli (bez informacji o lokalizacji instancji). Muszą być obecne przed odczytaniem sekcji PACK v8.
```
uint32 count
Powtórzone count razy:
SlimNode node — name/range/bounds jak MODL, ale BEZ location/angles/scale
uint8 is_terrain
uint8 transition
string_id model_file
string_id texture_file
uint32 light_count
float32[] light_states
uint32 color_count
uint32[] light_colors
```
Różnica względem MODL: brak pól `location`, `angles`, `scale` — te są zapisane per instancja w PACK.
---
## 31. Semantyka strumieniowania PACK w runtime
Gdy root scenariusz zawiera chunk PACK, silnik maszyna-fresh uruchamia asynchroniczny **sekcja-stream**:
### Parametry strumienia
| Parametr | Wartość | Opis |
|----------------------------|------------------|----------------------------------------------------|
| `kSectionSizeM` | 1000 m | Rozmiar sekcji |
| `kInitialBootstrapRadius` | 3 sekcje | Promień wstępnego ładowania wokół pozycji startowej|
| `kStreamRadius` | 9 sekcji | Bieżący promień streamingu |
| `kMovementLookahead` | 4 sekcje | Prefetch w kierunku jazdy |
| `kMaxInFlightSections` | 72 | Maks. sekcji w toku (backpressure) |
| `kMaxReadySections` | 36 | Maks. gotowych sekcji w kolejce |
| `kBootstrapTimeoutMs` | 120 000 ms | Timeout bootstrapu (tylko ścieżka blokująca) |
W **jazdzie** (`driver_mode`): **jedna sekcja PACK na klatkę**`drain_section_stream()``apply_pending_section()` (cała sekcja naraz, bez fałszywych budżetów ms per model).
### Dysk vs wątek główny
| Etap | Wątek | API |
|------|-------|-----|
| Deserialize MODL z `.eu7` | Worker | `read_pack_section()` |
| Page cache `.e3d` | Worker | `prefetch_pack_models()` |
| `GetModel()` unikalnych meshów + instancje | **Main** | `insert_eu7_pack_models()` |
| GPU draw | **Main** | `Render_Instanced()` dla `m_instanceable` |
`TModel3d::LoadFromFile()` + `GfxRenderer`**tylko main**. W sekcji: najpierw wszystkie unikalne meshe, potem tysiące lekkich `TAnimModel` trafiających do bucketów instancingu.
### Przepływ ładowania
```
1. read_module() → PIDX w RAM, PACK jako offset (seek)
2. init_section_stream() → worker pool
3. preload_section_stream() [loader, blokująco] → bootstrap + drain sekcji
4. W jazdzie (is_ready):
kick_section_stream_bootstrap() — enqueue, bez spin-wait
drain_section_stream() — jedna gotowa sekcja → apply_pending_section()
update_section_stream(camera)
```
### Wątki robocze
Po `read_pack_section()`: `prefetch_pack_models()``PackSectionReady` w kolejce.
### Drain (wątek główny)
`apply_pending_section()`**cała sekcja** w jednym `insert_eu7_pack_models()`:
1. `preload_unique_pack_meshes` — cold load tylko dla nowych `.e3d`
2. Pętla instancji → `Region->insert` → GPU instancing bucket
Jedna sekcja na wywołanie `drain_section_stream()` (jedna klatka = jedna sekcja, nie jeden model).
### Diagnostyka (`eu7_load_stats`)
| Pole | Znaczenie |
|------|-----------|
| `model_ms` / `models` | Czas i liczba `TAnimModel` z PACK |
| `pack_sections_loaded` / `pack_models` | Sekcje / instancje ze streamingu |
Overlay renderera: `inst-pool:` — ile instancji zakwalifikowanych do `Render_Instanced`.
### Pliki implementacji (runtime)
| Temat | Plik |
|-------|------|
| Streaming sekcji | `scene/eu7/eu7_section_stream.cpp` |
| Prefetch dysku | `scene/eu7/eu7_model_prefetch.cpp` |
| Instancje PACK | `simulation/simulationstateserializer.cpp` |
| `LoadEu7` + `m_instanceable` | `model/AnimModel.cpp` |
| `Render_Instanced` | `rendering/opengl33renderer.cpp` |
| Cache meshów | `model/MdlMngr.cpp` |
---
## 32. Historia wersji
| Wersja | Zmiany |
|--------|--------------------------------------------------------------------------------------------------|
| v1 | Format legacy — szczegóły nieokreślone, nieobsługiwany |
| v2 | Skalary i Vec3 jako float64 (8 B) na dysku |
| v3 | Skalary i Vec3 jako float32 (4 B) na dysku — `writeF64()` → rzutuje do float32 |
| v4 | **Slim node** (flagowany nagłówek węzła) + **packed vertex** (snorm16 normy + half16 UV) |
| v5 | Chunk **TERR** z trybem batched (terrain per sekcja 1 km); TERR może być w osobnym pliku `.eu7` |
| v6 | Chunk INCL rozszerzony o **site transform** (pełny TransformContext per include) |
| v7 | Chunki **PIDX + PACK** — modele scenerii podzielone na kafelki 1 km, asynchroniczny streaming |
| v8 | Chunk **PROT** (prototypy) + nowy format sekcji PACK z `solo_count` + `inst_count` |
---
## 33. Ścieżki plików i konwencje nazewnictwa
### Rozszerzenie
Pliki EU7B używają rozszerzenia `.eu7`. Identyfikacja pliku odbywa się przez:
1. Sprawdzenie rozszerzenia (`isSceneBinaryPath()`)
2. Sondowanie magic bytes `EU7B` na pozycji 0 (`probeSceneBinaryMagic()`)
### Konwersja ścieżek source → binary
- `*.scm``*.eu7` (te same katalog i stem)
- `*.inc``*.eu7`
- `*.scn``*.eu7`
- `*.sbt` → terrain `*.eu7` (odpowiednik binarny terenu)
Funkcja `binary_path()` w loaderze zamienia rozszerzenie przy tym samym stemie.
### Warianty pliku terenu
Plik `.eu7` może zawierać chunk TERR (teren). Loader sprawdza obecność TERR przez `probe_terrain_file()` (pełne wczytanie nagłówków).
Funkcja `is_scenario_terrain()` sprawdza czy obok scenariusza `<stem>.scn` istnieje `<stem>.eu7` z chunkiem TERR.
### Pliki include
Submoduły include są wczytywane jeden raz per ścieżka (deduplication przez `is_module_loaded()`). Ścieżka pliku `.eu7` includowanego jest wyznaczana przez `include_eu7_path()` — ten sam katalog co plik SCM/INC.
---
*Dokumentacja wygenerowana na podstawie kodu źródłowego; stan na 11 czerwca 2026.*