docs: rewrite README as an accurate entry point (#35)
All checks were successful
docs / build-and-deploy (push) Successful in 2s

The README was macOS-centric and internally contradictory about backends
(claimed `--target wasm32` and ELF/COFF cross-compile as implemented while the
prose said they were retired with the C compiler). Rewritten to match the repo:

- Tight pitch: compiled, self-hosted, C-free, ECS, deterministic 2D.
- Honest **Backends** table: native 2D ships today; the web/wasm platform layer
  (runtime/web/) and native-vs-wasm diff harness are in-tree and documented, but
  emitting wasm is not yet re-wired on the self-hosted toolchain — same for
  `--target` cross-compile and `--shared` (per COMPILING.md). Removed the false
  "implemented" claims.
- **Quick start** verified end-to-end: the clang seed one-liner, `bin/x build`,
  `bin/x app examples/games/snake.ludic`, and the headless flow (now writing
  build/out.ppm, not the repo root).
- Layout table matches the reorganised tree: examples/ subdirs (with a link to
  examples/README.md), the golden hash manifest, runtime/native + runtime/web,
  tooling and docs.
- Replaced the giant inline roadmap with a concise status plus links out to the
  issue tracker/proposals, the docs site, and the wiki.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-30 18:57:39 +03:00
parent fb728bbefe
commit a422ef4375

243
README.md
View file

@ -1,95 +1,110 @@
# Ludic # Ludic
An AI-first, ahead-of-time **compiled** game language with an ECS core, a Ludic is an **ahead-of-time compiled** language for 2D games with an
deterministic fixed-point runtime, and a native 2D backend. `ludicc` lowers entity-component core, a deterministic fixed-point runtime, and its graphics
Ludic to LLVM IR itself and emits a native binary — **and `ludicc` is itself stack built into the language. `ludicc` lowers Ludic straight to LLVM IR and
written in Ludic.** emits a native binary — and **`ludicc` is itself written in Ludic**, compiles
its own source to a byte-exact fixpoint, and rebuilds from a checked-in IR seed
with **no C compiler in the loop**.
``` ```
.ludic ──► ludicc ──► LLVM IR ──► object ──► native binary .ludic ──► ludicc ──► LLVM IR ──► object ──► native binary
(in Ludic) (in Ludic)
``` ```
**No C is generated, compiled or linked anywhere in a build.** There is no **No C is generated, compiled or linked in a build.** No interpreter, no
interpreter, no transpiler, and no C runtime: the framebuffer, sprites, PNG transpiler, no C runtime: the framebuffer, sprites, PNG/DEFLATE decoding,
decoding, TrueType text, the retained UI, the registers and the RNG are all TrueType text, the retained UI, the registers and the RNG are all written in
written in Ludic (`runtime/native/*.ludic`), and the macOS window is Ludic (`runtime/native/*.ludic`); only the window seam — five `win_*` functions
hand-written LLVM IR (`runtime/native/cocoa.ll`). Beneath that sits only the — is hand-written LLVM IR against the platform ABI (`runtime/native/cocoa.ll`),
platform's own ABI — malloc, fwrite, objc_msgSend, CoreGraphics — reached by the same floor Rust and Swift stand on.
compiler intrinsics, the same floor Rust and Swift stand on.
**The compiler is written in Ludic.** `selfhost/*.ludic` is a Ludic compiler — ## Backends
lexer, parser and LLVM-IR backend — that compiles every example (including the
6-file JRPG) to the byte-exact same binary the original C compiler produced, and
compiles **its own source** to a fixpoint. It is built from a checked-in IR seed
(`selfhost/ludicc.seed.ll`) with clang alone; run `bin/x bootstrap-cfree`
to rebuild it with no C compiler in the loop. The former C compiler is gone.
Neither the compiler nor the games are C. Beneath both sits only the platform's | Backend | Status |
own ABI — malloc, fwrite, objc_msgSend, CoreGraphics — reached by intrinsics, the |---|---|
same floor Rust and Swift stand on. (The wasm/cross-compile/shared-library driver | **Native 2D** (macOS/Cocoa window; headless render for CI) | **Shipping** — the default `bin/x app` target. |
paths lived in the old C compiler and are not yet re-implemented on the | **Web / wasm32** | **In progress.** The browser platform layer is in-tree and documented — `runtime/web/` (the `<canvas>` window `platform.js`, the libc-free `wasm.ll` floor) and a Node harness that diffs native vs. wasm frame-for-frame (`tools/ludic-web/run.mjs`). Emitting wasm was a capability of the retired C compiler and is **not yet re-wired on the self-hosted toolchain**; see [COMPILING.md](COMPILING.md). |
self-hosted native toolchain.)
## Layout The same is true of `--target` cross-compilation and `--shared` libraries: both
are designed and documented, both lived in the old C compiler, and both are
pending re-implementation on the self-hosted native toolchain.
| Path | What it is | ## Quick start
|------|-----------|
| `selfhost/*.ludic` | **the compiler, written in Ludic** — lexer, parser, and the LLVM-IR backend (ECS storage, queries, spawn, `match`/`machine`, UI, scenes, save/load, fixed-point). Concatenated by `bin/x selfhost-build`; built from `selfhost/ludicc.seed.ll` |
| `tools/x/*.ludic` | **the task runner, written in Ludic** — one binary (`bin/x`) that builds, tests, bootstraps and reseeds the whole project, replacing every shell script. `bin/x bootstrap-cfree` rebuilds the compiler from the IR seed with **no C compiler** and proves it reproduces its own IR |
| `runtime/native/core.ludic` | the runtime written *in Ludic* for the native path (framebuffer, text, registers, RNG, input) |
| `COMPILING.md` | the native pipeline: `ludicc → LLVM IR → exe/dylib`, `module`/`export`, cross-compilation, `rt_*` intrinsics |
| `runtime/native/image.ludic` | PNG decoding, images, sprites, alpha blending and 9-slice — in Ludic |
| `runtime/native/inflate.ludic` | DEFLATE decompression (RFC 1951), so PNG needs no zlib on any target |
| `runtime/native/truetype.ludic` | from-scratch TrueType loader + antialiased glyph rasterizer, in Q16.16 |
| `runtime/native/ui.ludic` | retained UI widget tree: layout, 9-slice, focus, events — in Ludic |
| `runtime/native/cocoa.ll` | the macOS window, written in LLVM IR (Objective-C runtime + CoreGraphics via their C ABI) |
| `runtime/web/wasm.ll` | the web's platform layer in LLVM IR: the allocator, bulk memory and strings, since wasm32 has no libc |
| `runtime/web/platform.js` | the browser's window — the same five `win_*` functions `cocoa.ll` implements, against a `<canvas>` |
| `runtime/web/index.html` | the page a web build is served from |
| `tools/ludic-web/run.mjs` | runs a headless wasm build under Node, so native and wasm output can be diffed |
| `examples/games/chronorift.ludic` | the JRPG written in Ludic (multi-file via `import`, model-based) |
| `examples/games/menu.ludic` | a retained-UI title screen (9-slice, TrueType, focusable buttons) |
| `examples/games/snake.ludic` | a second, unrelated game — proves the language is general (same toolchain, no engine hardcoding) |
| `bin/x` | the task runner — `bin/x app examples/<name>.ludic` compiles a program; `bin/x help` lists every command |
| `bin/x test` | regression suite: builds the compiler, compiles/runs all examples, checks save/load + diagnostics |
| `tools/ludic-tools/` | the editor toolchain, **in Ludic**: `ludic-fmt` (source formatter) and `ludic-lsp` (language server) — one lexer and one vocabulary shared by both |
| `tools/editors/` | plugins for VS Code and JetBrains, plus configuration for Neovim, Helix, Emacs, Sublime and Zed ([README](tools/editors/README.md)) |
| `bin/x test-tools` | regression suite for the toolchain: proves formatting never changes a program, drives the language server over real LSP traffic |
## Build & run `bin/x` is the project's task runner — one native binary, written in Ludic and
compiled by Ludic, that replaces every build/test/bootstrap shell script.
Everything is driven by `bin/x`, the project's task runner — one native binary, Bootstrap it once from a clean checkout (the only step Ludic can't do for
written in Ludic and compiled by Ludic, that replaces every build/test/bootstrap itself, since compiling Ludic needs a compiler) with clang alone:
shell script. Bootstrap it once from a clean checkout (the only step Ludic can't
do for itself, since compiling Ludic needs a compiler):
```bash ```bash
clang selfhost/ludicc.seed.ll -o bin/ludicc && bin/ludicc tools/x/main.ludic -o bin/x clang selfhost/ludicc.seed.ll -o bin/ludicc && bin/ludicc tools/x/main.ludic -o bin/x
``` ```
Then `bin/x build` builds the whole toolchain into `bin/` — including `bin/x` Then build the whole toolchain and run a game:
itself — and `bin/x help` lists every command. `ludicc` compiles Ludic straight
to machine code via LLVM IR; see **[COMPILING.md](COMPILING.md)** for the
pipeline and the runtime protocol.
```bash ```bash
bin/x app examples/games/chronorift.ludic bin/x build # -> bin/{ludicc,ludic,x,ludic-fmt,ludic-lsp}
./build/chronorift # opens a native window bin/x app examples/games/snake.ludic # compile + open a native window
./build/snake
``` ```
Headless render (for testing / CI): Render a frame headlessly (what CI checks) — output lands in `build/`, never the
repo root:
```bash ```bash
bin/x app examples/games/chronorift.ludic --headless bin/x app examples/games/chronorift.ludic --headless
printf 'ddddwww' | ./build/chronorift_headless # writes out.ppm mkdir -p build && printf 'ddddwww' | ./build/chronorift_headless # writes build/out.ppm
sips -s format png out.ppm --out frame.png sips -s format png build/out.ppm --out frame.png
``` ```
(The shared-library and wasm/web build paths lived in the old C compiler and are Run the suites:
not yet re-implemented on the self-hosted native toolchain, so `bin/x app` builds
native windowed and `--headless` targets today. See ```bash
**[COMPILING.md](COMPILING.md)** for the pipeline and the runtime protocol.) bin/x test # full regression: compiler builds from seed, every example, golden renders
bin/x selfhost-test # correctness + the self-hosting / C-free bootstrap fixpoints
bin/x help # every command
```
## Layout
| Path | What it is |
|------|-----------|
| [`selfhost/*.ludic`](selfhost/) | **the compiler, written in Ludic** — lexer, parser, and the LLVM-IR backend (ECS storage, queries, spawn, `match`/`machine`, UI, scenes, save/load, fixed-point). Built from `selfhost/ludicc.seed.ll` with clang alone. |
| [`selfhost/golden/renders.sha256`](selfhost/golden/renders.sha256) | text baseline of render-output hashes (replaces binary `.ppm` fixtures); regenerate with `bin/x golden`. |
| [`tools/x/*.ludic`](tools/x/) | **the task runner, written in Ludic** — one binary (`bin/x`) that builds, tests, bootstraps and reseeds the project, replacing every shell script. |
| [`runtime/native/`](runtime/native/) | the runtime **in Ludic** for the native path: `core` (framebuffer, input, RNG), `image`/`inflate` (PNG + DEFLATE, no zlib), `truetype` (glyph rasterizer), `ui` (retained widget tree); plus `cocoa.ll`, the macOS window seam in LLVM IR. |
| [`runtime/web/`](runtime/web/) | the browser platform layer: `platform.js` (the `<canvas>` window), `wasm.ll` (the libc-free floor), `index.html`. |
| [`examples/`](examples/README.md) | the example tour, grouped by intent — `games/`, `rendering/`, `ecs/`, `events/`, `networking/`, `lang/`, `library/`. See [examples/README.md](examples/README.md). |
| [`tools/ludic-tools/`](tools/ludic-tools/) | the editor toolchain **in Ludic**: `ludic-fmt` (formatter) and `ludic-lsp` (language server) — one lexer, one vocabulary shared by both. |
| [`tools/editors/`](tools/editors/README.md) | plugins for VS Code and JetBrains, plus config for Neovim, Helix, Emacs, Sublime and Zed. |
| [`docs/`](docs/) | the per-symbol API reference, regenerated into the docs site. |
| [`COMPILING.md`](COMPILING.md) | the native pipeline: `ludicc → LLVM IR → exe`, the `rt_*` runtime protocol, and the (pending) wasm/cross-compile/shared-library paths. |
Design and roadmap documents — `LANGUAGE.md`, `EVENTS-DESIGN.md`,
`NETWORKING-DESIGN.md`, `SCENES-DESIGN.md`, `LIFECYCLE-DESIGN.md`,
`SYNTAX-REDESIGN.md`, `MOBILE-DESIGN.md`, `LUANTI-ROADMAP.md`, `BOOTSTRAP.md` —
live at the repository root today and are being migrated to the wiki.
## Language at a glance
- `program` / `property` (typed fields + defaults) / `model` (named entity kinds)
/ `system` (`phase`, `@annotations`, `reads`/`writes`).
- ECS queries `for (a, b) in query [A, B, {Tag}] where <expr> { … }`,
`spawn`/`despawn` with slot reuse, `@`-driven lifecycle hooks.
- An **event bus** (`event` / `emit` / `@On`, cancellable, `@Public` promotion)
and **networking** primitives (`@Sync`, ownership, RPCs) over a built-in
loopback transport — all deterministic, all pure Ludic.
- `scene` / `layer` / `become`, `match` / `machine` + `state`.
- Types `int`, `fixed` (Q16.16), `bool`, `entity`, `str`, `byte`, typed buffers;
a growing namespaced **standard library** (`Math`, `Vector`, `Time`/`Date`/
`Duration`/`Clock`, `Random`, `Hash`, `Crypto`, sorting, …).
- Deterministic seeded RNG and `save()`/`load()` snapshot of the whole World.
- Built-in 2D: framebuffer primitives, PNG sprites, TrueType text, 9-slice, and
a retained `ui` widget tree declared as data.
See [LANGUAGE.md](LANGUAGE.md) for the full reference, and
[examples/README.md](examples/README.md) for runnable demos of each feature.
## Editor support ## Editor support
@ -98,78 +113,40 @@ bin/x tools # -> bin/ludic-fmt, bin/ludic-lsp
``` ```
`ludic-lsp` speaks LSP 3.17 over stdio, so one binary serves every editor: `ludic-lsp` speaks LSP 3.17 over stdio, so one binary serves every editor:
completion that knows whether you are after a `.`, inside a `query [...]` or in context-aware completion, diagnostics from the compiler itself,
a `ui` block; diagnostics from the compiler itself; go-to-definition and rename go-to-definition and rename across `import`ed files, and comment-preserving
across `import`ed files; comment-preserving formatting. `ludic-fmt` is the same formatting. `ludic-fmt` is the same formatter as a CLI, for pre-commit hooks and
formatter as a CLI, for pre-commit hooks and CI. CI. Both also understand ```` ```ludic ```` fences in Markdown. Plugins and
drop-in config are in [`tools/editors/`](tools/editors/README.md).
Plugins for **VS Code** and **JetBrains IDEs** (Community editions included) and ## Chrono Rift — the flagship game
drop-in configuration for Neovim, Helix, Emacs, Sublime and Zed are in
[`tools/editors/`](tools/editors/README.md). Both tools also understand
```` ```ludic ```` fences in Markdown, so this file and `LANGUAGE.md` get the
same highlighting, checking and formatting as the source tree.
## Language features implemented [`examples/games/chronorift.ludic`](examples/games/chronorift.ludic) is a
playable co-op JRPG — overworld, dungeon, random encounters, a turn-based co-op
battle, a boss, an item shop and snapshot save/load — split across modules under
[`games/chronorift/`](examples/games/chronorift/). Its art is CC0
[Kenney](https://kenney.nl) sprites, decoded from PNG at runtime by the
Ludic-written PNG/DEFLATE decoder — no zlib, no external dependency.
- `property` (typed fields + defaults), `system` (`phase`, `@annotations`,
`reads`/`writes` clauses), `const`, `fn` (with `requires`/`ensures` parsed).
- `model` — named entity **kinds** (bundles of properties); identity is one
int per entity, replacing empty tag properties. Filter with `{Kind}`.
- `import "file"` — multi-file programs (fragments spliced in, include-guarded,
per-file diagnostics).
- ECS queries: `for (a, b) in query [A, B, {Tag}] where <expr> { … }`.
- `spawn`/`despawn` with entity-slot reuse; nested queries.
- Control flow: `if`/`else`, `when`, `while`, numeric `for i in a .. b`.
- Types: `int`, `fixed` (Q16.16, with correct `*`/`/` lowering), `bool`,
`entity`, `str`. Fixed-point vs int arithmetic is resolved by the typechecker.
- Deterministic seeded RNG; `save()`/`load()` snapshot of the whole ECS World.
- 2D primitives: `clear`, `fill_rect`, `frame_rect`, `put_px`, `present`, `key`.
- TrueType text (`font_load`, `text_ttf`) with full Unicode + anti-aliasing;
arbitrary-size images + `draw_9slice`.
- `ui` — a retained widget tree declared as data (panels, labels, buttons,
images; layout, 9-slice skins, keyboard focus + click events).
- `match` / `machine`+`state`+`become` — dispatch and state machines.
- `scene` / `layer` / `enter` — mutually-exclusive game states, each with
`on enter`/`on exit` hooks and layered systems (layer order = draw order).
- `var` — typed module-level state, included in save/load snapshots.
- `module` + `@export fn` — compile a .ludic file to a shared library whose
exported functions are ordinary C-ABI symbols.
- `extern fn … = "symbol"` — call any C-ABI library, Ludic or otherwise.
- `--target wasm32-unknown-unknown` — the same game in a browser: the runtime,
the ECS and the graphics stack compiled to wasm, rendering frames identical to
the native build's.
## The game: Chrono Rift
A playable co-op JRPG in `examples/games/chronorift.ludic`, using CC0
[Kenney](https://kenney.nl) sprites (Tiny Town + Tiny Dungeon), decoded from
PNG at runtime by the Ludic-written PNG/DEFLATE decoder — no zlib, no external
dependency on any target.
Controls:
- **Overworld:** `WASD` move, `K` save, `L` load. - **Overworld:** `WASD` move, `K` save, `L` load.
- **Battle (local co-op):** Player 1 / Knight — `W`/`S` select, `Space` confirm. - **Battle (local co-op):** P1/Knight `W`/`S` select, `Space` confirm;
Player 2 / Mage — `I`/`K` select, `J` confirm. Each player takes their own P2/Mage `I`/`K` select, `J` confirm.
turn each round (Attack / Defend / Run; Mage has Attack / Heal / Defend).
## Status ## Status & roadmap
- [x] Compiler pipeline: Ludic → LLVM IR → native binary / shared library The compiler self-hosts to a byte-exact fixpoint and rebuilds from its IR seed
- [x] No C generated, compiled or linked in a build; runtime written in Ludic with no C compiler; the ECS runtime, windowed + headless 2D rendering, the event
- [x] Cross-compilation to ELF (x86-64, aarch64) and Windows COFF bus, the deterministic networking stack, scenes, and save/load are all in place
- [x] ECS runtime (properties, systems, phases, queries, entity pooling) and covered by `bin/x test`.
- [x] Windowed 2D rendering (Cocoa driven from LLVM IR) + headless PPM verification
- [x] CC0 Kenney PNG sprites (`png_load`, decoder written in Ludic) + scrolling camera Active work and proposals — the standard library, a fuller type system,
- [x] Overworld: tilemap, movement, collision rendering/animation/lighting extras, input, filesystem/IO, testing, and
- [x] Random encounters + turn-based battle (HP/MP, seeded-RNG damage) re-wiring the web/wasm and cross-compile backends — are tracked as issues, not
- [x] Party + **local co-op** (P1 Knight, P2 Mage, per-player turns) inlined here:
- [x] Leveling (XP → stat growth) and game-over / respawn
- [x] **Self-hosting**: a Ludic-written compiler compiles its own source to a byte-exact fixpoint, and rebuilds itself from a checked-in IR seed with no C compiler in the loop (`selfhost/`, `bin/x bootstrap-cfree`) - **Issues & proposals:** <https://git.workshopsoft.io/workshopsoft/ludic/issues>
- [x] Snapshot save / load (full ECS World) - **Docs site (API reference):** <https://workshopsoft.pages.workshopsoft.io/ludic/>
- [x] Two maps (overworld + dungeon) with map switching via the arch - **Wiki (design & roadmap):** <https://git.workshopsoft.io/workshopsoft/ludic/wiki>
- [x] Boss encounter (Rift Warden) + victory condition
- [x] Item shop (gold → potions) at the house; potions usable in battle (Knight ITEM)
- [ ] More skills / enemy variety (future)
## Contributing ## Contributing
@ -186,5 +163,5 @@ permissive license with an explicit patent grant.
The bundled [Kenney](https://kenney.nl) art under `assets/kenney/` is The bundled [Kenney](https://kenney.nl) art under `assets/kenney/` is
third-party and released under **CC0 1.0** (public domain); each pack keeps its third-party and released under **CC0 1.0** (public domain); each pack keeps its
own `License.txt`. Code and assets are licensed separately: the Apache-2.0 own `License.txt`. Code and assets are licensed separately: Apache-2.0 covers
license covers the source, not the art. the source, not the art.