# AtomicEngine > Eine 2D-Platformer-Gameengine bei der jeder Pixel wie ein Atom mit eigenen > Eigenschaften und Anziehungskräften zu seinen Nachbarn agiert. > Browser-basiert mit Rust/WASM für die Physik und WebGL2 fürs Rendering. ## Architektur-Übersicht ``` engine/ (Rust → WASM) web/ (TypeScript + Vite) ┌─────────────────────┐ ┌──────────────────────┐ │ grid.rs │ │ main.ts │ │ material.rs │ │ renderer.ts (WebGL2) │ │ physics.rs │ │ input.ts │ │ render_buffer.rs │ │ camera.ts │ │ lib.rs (wasm API) │ │ │ └─────────────────────┘ └──────────────────────┘ │ │ └── SharedArrayBuffer ──────┘ ``` ## Tech-Stack | Schicht | Technologie | Warum | |---------------|--------------------------|------------------------------------| | Physik | Rust → WASM | Performance, Sicherheit | | WASM Bridge | wasm-bindgen, wasm-pack | Auto JS-Bindings | | Renderer | WebGL2 (Canvas) | GPU-beschleunigt, 60fps | | Web Shell | TypeScript, Vite | Build, Dev-Server, HMR | | Speicher | SharedArrayBuffer | Zero-copy WASM ↔ JS | ## Welt-Modell - **Große Welt**: 4096×3072 Pixel (16×12 Chunks à 256×256, tiled 4×4 Szenen) - Nur Zellen im Sichtfeld + 200px Margin werden simuliert - Rest eingefroren — keine CPU-Kosten für unsichtbare Bereiche - Grid wächst auf 4096×3072 = 12.5M Zellen (~48 MB Speicher) ### Datenstruktur Cell (pro Pixel) ```rust struct Cell { material: u8, // Material-ID (0 = Luft) health: u8, // Lebenspunkte/Dichte temp: u8, // Temperatur flags: u8, // Bitflags (aktiv, Player, Flüssig, ...) } ``` Grid ist SoA (Structure of Arrays): - `Vec` für materials, healths, temps, flags - Breite × Höhe = Chunk-Größe ## Material-System 16 Materialien (0=Luft, 1=Stein, 2=Erde, 3=Sand, 4=Wasser, 5=Holz, 6=Lava, 7=Player, 8=Dampf, 9=Feuer, 10=Glas, 11=Eis, 12=Öl, 13=Säure, 14=Funken, 15=Rauch, 16=Eisen). Properties pro Material: `MaterialProps` in `engine/src/material.rs`: - `color`, `density`, Bewegung-Typ (`is_powder/is_liquid/is_gas/is_solid/is_static`) - `flammability`, `melt_temp`, `boil_temp`, `heat_conduct`, `acid_resist` - `light_emit` (0–255, wie viel Licht das Material abstrahlt) - `light_block` (0–255, wie stark das Material Licht blockiert) ### Interaktions-System **Heat-Transfer**: Temperatur-Diffusion über Moore-Nachbarn (`heat_conduct`-abhängig). **Phase-Transformationen** (`material::transform`): - Eis (11) → Wasser (4) bei temp ≥ 5°C - Wasser (4) → Dampf (8) bei temp ≥ 100°C - Dampf (8) → Wasser (4) bei temp ≤ 80°C - Lava (6) → Stein (1) bei temp ≤ 5°C - Holz (5) → Feuer (9) bei temp ≥ 240°C - Öl (12) → Feuer (9) bei temp ≥ 120°C - Sand (3) → Glas (10) bei temp ≥ 200°C - Glas (10) → Lava (6) bei temp ≥ 160°C - Eisen (16) → Lava (6) bei temp ≥ 150°C - Stein schmilzt nicht (hält Lava auf) **Reaktionen** (`physics::react`): - Wasser + Lava → Dampf + Stein - Wasser + Feuer → Dampf + Luft (löscht Feuer) - Lava + Holz/Öl → entzündet Holz/Öl sofort zu Feuer - Säure greift ALLE Materialien via `acid_resist` an (Health-basiert, nicht Instant-Delete): - resist < 100 (Holz 50, Erde 70): ~2s/Zelle - resist 100–200 (Sand 150, Stein 180): ~4s/Zelle - resist ≥ 200 (Eis, Öl): ~13s/Zelle - resist = 255 (Eisen, Glas, Wasser, Lava): immun **Feuer-System**: - Braucht Brennstoff (Holz, Öl) um zu überleben — prüft direkt unter sich + Moore-Nachbarn - Feuer-Säule: Hat eine Zelle Feuer unter sich, gilt sie als versorgt (Ketten-Propagation) - Mit Brennstoff: keine Health-Decay - Ohne Brennstoff: −6hp/Frame → erlischt in ~7 Frames (~0.1s) - Brenn-Rate `flammability`-abhängig: `300/flam` Frames zwischen −1hp-Konsum - Holz (30): ~10 Frames/Konsum → ~42s pro Block - Öl (80): ~3 Frames/Konsum → ~13s pro Block - Ausbreitung: 1/250 Chance auf brennbare Nachbarn - Wasser/Eis in Nachbarschaft → Feuer erlischt sofort - Erzeugt Funken (14) und Rauch (15) beim Brennen - **Flammen-Züngeln**: 1/5 Chance pro Zelle, Funken-Partikel nach oben zu feuern (4–12 Frames Lebensdauer) - **Liquid-Momentum**: FLAG_MOMENTUM auf fallendem/sloshendem Wasser, waves klettern an Schalenwänden hoch - **Flammen-Züngeln**: 1/5 Chance pro Zelle, Funken-Partikel nach oben zu feuern (4–12 Frames Lebensdauer) **Ephemere Partikel** (Funken + Rauch): - `update_ephemeral`: dekrementiert Health, bei 0 → Luft - Flammen-Funken: 4–12 Frames, 1/5 Emission, kurzlebig für Flammenspitzen - Funken: 30–80 Frames Lebensdauer, orange, 1/16 Emission/Frame - Rauch: 140–220 Frames, hellgrau transparent, 1/6 Emission/Frame - Gas-Pass separat (alle 2 Frames, top→bottom = max 1px/Frame Aufstieg) ## Physik-Loop (pro Frame) Reihenfolge in `physics::update(cam_x, cam_y, rw, rh, zoom)`: Nur Zellen innerhalb **Kamera-Sichtfeld + 200px Margin** werden simuliert. Rest der Welt ist eingefroren (keine CPU-Kosten). 1. **update_fire** — Feuer-Update: Brennstoff-Verbrauch, Ausbreitung, Funken/Rauch-Emission, Flammen-Züngeln 2. **Haupt-Loop** (bottom→top, alternierende Spalten, nur aktive Region): - Heat-Transfer über Moore-Nachbarn - Material-Reaktionen (Water+Lava, Säure+Holz, ...) - Phasen-Transformation (Temp-basiert) - Bewegung: Powders ↓, Liquids ↓↔, Solids ↓ (kein Gas!) 3. **update_gases** (top→bottom, nur jedes 2. Frame, nur aktive Region): Gas + Flammen-Funken steigen max 1px/Frame 4. **update_ephemeral** (nur aktive Region): Spark/Smoke Health dekrementieren, bei 0 → Luft ## Level-Format RGBA-PNG, jeder Pixel = 1 Atom: - R = Material-ID (0=Luft) - G = Dichte/Gesundheit - B = Temperatur - A = Flags Levels in Aseprite/Photoshop malbar. Große Welten = Raster von PNG-Dateien. ## Rendering 1. WASM schreibt sichtbaren Bereich als RGBA-Buffer 2. Buffer als Uint8Array aus WASM-Speicher gelesen 3. WebGL2 lädt als Textur → Fullscreen-Quad 4. Kamera-Matrix für Scroll/Zoom ### Textur-System Jedes Material bekommt deterministische Pixel-Variation basierend auf Grid-Koordinaten: - **Holz**: Vertikale Maserung (dünne Fasern alle ~5px, ±1px Welle) - **Erde**: Grobkörnige Flecken (niederfrequentes Rauschen, ±20 RGB) - **Stein/Glas**: Subtiles Rauschen (±10 RGB) - **Sand**: Feines Granulat (±6 RGB) - **Eis**: Minimales Rauschen (±5 RGB) - **Flüssigkeiten/Gase**: Keine Textur - **Feuer/Lava**: Kanal-getrennte Varianz (R ±6, G ±12, B ±8) — natürliche Gelb-/Orange-Mischung ### Beleuchtungs-System **Light-Propagation** in `render_buffer.rs`: 1. Emissions-Pass: Jedes Pixel mit `light_emit > 0` strahlt Licht 2. Flood-Fill (max `16/zoom` Passes, early-termination): 8-Richtungs-Propagation - Falloff: `(2|3)/zoom` (kardinal/diagonal) + `light_block` (gecapped bei 200) - Opaque Pixel blocken Weitergabe, werden aber selbst beleuchtet 3. Abwechselnde Scan-Richtung für gleichmäßige Verteilung 4. Zoom-skaliert: Passes + Falloff passen sich an Kamera-Zoom an **Lichtquellen**: Feuer (255), Lava (200), Funken (180) **Glow-Effekt**: Luft-Pixel mit Licht blenden von dunklem Hintergrund zu warmem Orange — quadratische Kurve (`t = lvl²/255`) für natürlichen Abfall. Nur direkt an der Quelle stark sichtbar. **Reflexion**: Semi-transparente Materialien (Glas, Wasser, Eis — block 1–179) werfen 50% des empfangenen Lichts an Nachbar-Pixel zurück → indirekte Beleuchtung. **Beleuchtungs-Formel** (Material-Pixel): Additiv: `r = min(255, r × 150/256 + light)` — Ambient-Basis 59% + Licht obendrauf. **Hintergrund-Gradient**: Blauer Himmel (`[80,140,220]`) von y=0–600, fließender Übergang zu dunkel (`[20,20,30]`) bei y=600–700. Untergrund ab y=700 dunkel. **Schatten**: Experimentell versucht (Platform-Schatten, Sonnen-Raycasting), aktuell ausgebaut. Neu-Ansatz für nächste Session. ### UI - **Material-Palette**: Leiste am unteren Bildschirmrand, Farb-Swatch + Name + Taste - **FPS-Anzeige**: Oben rechts, grün, alle 500ms aktualisiert ### Player-System (Phase 4 — in Arbeit) Player-Material (7) existiert als grüner Pixel-Cluster. `move_player(dx, dy)`-API in `physics.rs` via WASM exportiert (`engine.movePlayer`). Aktueller Ansatz: Clear+Place (alle Player-Zellen löschen, an Zielposition neu setzen), all-or-nothing bei Kollision mit Solids. Gravitation: `dy=1` jede Frame. Horizontal-Input: Pfeiltasten separat. Kamera: folgt Player-Zentrum (Scan 200×200 Region um letzte Position, cached). Offen: Cluster-Form stabil halten (kreisrund), Sprung-Mechanik, Schadenssystem. ## Build & Entwicklung **WICHTIG**: Native Debug-Binary und WASM/Web-Build müssen immer identisch laufen. Der Debug-Build nutzt dieselben Module (`grid`, `material`, `physics`, `render_buffer`) und dieselben Dimensionen (256×192 Grid, 320×180 Render-Buffer). Performance-Unterschiede sind WASM-Overhead, nicht Logik-Abweichungen. Bei Änderungen an der Engine immer beide Builds testen. ```bash # Rust → WASM bauen cd engine && wasm-pack build --target web --out-dir ../web/pkg # Native Debug-Binary (x86, ohne WASM) cd engine && cargo run --bin atomic-debug --release # Web Dev-Server cd web && npm run dev # Produktion cd web && npm run build ``` ## Datei-Index (was findet man wo) | Was | Datei | |----------------------------------|-----------------------------------| | WASM öffentliche API | `engine/src/lib.rs` | | Native Debug Binary | `engine/src/main.rs` | | Cell + Chunk + Grid DS | `engine/src/grid.rs` | | Material-Definitionen | `engine/src/material.rs` | | Physik: Kräfte, Sand, Fluide | `engine/src/physics.rs` | | RGBA-Buffer Export | `engine/src/render_buffer.rs` | | Rust-Abhängigkeiten | `engine/Cargo.toml` | | WebGL2 Renderer | `web/src/renderer.ts` | | WASM-Bridge + Material-Konstanten| `web/src/engine.ts` | | Game-Loop, Wiring | `web/src/main.ts` | | Input (Keyboard, Maus) | `web/src/input.ts` | | Kamera (Scroll, Zoom) | `web/src/camera.ts` | | HTML Einstieg | `web/index.html` | | Vite Konfiguration | `web/vite.config.ts` | | TypeScript Konfiguration | `web/tsconfig.json` | ## Aktueller Status - [x] Phase 0: Projekt-Struktur + WASM/WebGL End-to-End ← **fertig** - [x] Phase 1: Sand-Physik (Pixel fällt nach unten) ← **fertig** (Sand + Flüssigkeiten) - [x] Phase 2: Mehrere Materialien + Kräfte-Tabelle ← **fertig** - [x] Phase 3: Chunk-System + große Welt + Kamera ← **fertig** - [ ] Phase 4: Spieler als Atom-Cluster + Input ← **angefangen** — Player-Material (7) existiert, `move_player(dx,dy)` API da, aber Cluster-Zusammenhalt + Steuerung noch instabil. Ansatz: Player-Zellen als Gruppe bewegen (clear+place, nicht swap), Gravitation separat von Horizontalbewegung, Kamera folgt Player-Zentrum. - [ ] Phase 5: Erweiterungen (Items, Player-Interaktion, Editor, ...) ## Namenskonventionen - Rust: snake_case, English - TypeScript: camelCase, English - Keine Kommentare im Code (nur wenn explizit gewünscht)