docs: update AGENTS.md and PLAN.md with all recent changes

AGENTS.md:
- Multi-layer world (temp/gas/pressure/light) fully documented
- Audio system (15 sounds, rodio, overlapping playback)
- UI layer (flat Vec, procedurally scalable font)
- Uniform tick rate (all loaded chunks active every tick)
- Fire propagation (updated_this_tick prevents chain reactions)
- Surface terrain (low-frequency noise, wide rolling hills)
- Chunk streaming (radius 2, all active, save every 10 ticks)
- Dirty rects (no size limit, full processing)
- cells_swap (split_at_mut optimization)
- Module layout updated with audio/mod.rs
- AI spectrums: gas + pressure added

PLAN.md:
- Phase 1 (combat): all checked off
- Phase 1.5 (UI): most items checked off
- Phase 3 (RPG): stats/inventory/XP/status effects checked off
- Phase 5: audio checked off
- Phase 6 (multi-layer): marked DONE
- Milestones updated through 0.55
- Numbers: 9000 lines, 185 tests, 130-150 FPS
This commit is contained in:
Emil
2026-06-22 17:16:47 +03:00
parent 22de8bc42e
commit f09c44f7a0
2 changed files with 82 additions and 62 deletions
+20 -12
View File
@@ -27,10 +27,12 @@ Rust edition 2024, requires rustc >= 1.96. Vulkan 1.2+ required for `ascii`/`gra
- **AI observation** uses multi-spectrum ASCII layers via pipe protocol:
- `materials` — material type per cell
- `temperature` — heat levels encoded as characters
- `light` — light intensity per cell
- `light` — light intensity per cell (world-space fallback)
- `entities` — entity positions and types only
- `density` — material density visualization
- `velocity` — entity movement speed and CA activity
- `gas` — gas type + density per cell (NEW)
- `pressure` — pressure levels per cell (NEW)
## Tape System
@@ -44,7 +46,7 @@ Each tape frame contains:
- Tick number, depth, kills, score
- Camera position
- Player HP, position, entity count
- All 6 spectrum layers as ASCII text
- All 8 spectrum layers as ASCII text
Pipe protocol spectrum commands:
- `{"cmd":"get_spectrum","spectrum":"materials","w":80,"h":25}` — single spectrum
@@ -107,7 +109,7 @@ SPV files are committed. `include_bytes!` embeds them at compile time.
**Source of truth**: `ChunkedGrid` of `Cell` structs with parallel per-chunk layer arrays. Replaces the old fixed-size `Grid` with dual storage:
- **Bounded mode**: `Vec<Chunk>` for deterministic 250x250 test/AI grids.
- **Infinite mode**: `HashMap<(i64, i64), Chunk>` for continuous 12500x12500 cell (100000x100000 px) Noita-scale worlds.
- **Infinite mode**: `HashMap<(i64, i64), Chunk>` for continuous Noita-scale worlds.
Main game (`--mode terminal`, `--mode ascii`, `--mode graphics`) uses the infinite mode. Each `Cell` stores `material`, `fg`/`bg` color, `variant` inline (9 bytes, no temp). Temperature, gas, pressure, and light are stored in parallel arrays per chunk. Chunks are 64x64 cells with `active`, `modified`, `was_modified`, `generated`, and `dirty` flags.
@@ -120,10 +122,12 @@ Main game (`--mode terminal`, `--mode ascii`, `--mode graphics`) uses the infini
Layer access via `grid.get_temp()`/`set_temp()`, `grid.get_gas()`/`set_gas()`, `grid.get_pressure()`/`set_pressure()`, `grid.get_light()`/`set_light()`. All `set_*` methods call `mark_dirty()` (shared dirty rect). `cells_swap` swaps all layers. `set_material` also sets `default_temp()`.
**Simulation steps** per `fixed_update()`: `apply_gas_damage()``ca.step()` (material CA + heat_transfer + gas_step + pressure_step + light_step) → entity updates → combat → status effects. The `ca.step()` saves pre-clear dirty rects and passes them to layer steps so heat/gas/pressure can diffuse beyond CA-active cells.
**Simulation steps** per `fixed_update()`: `apply_gas_damage()``ca.step()` (material CA + heat_transfer + gas_step + pressure_step + light_step) → entity updates → combat → status effects. The `ca.step()` saves pre-clear dirty rects and passes them to layer steps so heat/gas/pressure can diffuse beyond CA-active cells. All loaded chunks are activated every tick — uniform simulation rate across the world.
**Serialization**: Multi-section chunk format with `VWM1` magic header. Sections: cells (8 bytes × 4096), temps (4 × 4096), gas (2 × 4096), pressure (1 × 4096), light (3 × 4096). Old 12-byte format auto-detected and loaded for backward compat.
**Audio** (`src/audio/mod.rs`): `AudioEngine` using `rodio` for WAV playback. 15 procedurally generated sounds (jump, shoot, hit, explosion, death, pickup, descend, powerup, step, lava_bubble, acid_sizzle, water_splash, fire_crackle, ui_click, goblin_growl) embedded via `include_bytes!`. Each sound creates its own `Sink` + `detach()` for overlapping playback. Ambient sounds scan 15-cell radius around player. `M` key toggles audio. Gracefully degrades when no audio device available.
**Four entity kinds**: `Player`, `Goblin`, `Slime`, `Corpse`. Three physics types: cellular (CA materials in grid), rigid (alive entities, AABB + slope stepping), ragdoll (corpses, Verlet constraints).
**Three render modes**: `terminal` (crossterm ANSI), `ascii` (Vulkan glyph atlas + instanced), `graphics` (Vulkan colored quads). All three read the same `ChunkedGrid` + `EntityManager` and an optional `LightGrid` overlay. GPU renderers upload a viewport-relative grid buffer and index it in shaders with `cam_pos`.
@@ -149,11 +153,11 @@ Layer access via `grid.get_temp()`/`set_temp()`, `grid.get_gas()`/`set_gas()`, `
**Items & inventory**: `Item` (weapon, armor, consumable), `ItemManager`, and inventory/equipment slots on `Player`. Items spawn in the world and are picked up on contact. `Game::use_item(0)` equips weapons/armor or consumes potions; `Game::drop_item(0)` returns an item to the world. Equipped weapon adds damage bonus to projectiles; equipped armor reduces enemy contact damage.
**UI layer**: `ui::UiLayer` overlays non-destructive UI on all renderers. Health bars above entities, bottom-line HUD, scrolling message log, floating damage numbers, screen-edge indicators, death screen, entity labels, status icons, minimap, and a character panel. UI is drawn unlit on top of the world. In GPU (`ascii`/`graphics`) and capture modes the UI is rendered as a separate 2x2 pixel-per-cell pass; terminal mode renders UI at full character size.
**UI layer**: `ui::UiLayer` overlays non-destructive UI on all renderers. Uses a flat `Vec<Option<UiCell>>` array (not HashMap) for O(1) cell access. `font_scale` auto-computed from screen height — text, panels, health bars, and minimap scale proportionally. Health bars above entities, bottom-line HUD, scrolling message log, floating damage numbers, screen-edge indicators, death screen, entity labels, status icons, minimap (sampled with step_by(4)), and a character panel. UI is drawn unlit on top of the world. In GPU (`ascii`/`graphics`) and capture modes the UI is rendered as a separate 2x2 pixel-per-cell pass; terminal mode renders UI at full character size.
**Chunk system**: `ChunkedGrid` is divided into 64x64 `Chunk`s. Each chunk tracks `active`, `modified`, `was_modified`, `generated`, and `dirty` (an optional bounding rect of cells that need processing). `save_chunk(path, cx, cy)` and `load_chunk(path, cx, cy)` serialize chunk cells via 12-byte binary format. Cell serialization is handled by `Cell::to_bytes()` / `Cell::from_bytes()`. `Chunk::generated` is set when a chunk is generated or loaded from cache, preventing accidental regeneration.
**Chunk system**: `ChunkedGrid` is divided into 64x64 `Chunk`s. Each chunk tracks `active`, `modified`, `was_modified`, `generated`, and `dirty` (an optional bounding rect of cells that need processing). `save_chunk(path, cx, cy)` and `load_chunk(path, cx, cy)` serialize chunk data via multi-section `VWM1` binary format. Cell serialization is handled by `Cell::to_bytes()` / `Cell::from_bytes()` (8 bytes/cell). `Chunk::generated` is set when a chunk is generated or loaded from cache, preventing accidental regeneration.
**Dirty rect optimization** (Noita-style): Each chunk maintains a `dirty: Option<(i32, i32, i32, i32)>` bounding rect of cells that need CA processing. When a cell changes via `set`, `set_material`, `cells_swap`, or `set_cell_index`, the chunk's dirty rect is expanded to include that cell ±1 (for neighbor influence). The CA step only iterates cells within dirty rects, skipping chunks with no dirty rect entirely. Liquids and gases (water, lava, acid, steam, fire, smoke) re-mark themselves dirty after processing so they continue to flow and react. Sand and other solids can sleep when at rest. `heat_transfer` only processes cells within dirty rects and reuses its temperature buffer across frames. `update_active_chunks` activates chunks with dirty rects in addition to chunks near entities. This reduces CA step time from ~500μs to ~25μs on a 250x250 grid.
**Dirty rect optimization** (Noita-style): Each chunk maintains a `dirty: Option<(i32, i32, i32, i32)>` bounding rect of cells that need CA processing. When a cell changes via `set`, `set_material`, `cells_swap`, or `set_cell_index`, the chunk's dirty rect is expanded to include that cell ±1 (for neighbor influence). The CA step only iterates cells within dirty rects, skipping chunks with no dirty rect entirely. Liquids and gases (water, lava, acid, steam, fire, smoke) re-mark themselves dirty after processing so they continue to flow and react. Sand and other solids can sleep when at rest. All dirty rects are processed fully — no size limits — ensuring uniform simulation across chunks. `update_active_chunks` activates all loaded chunks every tick for consistent physics.
**World cache**: main game seeds are saved in `Game::seed` and written to `cache/worlds/<seed>/`. Each cached world stores per-chunk binary files plus a `meta.json` with player spawn and item placement. `Game::init_world()` loads the cache if it exists; otherwise it generates the spawn region and saves it. This makes Noita-scale worlds load instantly after the first visit.
@@ -162,7 +166,7 @@ Layer access via `grid.get_temp()`/`set_temp()`, `grid.get_gas()`/`set_gas()`, `
- `2 <= cy < 6` — caves (stone, CA-carved empty space, lava/water/acid pools)
- `cy >= 6` — dungeon (BSP rooms, corridors, stone walls)
**Chunk streaming**: `Game::stream_chunks()` is called every `fixed_update`. It loads cached chunks (or generates new ones) in a 3-chunk radius around the player, saves modified chunks beyond that radius, and unloads distant chunks. Streaming is active for infinite grids and for bounded grids larger than 2048×2048; test/AI 250×250 grids skip streaming.
**Chunk streaming**: `Game::stream_chunks()` is called every `fixed_update`. It loads cached chunks (or generates new ones) in a 2-chunk radius around the player, saves modified chunks beyond that radius every 10 ticks (1 chunk at a time), and unloads distant chunks every 120 ticks. All loaded chunks are activated for uniform simulation. Streaming is active for infinite grids; test/AI 250×250 grids skip streaming.
**Vertical descent**: `MaterialId::Stairs` is a solid feature material. Player stands on stairs and presses `>` to descend. `Game::descend()` increments depth, resets the world, and respawns the player at the top. HUD shows current depth.
@@ -181,6 +185,9 @@ Layer access via `grid.get_temp()`/`set_temp()`, `grid.get_gas()`/`set_gas()`, `
- **Random seeding** — `Game::new()` uses a fixed seed for tests/AI sessions; `Game::new_random()` seeds from system time and is used by terminal/ascii/graphics modes. Cached worlds are keyed by seed.
- **Dirty rects** — `ChunkedGrid::mark_dirty(x, y)` expands the chunk's dirty rect to include (x,y) ±1 and propagates to neighbor chunks at boundaries. `cells_swap` and `set_cell_index` call `mark_dirty` automatically. `set` and `set_material` also call `mark_dirty`. The CA step clears each chunk's dirty rect at the start of processing and rebuilds it from cell modifications during processing.
- **World scale**: `WORLD_SCALE = 5` in `worldgen.rs` — all world features (trees, pools, walls, dunes, rooms, corridors, terrain amplitude) are multiplied by this factor. Change this constant to adjust entity-to-world size ratio.
- **Surface terrain**: Multi-octave sine noise with low frequencies (base 0.012, detail 0.04, micro 0.11) scaled by `WORLD_SCALE`. Produces wide rolling hills (~260 cell wavelength) instead of narrow peaks.
- **Uniform tick rate**: All loaded chunks simulate every tick — no chunk is frozen or partially updated. `cells_swap` uses `split_at_mut` for direct chunk array access in bounded mode.
- **Fire propagation**: Newly ignited cells get `updated_this_tick = true` to prevent chain reactions within a single tick. Fire spreads 1 cell/tick (linear, not exponential).
## Module Layout
@@ -190,12 +197,13 @@ src/
lib.rs # pub mod declarations
game.rs # Game struct, world gen, fixed_update, collision, combat, slime AI
input.rs # Terminal input (crossterm, InputHandler) — terminal mode only
audio/mod.rs # AudioEngine (rodio), 15 embedded WAV sounds, ambient scanning
world/ # Cell, MaterialId, MaterialRegistry, ChunkedGrid, Grid (legacy), Chunk, CellularAutomaton, WorldGenerator, WorldCache
physics/ # VerletSolver, SubBody (with color field), Constraint, resolve_grid_collision
physics/ # VerletSolver, SubBody (with color field), Constraint, resolve_grid_collision, ProjectileManager
entity/ # Entity (rigid/ragdoll), EntityManager, Player, BodyTemplate, Item, ItemManager
render/ # terminal.rs, vulkan.rs (ASCII), graphics.rs (cells), lighting.rs, window_input.rs
ai/ # GameSession, AiAction, pipe protocol, replay, scenarios
ui/ # UiLayer, HUD, messages, damage numbers, edge indicators
render/ # terminal.rs, vulkan.rs (ASCII), graphics.rs (cells), lighting.rs, window_input.rs, capture.rs
ai/ # GameSession, AiAction, pipe protocol, replay, scenarios, spectrum
ui/ # UiLayer (flat Vec), HUD, messages, damage numbers, edge indicators, scalable font
```
## PLAN.md
+62 -50
View File
@@ -10,50 +10,60 @@
| System | Status | Details |
|--------|--------|---------|
| Cellular automaton | Working | 14 materials: sand, water, stone, lava, wood, flesh, bone, steam, fire, acid, smoke, grass, dirt, empty |
| Rigid entities | Working | AABB collider, slope stepping, 27 sub-bodies (5x5 + arm), player + goblins |
| Multi-layer world | Working | Temperature, gas (smoke/poison/CO2/steam), pressure, world-space light — parallel per-chunk arrays |
| Rigid entities | Working | AABB collider, slope stepping, 70 sub-bodies, player + goblins + slimes |
| Ragdoll corpses | Working | Verlet constraints, death = rigid→ragdoll transition with inherited velocity |
| Terminal renderer | Working | Full terminal size, ANSI truecolor, diff-based rendering |
| ASCII renderer (Vulkan) | Working | ash + winit, instanced rendering, glyph atlas, 16x16 square cells, adaptive viewport |
| ASCII renderer (Vulkan) | Working | ash + winit, instanced rendering, glyph atlas, 8x8 cells, adaptive viewport |
| Graphics renderer (Vulkan) | Working | ash + winit, colored cells (no glyphs), each material = unique base color, adaptive viewport |
| AI pipe protocol | Working | JSON stdin/stdout, 16 commands, full state export |
| Test framework | Working | 109 Rust tests + 14 JSON scenarios, all passing |
| AI pipe protocol | Working | JSON stdin/stdout, 16 commands, full state export, 8 spectrums (materials/temp/light/entities/density/velocity/gas/pressure) |
| Audio | Working | 15 procedurally generated sounds via rodio, overlapping playback, ambient scanning, M to toggle |
| UI layer | Working | Procedurally scalable font, flat Vec array, health bars, HUD, minimap, inventory overlay, character panel |
| Test framework | Working | 185 Rust tests + 14 JSON scenarios, all passing |
| Replay system | Working | Seeded determinism, record/playback, play_until_tick |
| World generation | Working | Procedural chunk-based biomes: surface (noise), caves (CA), dungeons (BSP); vertical biome progression by chunk Y; 12500×12500 continuous world; chunk streaming + cache |
| World generation | Working | ×5 scale: trees 30 cells, pools r20, BSP rooms 20×20, corridors 5 wide; chunk streaming + cache |
| Cross-platform | Working | Windows/Linux/macOS via winit + ash_window, no platform-specific code |
| Adaptive viewport | Working | Window resize → more/fewer cells visible, cells stay 16x16 pixels |
| Per-cell color (reality layer) | Working | Each cell stores fg/bg color inline, no registry lookup in render path |
| Adaptive viewport | Working | Window resize → more/fewer cells visible, cells stay 8x8 pixels |
| Per-cell color | Working | Each cell stores fg/bg color inline, no registry lookup in render path |
| Performance | Working | 130-150 FPS (graphics), uniform tick rate across all chunks |
### Architecture
```
Source of truth: `ChunkedGrid` of `Cell` structs
Source of truth: `ChunkedGrid` of `Cell` structs with parallel layer arrays
- Bounded mode: `Vec<Chunk>` for 250x250 test/AI grids
- Infinite mode: `HashMap<(i64, i64), Chunk>` for continuous 12500x12500 cell worlds
- 64x64 chunks, dirty rects, active flags, per-chunk persistence
- Chunk streaming: load/generate around player, save/unload distant chunks
- Infinite mode: `HashMap<(i64,i64), Chunk>` for continuous worlds
- 64x64 chunks, dirty rects, all loaded chunks active every tick
- Multi-layer: temps (f32), pressure (u8), gas_type+density (u8×2), light ([u8;3])
- Cell: 9 bytes (material + variant + fg/bg, no temp)
- Serialization: VWM1 multi-section format, backward compat with old 12-byte
World scale: WORLD_SCALE=5, adjustable via single constant
Surface: multi-octave sine noise, wide rolling hills (~260 cell wavelength)
Three entity types:
1. Cellular — materials in grid, per-cell CA rules
2. Rigid — alive entities, AABB collider, slope stepping, single velocity
3. Ragdoll — corpses, loose Verlet bodies, independent physics
Game loop: fixed 60Hz timestep
Physics tick: CA step → rigid update (slope step)ragdoll update → damage
Render: terminal (ANSI) / ascii (Vulkan glyphs) / graphics (Vulkan cells) / pipe (JSON) / headless (file)
Game loop: fixed 60Hz timestep, uniform across all chunks
Physics tick: CA step → heat_transfer → gas_step → pressure_steplight_step
→ entity updates → combat → status effects → gas damage
Render: terminal (ANSI) / ascii (Vulkan glyphs) / graphics (Vulkan cells) / pipe (JSON)
Three render modes:
--mode terminal → pure ANSI ASCII in terminal
--mode ascii → Vulkan window with ASCII characters (glyph atlas)
--mode graphics → Vulkan window with colored cells (no glyphs, material base colors)
Audio: 15 embedded WAV sounds via rodio, overlapping playback, ambient scanning
UI: flat Vec array, procedurally scalable font, non-destructive overlay
```
### Numbers
- ~6700 lines Rust
- 171 integration tests, 14 JSON scenarios
- 40+ git commits
- ~9000 lines Rust
- 185 integration tests, 14 JSON scenarios
- 50+ git commits
- 0 compiler warnings (excluding winit deprecation notices)
- Cross-platform: Windows/Linux/macOS
- 15 embedded sound effects
- 130-150 FPS (graphics mode, release, ×5 world scale)
---
@@ -71,17 +81,17 @@ loop is: aim → shoot → projectile travels → hits enemy → damage/kill.
- [x] Knockback: damage applies velocity impulse to rigid body center
- [x] Goblin AI: move toward player, attack when adjacent
- [x] Slime AI: jump toward player, contact damage
- [ ] **Projectile system**: lightweight rigid bodies (arrows, fireballs, magic bolts)
- [x] **Projectile system**: lightweight rigid bodies (arrows, fireballs, magic bolts)
- Player shoots with directional input (mouse aim or movement-direction)
- Projectile = small AABB, velocity, damage, lifetime
- On hit with entity: deal damage, destroy projectile
- On hit with solid cell: stop/destroy
- Fireball: ignites materials it touches (Noita-style material interaction)
- Magic bolt: pure damage, no material effect
- [ ] Health bars in render (colored indicator above entity)
- [ ] Death → ragdoll → corpse decomposition (flesh cells drop into grid over time)
- [ ] Material interaction with entities: entity walks through fire → ignites, acid → dissolves
- [ ] Goblin AI: flee when low HP
- [x] Health bars in render (colored indicator above entity)
- [x] Death → ragdoll → corpse decomposition (flesh cells drop into grid over time)
- [x] Material interaction with entities: entity walks through fire → ignites, acid → dissolves
- [x] Goblin AI: flee when low HP
**Tests needed:**
- Projectile travels and deals damage on hit
@@ -106,34 +116,34 @@ Render pipeline per frame:
3. Present to screen
```
- [ ] `UiLayer` struct: sparse map of (screen_x, screen_y) → (char, fg_color, bg_color)
- UI elements write to this map, not to the grid
- [x] `UiLayer` struct: flat `Vec<Option<UiCell>>` array for O(1) cell access
- UI elements write to this array, not to the grid
- Renderer composites: if UiLayer has a cell at (x,y), it overrides the world cell visually
- World state is never modified by UI
- [ ] Health bar: colored bar above player entity, shows current/max HP
- [x] Health bar: colored bar above player entity, shows current/max HP
- ████░░░░ style, colored green→yellow→red by HP ratio
- Positioned relative to player's screen position, scrolls with camera
- [ ] Entity labels: small text above/below entities (name, level for RPG)
- [ ] Status effect icons: burning 🔥, poisoned, frozen — shown next to entity
- [ ] HUD bar (bottom of screen, non-destructive):
- [x] Entity labels: small text above/below entities (name, level for RPG)
- [x] Status effect icons: burning, poisoned, frozen — shown next to entity
- [x] HUD bar (bottom of screen, non-destructive):
- HP: ████████░░ 80/100
- Material brush: [Sand] (current selected)
- Tick: 1234 Depth: 1
- FPS counter (debug mode)
- [ ] Tooltip on hover: when cursor is over a cell, show material name + temperature
- [ ] Message log (top of screen, scrolling): "Goblin hits you for 10 damage"
- [x] Message log (top of screen, scrolling): "Goblin hits you for 10 damage"
- Last N messages, older ones fade (dimmer color)
- [ ] Inventory overlay (toggle with 'i'): semi-transparent panel, doesn't modify world
- [x] Inventory overlay (toggle with 'i'/Tab): semi-transparent panel, doesn't modify world
- List of items, selected highlight, weight/value display
- Opens/closes without affecting simulation
- [ ] Menu system (pause, settings, save/load): full-screen overlay with border
- Game loop pauses (or continues in background), UI captures input
- [ ] Minimap (corner of screen): compressed world view, explored areas only
- [x] Minimap (corner of screen): compressed world view, explored areas only
- Each minimap cell = 5x5 world cells, averaged material color
- Player position marker, entity dots
- [ ] Crosshair/targeting: when aiming projectiles, shows trajectory preview
- [ ] Damage numbers: floating text above entities when hit, rises and fades
- [ ] Screen-edge indicators: arrows pointing to off-screen entities of interest
- [x] Damage numbers: floating text above entities when hit, rises and fades
- [x] Screen-edge indicators: arrows pointing to off-screen entities of interest
**Key principle: UI layer NEVER writes to grid, entities, or any game state.
It reads state and renders visuals on top. This keeps the source of truth clean
@@ -180,18 +190,18 @@ instanced quads with UI texture coordinates. Transparent background, drawn on to
**Goal: character progression, inventory, abilities**
- [ ] Stats: strength, agility, toughness, willpower — affect damage, speed, HP, etc.
- [ ] Inventory system: items as data structs, pick up by walking over, drop with key
- [ ] Equipment: weapon affects melee damage/range, armor affects damage reduction
- [ ] Items in world: weapons, potions, scrolls, food — rendered as distinct ASCII chars
- [x] Stats: strength, agility, toughness, willpower — affect damage, speed, HP, etc.
- [x] Inventory system: items as data structs, pick up by walking over, drop with key
- [x] Equipment: weapon affects melee damage/range, armor affects damage reduction
- [x] Items in world: weapons, potions, scrolls, food — rendered as distinct ASCII chars
- [ ] Mutations (Caves of Qud style): modify entity properties
- "Silicon skin" → entity material becomes Stone, immune to acid
- "Flame body" → entity emits fire cells, immune to fire
- "Liquid form" → entity can squeeze through 1-cell gaps
- "Multiple arms" → extra attack, can hold more items
- [ ] XP and leveling: kill entities → gain XP → level up → choose mutation
- [x] XP and leveling: kill entities → gain XP → level up → choose mutation
- [ ] Skills: active abilities on cooldown (dash, stomp, material blast)
- [ ] Status effects: burning, poisoned, frozen, bleeding — each with tick effect
- [x] Status effects: burning, poisoned, frozen, bleeding — each with tick effect
- [ ] Dialogue: talk to NPCs, simple text tree
**Tests needed:**
@@ -277,17 +287,17 @@ Two distinct render modes, both GPU-accelerated via Vulkan:
- [ ] Boss entity: large rigid body (10x10), multiple attack patterns
- [ ] Books/readable items: lore text displayed in terminal
- [ ] Crafting: combine materials to create new ones (water + dirt = mud)
- [ ] Sound: procedural audio via terminal bell or optional ALSA
- [x] Sound: procedural audio via rodio, 15 embedded WAV sounds, overlapping playback
- [ ] Save/load: full game state to file (grid + entities + player + inventory)
- [ ] Death screen: stats summary, cause of death
- [ ] Tutorial: first-time controls overlay
- [ ] Difficulty scaling: deeper levels = stronger enemies
### Phase 6: Advanced Physics
### Phase 6: Advanced Physics (DONE)
**Goal: deeper Noita-style material simulation**
- [ ] Multi-layer world: separate grid layers for material, temperature, pressure, gas/air, light
- [x] Multi-layer world: separate grid layers for material, temperature, pressure, gas/air, light
- Air layer: gas flow, ventilation in caves, gas accumulates at ceiling, displaced by fire
- Pressure layer: liquids have pressure, flow through pipes and U-bends
- Temperature layer: proper heat diffusion, materials melt/freeze at thresholds
@@ -547,10 +557,12 @@ assets/
|-----------|---------|--------|
| 0.1 (done) | Core engine: CA, rigid, ragdoll, terminal, AI pipe | June 2026 |
| 0.2 (done) | Vulkan ASCII + graphics renderers, adaptive viewport, per-cell color, slope stepping | June 2026 |
| 0.3 | Combat, goblin AI, projectiles, corpse decomposition | July 2026 |
| 0.35 | UI layer: health bar, HUD, message log, minimap, inventory overlay | July 2026 |
| 0.4 | Chunks, biomes, dungeon gen, camera zoom | August 2026 |
| 0.5 | RPG layer: stats, inventory, mutations, XP | October 2026 |
| 0.3 (done) | Combat, goblin AI, projectiles, corpse decomposition | June 2026 |
| 0.35 (done) | UI layer: health bar, HUD, message log, minimap, inventory overlay, scalable font | June 2026 |
| 0.4 (done) | Chunks, biomes, dungeon gen, world scale ×5, chunk streaming + cache | June 2026 |
| 0.45 (done) | RPG layer: stats, inventory, XP, status effects, items | June 2026 |
| 0.5 (done) | Multi-layer world: temp, gas, pressure, light + audio system | June 2026 |
| 0.55 (done) | Performance: uniform tick rate, 130-150 FPS, fire chain fix | June 2026 |
| 0.6 | Lighting/particles/textures (Phase 4b) | December 2026 |
| 0.7 | Multi-layer world: air, pressure, temperature, light as separate grids | Feb 2027 |
| 0.8 | AI agent: LLM + RL bridge, agent recording | April 2027 |