Add kiln-tui: a terminal player for kiln boards

New `kiln-tui` crate (ratatui 0.30 / crossterm) that plays a board from a
.toml map named on the command line, letting the player walk around with the
arrow keys or hjkl. It depends only on kiln-core — the engine needed zero
changes, confirming it is genuinely UI-agnostic.

- Text rendering: each Glyph.tile index is reinterpreted as a character via a
  CP437->Unicode table (cp437.rs); fg/bg map to ratatui Color::Rgb truecolor.
- render.rs BoardWidget: per-axis viewport that centers a small board and
  scrolls a large one to keep the player visible.
- term.rs TerminalCaps: detects truecolor and Kitty keyboard-protocol support,
  enabling the protocol when present (held-key repeat, modifier-only keys);
  shows a rainbow "truecolor" badge in the bottom border.
- Input loop acts on Press and Repeat (continuous held-key movement) and
  ignores Release.

Workspace: removed kiln-egui from members (files left untouched). Updated
CLAUDE.md and ARCHITECTURE.md to document the workspace split and kiln-tui.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 20:10:02 -05:00
parent 353461dbfd
commit de1870432f
9 changed files with 1252 additions and 3403 deletions
+44 -11
View File
@@ -12,7 +12,9 @@ This document records the architectural decisions made so far and the reasoning
| Concern | Choice | Reason |
|---------|--------|--------|
| GUI/windowing | eframe 0.33 / egui 0.33 | Pure Rust, retained-mode, works on desktop and (eventually) WASM |
| Engine core | `kiln-core` crate | UI-agnostic game types + map I/O; the one crate every front-end depends on |
| Terminal front-end | `kiln-tui` — ratatui 0.30 / crossterm | Pure Rust TUI **player**; renders boards as colored text |
| Desktop front-end | `kiln-egui` — eframe / egui 0.33 | Pure Rust retained-mode GUI player + editor. **Currently not a workspace member** (see Module structure) |
| Scripting | Rhai 1.x | Pure Rust, sandboxed, WASM-compatible; designed for embedding |
| Map format | TOML + serde | Human-readable, good Rust tooling, standard in the ecosystem |
@@ -22,22 +24,27 @@ This document records the architectural decisions made so far and the reasoning
## Module structure
Cargo **workspace**; root `members = ["kiln-core", "kiln-tui"]`. `kiln-egui` stays in the tree but was removed from the workspace and is not built at the root.
```
src/
main.rs — App, AppMode, frame loop (input → menu → panels → board → dialogs)
kiln-core/src/ — the engine (UI-agnostic)
game.rs — core data types and game logic
map_file.rs — TOML deserialization, Board loader
render.rs — drawing primitives; cell size comes from BitmapFont
font.rs — BitmapFont, tile UV mapping, placeholder font
font_dialog.rs — floating font-picker dialog (path, tile dims, preview, apply)
editor.rs — EditorTab, EditorState, show_editor_panel
glyph_picker.rs — floating Glyph picker dialog; decoupled (open, glyph, font) interface
map_file.rs — TOML deserialization, Board load/save
kiln-tui/src/ — terminal player (no editor)
main.rs — CLI arg + play loop; TerminalCaps detection; Kitty flags
render.rs — BoardWidget: draws the board into a ratatui buffer as text
cp437.rs CP437 → Unicode table; renders tile indices as characters
term.rs — TerminalCaps detection + Kitty keyboard-protocol setup
kiln-egui/src/ — eframe/egui player + editor (NOT a workspace member)
main.rs, render.rs, font.rs, font_dialog.rs, editor.rs, glyph_picker.rs
maps/
start.toml — the starting map (loaded at launch)
start.toml — example map; play via `cargo run -p kiln-tui -- maps/start.toml`
assets/
vga-font-8x16.png — default CP437 bitmap font (256×128 px, 32 cols × 8 rows, 8×16 tiles)
vga-font-8x16.png — default CP437 bitmap font (256×128 px, 8×16 tiles); used by kiln-egui
```
The per-module deep-dives below are labeled by file. `game.rs`/`map_file.rs` are **kiln-core**; the kiln-tui player has its own section; `font.rs`, `font_dialog.rs`, and the egui `main.rs`/`render.rs`/`editor.rs`/`glyph_picker.rs` are **kiln-egui** (retained as design notes for when the GUI is revived).
### `game.rs` — core types
The types here are the runtime representation of a game world. They are deliberately free of any file-loading or rendering concerns.
@@ -87,6 +94,30 @@ Currently a thin wrapper around `Board` that provides game-logic methods (`try_m
---
### kiln-tui — terminal player
`kiln-tui` is a **play-only** front-end (no editor yet): it takes a map-file path on the command line, loads a `Board` via `kiln_core::map_file::load`, and lets the player walk around with the arrow keys (or `hjkl`). It depends only on `kiln-core`, `ratatui`/`crossterm`, and the `color` crate — none of the egui stack. This is the proof that `kiln-core` is genuinely UI-agnostic: the engine needed **zero changes** to add a second front-end.
**Text rendering instead of bitmap tiles.**
A terminal can't blit a bitmap font, so kiln-tui reinterprets each `Glyph.tile` index as a *character*. The default kiln font is CP437, where the tile index equals the code point, so `cp437::tile_to_char` maps indices through a 256-entry CP437→Unicode table (graphic glyphs for 0x000x1F, box-drawing for 0xB00xDF, ASCII in between). The `[font]` map section is still parsed and stored on `Board`, but kiln-tui ignores the image path and uses the tile indices directly. `Glyph.fg`/`bg` (`Rgba8`) map to `ratatui::style::Color::Rgb` — full 24-bit truecolor, alpha dropped (256-color terminals approximate it).
**`render.rs``BoardWidget`.**
A `ratatui::widgets::Widget` that draws the board into the frame buffer. The `axis(board_len, view, player) -> (offset, pad, count)` helper resolves each axis independently: a board smaller than the viewport is **centered** with screen padding; a larger one **scrolls** to keep the player visible, clamped so no empty margin shows past the board edge. Per cell, glyph priority is player > object > grid floor.
**`term.rs` — capability detection + Kitty protocol.**
`TerminalCaps { keyboard_enhancement, truecolor }` is detected once at startup: `keyboard_enhancement` via crossterm's `supports_keyboard_enhancement()` (a real query/response with the terminal), `truecolor` from `$COLORTERM`. When keyboard enhancement is supported, kiln-tui pushes the Kitty keyboard-protocol flags (`DISAMBIGUATE_ESCAPE_CODES | REPORT_EVENT_TYPES | REPORT_ALTERNATE_KEYS | REPORT_ALL_KEYS_AS_ESCAPE_CODES`) after `ratatui::init()` and pops them before `restore()`. This unlocks richer input — modifier-only keypresses, key release/repeat events, unambiguous Ctrl combos — which the engine intends to hand to scripts so they can choose bindings the terminal actually supports.
**Input loop.**
Because `REPORT_EVENT_TYPES` makes the terminal emit Release (and Repeat) events, the loop acts on `Press` **and** `Repeat` — so holding a key moves the player continuously at the terminal's own repeat cadence — and skips `Release`, which would otherwise fire a second, spurious move per keypress. In legacy terminals no Repeat events arrive, so held-key movement falls back to terminal auto-repeat; same behavior, no special-casing.
**Capability badge.**
`TerminalCaps::summary_line()` renders a one-line badge in the bottom border. When truecolor is available it shows off by drawing the word "truecolor" with each letter a different rainbow color (`hue_to_rgb`, a full-saturation HSV→RGB helper); otherwise it shows a plain "256-color", followed by the input descriptor ("enhanced input" / "legacy input") in the default color.
**Why a capability struct, not a terminal name.**
Scripts should branch on *what works* (truecolor, keyboard enhancement), not on a brittle terminal brand string that is often unset over SSH or inside tmux. `TerminalCaps` deliberately carries no program-identity field.
---
### `font.rs` — bitmap font
**`BitmapFont`** wraps an egui `TextureHandle` plus tile geometry (`tile_w`, `tile_h`, `img_w`, `img_h`). The texture is preprocessed at load time: the top-left pixel of the source PNG becomes the "background" sentinel — those pixels are stored as `TRANSPARENT`, all others as opaque `WHITE`. This lets egui's image tinting do all the colorization work at render time:
@@ -289,6 +320,8 @@ A direct mapping from palette character → `(Glyph, Archetype)` means the map f
**Map save** — the editor can paint cells but has no way to write changes back to a `.toml` file. Saving will require serializing the `Board` back to `MapFile` format and writing it to disk.
**Terminal editor**`kiln-tui` is play-only; it has no editing UI. The editor currently lives only in `kiln-egui`, which is out of the workspace. A future TUI editor (or reviving the egui one) is an open direction.
---
## Future considerations