# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project `viberogue` is a ZZT-inspired game-making system written in Rust (edition 2024). The goal is a system where players can create games using a scripting language, similar to the classic DOS game ZZT. It uses **Rhai** for scripting (WASM-compatible, sandboxed, pure Rust). ## Code style - Add `///` rustdoc comments to every `pub` type, field, and function. The user reads rustdoc in their IDE to understand the code while making changes. - Add inline `//` comments inside non-trivial function bodies to explain the *why* of each logical step — especially in `update` loops, rendering math, and conversion logic. - Keep comments accurate: update them when the code they describe changes. ## Commands ```bash cargo build # compile cargo run # build and run (loads maps/start.toml) cargo test # run all tests cargo test # run a single test by name (substring match) cargo clippy # lint cargo fmt # format ``` ## Architecture The game is a single Rust binary using **eframe 0.33 / egui 0.33** for the GUI. eframe drives a retained-mode UI: the `App::update` method is called every frame and is responsible for both drawing and responding to input. `update` is structured in phases: - Input handling — arrow keys move the player (Play mode only) - `egui::TopBottomPanel::top` — menu bar (File → Exit, Play/Edit mode toggle) - `egui::SidePanel::right` — archetype palette panel (Edit mode only; declared before CentralPanel) - `egui::CentralPanel::default` — game viewport; rendered with `ui.painter()`; click-to-paint in Edit mode ### Modules **`src/game.rs`** — all core game types: - `Glyph` (`Copy`) — per-cell visual: `ch: char`, `fg/bg: Color32`. `Glyph::player()` is the only constructor used at runtime (colors for board tiles come from the map file). - `Behavior` — plain data struct of runtime behavioral properties: `passable: bool`, `opaque: bool`. Returned by `Archetype::behavior()`; new properties added here require no match arms elsewhere. - `Archetype` (`Copy`, `PartialEq`) — enum of named element types: `Empty`, `Wall`, `Object`, `ErrorBlock`. Each variant provides `behavior()`, `name()` (used in map files), and `default_glyph()` (used by the editor when stamping a cell). `ErrorBlock` is a sentinel for unknown archetype names — renders as yellow `?` on red. - `ALL_ARCHETYPES: &[Archetype]` — ordered list of valid editor choices (excludes `ErrorBlock`). - `Board` — the complete game unit (ZZT-style "board"): `width`, `height`, `cells: Vec<(Glyph, Archetype)>` (row-major; each cell owns its visual and behavioral class directly), `player: Player`, `objects: Vec`, `portals: Vec`. `cells` is `pub(crate)`. - `Player` — `x: i32, y: i32` - `ObjectDef` / `PortalDef` — parsed from map files, stored on Board; not yet runtime-wired - `GameState` — holds `board: Board`; `try_move(dx, dy)` checks passability before moving the player **`src/map_file.rs`** — map file loading: - `MapFile` and friends — serde `Deserialize` types for TOML map files - `impl From for Board` — converts a parsed file into a ready-to-use `Board` - `pub fn load(path: &str) -> Result>` — reads and converts a `.toml` map file **`src/main.rs`** — app entry point: - `AppMode` enum (`Play` | `Edit`) — gates arrow-key input; toggles the side panel and viewport mode - `EditorTab` enum (`Palette` | `Board` | `World`) — which tab is active in the side panel - `EditorState` — holds `selected: Archetype`, `glyph: Glyph` (the glyph to stamp, independent of archetype default), `glyph_picker_open: bool`, and `tab: EditorTab`. Selecting a new archetype resets `glyph` to that archetype's default. - Window uses fixed default size (`DEFAULT_WINDOW_W = 840`, `DEFAULT_WINDOW_H = 524`) independent of map dimensions; `board_origin()` handles centering or player-centered scrolling at runtime - `draw_glyph(painter, origin, x, y, glyph)` draws one cell: filled rect (bg) + monospace char (fg) - `board_origin(available, board_w, board_h, player)` — if the board fits in the viewport, centers it; if it overflows, centers on the player and clamps to board edges (no empty space shown) - Player is rendered on top of the board using `Glyph::player()` - **Play mode**: arrow keys move player; viewport scrolls to keep player centered when map > window - **Edit mode**: `ScrollArea::both()` wraps the board so scroll bars appear when map > window; click-to-paint stamps `(self.editor.glyph, self.editor.selected)` at the clicked cell; side panel (200 px, resizable) shows Palette/Board/World tabs - **Glyph picker dialog** (`egui::Window`): opened from the Palette tab; shows a board-palette of unique glyphs, fg/bg `color_edit_button_srgba` pickers, and a 16×6 grid of printable ASCII chars (0x20–0x7E) ### Map file format (`maps/*.toml`) XPM-inspired: a `[palette]` maps single characters to `(Glyph, Archetype)` definitions; `[grid] content` is a TOML multi-line string where each character indexes the palette. ```toml [map] name = "Room Name" width = 60 height = 25 player_start = [30, 12] [palette] " " = { archetype = "empty", ch = " ", fg = "#000000", bg = "#000000" } "#" = { archetype = "wall", ch = "#", fg = "#808080", bg = "#606060" } [grid] content = """ ############################################################ # # ############################################################ """ [[objects]] # optional; parsed but not yet runtime-wired x = 10 y = 5 script = """ on_touch(|| { send_message("open"); }); """ [[portals]] # optional; parsed but not yet runtime-wired x = 59 y = 12 target_map = "cave" target_entry = "west_door" ``` Colors are `"#RRGGBB"` hex strings. `player_start` is a header field — the player is not a board cell. The grid multi-line string's leading newline is trimmed by TOML; trailing newline is handled correctly by `str::lines()`. Unknown archetype names produce an `ErrorBlock` cell and a logged warning. ### Key design decisions - **`Behavior` and `Archetype` are separate types** — `Archetype` is the named class of a thing (`Wall`, `Empty`, `Object`); `Behavior` is its runtime properties (`passable`, `opaque`). Adding a new property means adding a field to `Behavior`, not a match arm at every call site. - **`cells: Vec<(Glyph, Archetype)>`** — each cell owns its visual and behavioral class directly; there is no per-board element palette or integer indirection. `Archetype` is `Copy` so this is efficient. - **Archetypes are referenced by name in map files** — so `ALL_ARCHETYPES` can be reordered or extended without breaking saved games. - **`Board` is the complete unit** — grid, player, objects, and portals all live on `Board`, matching how ZZT treats a "board". No separate wrapper struct. - **Glyph (visual) and Archetype (behavior) are decoupled** — each cell has its own `Glyph` (so colors can vary per-cell, e.g. fire flickering) while sharing an `Archetype` with other cells of the same type. - **File loading happens in `main()`** before the window is created, so board dimensions are available for window sizing. eframe runs on its own event loop thread; do not assume single-threaded execution.