Files
kiln/CLAUDE.md
T
randrews 453c7baf4e Add rustdoc and inline comments throughout; update CLAUDE.md style guide
All public types, fields, and functions in game.rs and map_file.rs now
have /// doc comments. main.rs has /// on App and draw_glyph plus inline
comments explaining the input/render pipeline. CLAUDE.md records the
expectation that generated code includes comments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:48:52 -05:00

103 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <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 two panels:
- `egui::TopBottomPanel::top` — menu bar (File → Exit)
- `egui::CentralPanel::default` — game viewport; rendered with `ui.painter()`
### 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).
- `Element` — behavior: `passable: bool` (future: `opaque`, etc.)
- `Board` — the complete game unit (ZZT-style "board"): `width`, `height`, `elements: Vec<Element>` (behavior palette), `cells: Vec<(Glyph, usize)>` (row-major; usize indexes into `elements`), `player: Player`, `objects: Vec<ObjectDef>`, `portals: Vec<PortalDef>`. `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<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:
- 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`
- 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()`
- Arrow key input handled via `ctx.input(|i| ...)` before panel rendering
### Map file format (`maps/*.toml`)
XPM-inspired: a `[palette]` maps single characters to `(Glyph, Element)` 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]
" " = { passable = true, ch = " ", fg = "#000000", bg = "#000000" }
"#" = { passable = false, 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()`.
### Key design decisions
- **`cells: Vec<(Glyph, usize)>`** — the `usize` element index is intentionally an anonymous tuple field; it only has meaning relative to a specific `Board`'s `elements` palette and cannot be misused as a standalone value.
- **`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 Element (behavior) are decoupled** — each cell has its own `Glyph` (so colors can vary per-cell, e.g. fire flickering) but shares `Element` definitions from the palette.
- **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.