ludic/tools/ui-preview/README.md
Orkuncakilkaya 49b5e80af5 ludic ui-preview: ludic.ui components previewed for a studio over stdio (R5)
A prebuilt tool, bin/ludic-ui-preview (built by ludic-dev build, shipped beside ludic-lsp, run as
`ludic ui-preview [--font DIR]`): ludic.ui with a backend that records every draw call as a line,
and mock component classes the studio describes (load / model / calls). frame t runs ui_show at
interface time t; text is measured on the CPU with the game's font.json metrics; locale goes
through ludic.i18n; tree / box / rules inspect the frame. No game code, no GPU, no network. The wire
protocol is frozen as tools/ui-preview/protocol-v1.md; smoke.txt is a transcript to run.

ludic.ui gains, all additive: UiClass.make_of, UiBackend.said / emitted, UiRule.text / at (with
comment line breaks kept so rule lines count true, and a class's styles read under its .lss path),
and ui_root, ui_find, ui_rules_of, ui_rule_value, ui_building, ui_class, ui_class_load,
ui_file_forget, ui_errors_clear; ui_nine_cuts_into exported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:53:26 +03:00

59 lines
3.6 KiB
Markdown

# ludic ui-preview
A game's `ludic.ui` components, previewed for a studio (a UI editor) without the game: `ludic.ui`
itself, with a backend that records its draw calls instead of drawing them, and component classes
the studio describes over stdin. No game code is built, nothing touches a GPU, nothing opens a
network connection. It is the toolchain's own program, shipped prebuilt as `bin/ludic-ui-preview`.
```bash
ludic ui-preview [--font DIR] # the host on stdin/stdout; DIR holds the game's font.json
```
**The wire protocol is [protocol-v1.md](protocol-v1.md)**, frozen: every request, every reply and
every line format. `smoke.txt` is a scripted session with what each reply must contain.
## What it does
- **Components are mocks.** `load` registers a `UiClass` named as the component, with the template
and styles the studio sends (unsaved text is fine), or changes that class in place. Its model is
the story's (`model`), with the props its parent's tag gives it and whatever its template `set`s
laid over it; its functions answer from the story's table (`calls`). A component tag the studio
has not sent yet is an empty class until it is.
- **The frame is ludic.ui's own.** `frame t` sets the interface clock to `t`, hands over the input
the studio sent (`input ...`), and calls `ui_show`: build, cascade, layout, pointer and focus,
draw, then the actions a press ran. The draw list is every `UiBackend` call in painting order.
- **Text is measured as the game measures it.** `font DIR` (or `--font DIR`) reads the same
`font.json` ludic.render3d's overlay reads, and the width of a run is the overlay's sum of glyph
advances at the font's whole-pixel size; each `text` line carries it, so the studio fits its own
rendering to the game's line breaks.
- **Languages** are the game's: `locale <file.po>` translates every drawn text with ludic.i18n.
- **Natives** (a radar, a dial) are game code, so a `native` line is a box for the studio to fill.
- **Inspection**: `tree`, `box <id>` (box and computed style) and `rules <id>` (the matching rules,
each with the file and line its selector was written on).
## ludic.ui additions it relies on
All additive, and none changes what a game draws:
- `UiClass.make_of` - a constructor handed the class, so one host function makes every mock.
- `UiBackend.said` - template errors to the host instead of `print`; `UiBackend.emitted` - every
`emit`, for the host's log.
- `UiRule.text` and `UiRule.at` - a rule's selector as written and `file:line`. A stylesheet's
comments keep their line breaks when stripped, so lines count true; a component's own styles are
read under its `.lss` path.
- `inspect.ludic`: `ui_root`, `ui_find`, `ui_rules_of`, `ui_rule_value`, `ui_building`,
`ui_class_load`, `ui_file_forget`, `ui_errors_clear`; `class.ludic`: `ui_class`;
`ui_nine_cuts_into` exported.
## Limits in v1
- An error's column is not known (`col` is 0): ludic.ui records a file and line per element and per
rule, not a column. A malformed XML file is read leniently by the runtime's parser and does not
report a syntax error.
- In a GAME build a component's styles have their `@import`s inlined by the compiler, so a rule's
line there counts in the inlined text; in the host the studio sends the file as written, and
lines are the file's.
- A declaration's value is given as written only when it is plain text; one with `{...}` holes is
`""` in `rules` (its computed result is in `box`).
- Images are referenced, not decoded: the studio draws them from its own copies of the game's files
and atlases, and says a picture's natural size with `image` when `object-fit` needs it.