Files
kiln/CLAUDE.md
T
randrews f0c49295ea Update CLAUDE.md and ARCHITECTURE.md for Behavior/Archetype redesign and editor mode
Documents the replacement of Element with Behavior + Archetype, the
elimination of the per-board element palette, the new map file format
(archetype = "name" instead of passable = bool), the ErrorBlock sentinel,
and the editor mode (AppMode, EditorState, palette panel, click-to-paint).
Also updates "What's not yet implemented" to reflect editor progress and
call out the Object behavior / glyph chooser / file open gaps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:34:34 -05:00

6.5 KiB
Raw Blame History

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

cargo build          # compile
cargo run            # build and run (loads maps/start.toml)
cargo test           # run all tests
cargo test <name>    # 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<ObjectDef>, portals: Vec<PortalDef>. cells is pub(crate).
  • Playerx: 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<MapFile> for Board — converts a parsed file into a ready-to-use Board
  • pub fn load(path: &str) -> Result<Board, Box<dyn std::error::Error>> — reads and converts a .toml map file

src/main.rs — app entry point:

  • AppMode enum (Play | Edit) — gates arrow-key input; toggles the palette panel
  • EditorState — holds selected: Archetype (the archetype to paint on click)
  • Loads maps/start.toml before creating the window (so board dimensions drive window size)
  • Window min-size = board.width * CELL_W + 2*PANEL_MARGIN × board.height * CELL_H + MENU_H + 2*PANEL_MARGIN (sized for Play mode; Edit mode panel may clip at minimum size)
  • Board is centered in the CentralPanel when the window is larger than minimum
  • draw_glyph(painter, origin, x, y, glyph) draws one cell: filled rect (bg) + monospace char (fg)
  • Player is rendered on top of the board using Glyph::player()
  • Click-to-paint converts mouse_pos - origin to cell coordinates using the same math as rendering

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.

[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 typesArchetype 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.