Grid.* operates on the Map tilemap (Map.size/Map.row): a cell is passable unless
it is out of bounds or holds the caller's `wall` tile (a char code, e.g. '#'), so
any impassable glyph works. Everything is integer and deterministic.
- Grid.line(x0,y0,x1,y1) -> []Cell Bresenham line cells (LOS/raycast base)
- Grid.blocked(x,y,wall) -> bool the shared passability test
- Grid.line_of_sight(x0,y0,x1,y1,wall) unobstructed straight line?
- Grid.flood(x,y,wall) -> []Cell 4-connected reachable region (BFS)
- Grid.a_star(x0,y0,x1,y1,wall) -> []Cell shortest 4-connected path (A*,
Manhattan heuristic), empty if unreachable
The engine (runtime/native/grid.ludic, ~150 lines of Ludic, C-free) is spliced
into a game via core.ludic since it reads the tilemap runtime; returned Cell
slices are ordinary Ludic slices (`len` / `[i]`; each cell has `.x` `.y`).
Pathfinding lives under Grid rather than a `Path` namespace — that name is
already the filesystem-paths library (#10).
Verified against Python references: a 1500-case fuzzer over random maps agrees
exactly on A* path length (optimal, == BFS), flood-fill count, and line-of-sight.
examples/library/grid.ludic asserts the behaviour and is wired into `x test`
(now 61 passed); docs: a Grid section + 5 per-symbol pages, inventory/coverage
green. Seed reseeded; the C-free bootstrap fixpoint holds.
Scope: this lands the Grid.*/pathfinding half of #24. The ECS Query.* helpers
(count/first, and nearest/within which want a runtime spatial index) remain the
tracked follow-up the issue calls out as blocked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| assets | ||
| inventory.json | ||
| README.md | ||
Ludic documentation generator
Generates the public documentation site (the pages branch) from a single
source of truth, so the site can never drift from the language.
Source of truth
docs/
language/<category>/<id>.md one file per symbol — keyword, type, phase,
builtin, namespace method, operator, annotation
language/<category>/_section.md section title + blurb + order
language/colors/palette.json the 221 named colors (generated by `x docs-palette`)
site/site.json landing-page messaging (hero, features, …)
site/snippets/*.ludic the code shown on the landing page (real programs)
tools/docgen/inventory.json the authoritative symbol set the coverage guard checks
A symbol file
---
id: screen-fill_rectangle # anchor + page name (screen-fill_rectangle.html)
name: Screen.fill_rectangle
category: screen
kind: namespace-method # keyword|type|phase|namespace-method|builtin|annotation|operator
tokens: Screen.fill_rectangle # literal token(s) the highlighter matches & links
sig: Screen.fill_rectangle(x, y, width, height, color)
tip: Draw a solid, filled rectangle. # one sentence — the hover-card summary
order: 1
ns: Screen # namespace-method only
member: fill_rectangle # namespace-method only
related: screen-clear screen-draw_rectangle
---
Rich description — inline `<code>`/`backticks`, "model instance" language.
Parameters: # for anything that takes arguments
- `x` — the left edge, in pixels
- `color` — the fill color, e.g. a `Color.*` name
```ludic
program Example { … descriptive-named, compilable … }
```
What it produces
One page per symbol (<id>.html), a namespace overview page per namespace
(ns-screen.html … ns-color.html), a searchable index (api.html, fuzzy
search over every symbol), the landing page (index.html), the
ludic-highlight.js highlighter (all its symbol tables, tips, per-item link
targets, per-parameter anchors and hover-card data generated from the sources
above), symbols.json, and .nojekyll.
In any code sample: keywords/types/builtins/annotations link to their page;
Screen.fill_rectangle links Screen → the namespace page and fill_rectangle
→ the method page separately; a named argument like width: links to that
parameter's anchor; Color.Charcoal links Color and Charcoal separately;
hovering any token shows a summary card from the real API data.
Build & check
The generator and its guards are written in Ludic and run through x — there is
no Python in the pipeline. assets/ (the CSS/HTML/JS templates) and
inventory.json are the only inputs the tools here still read directly.
bin/x docs-gen --out build/pages # generate the whole site
bin/x docs-check build/pages # coverage + duplicate-token + link guard
bin/x check-docs # parse every ```ludic doc fence
docs-check fails CI if any symbol in inventory.json lacks a page, if a token
is documented on two pages, or if a highlighter link points at a missing page —
so "every symbol is documented, autogenerated each time" is enforced. It needs a
built bin/x (bootstrapped from the IR seed with clang alone).
Publish
.forgejo/workflows/docs.yml bootstraps the toolchain from the IR seed and runs
x docs-gen + x docs-check on every push to main that touches docs/**,
tools/docgen/** or tools/x/**, and publishes the result to the pages branch
root. index.html + .nojekyll always stay at the root.
Colors
palette.json and selfhost/backend/stdlib/emit_color.ludic are both generated
by bin/x docs-palette from a single palette table — the pal_add(...) rows in
tools/x/docgen.ludic. Edit the table there and regenerate; do not hand-edit the
generated files.