Implement the bulk of the namespaced-stdlib proposal (workshopsoft/ludic#2): 156 namespace methods across Math, Text, List, Ease, Collide, World, Net, Sys, Save, Mem, extended Screen, Color functions, extended Random, and Time. All deterministic fixed-point; self-hosting (C-free bootstrap fixpoint holds). Compiler (selfhost/): - Math.*: sqrt/sin/cos/tan/atan2/asin/acos (fixed-point runtime prelude — bit-by-bit isqrt, 256-entry interpolated sine table, Ross atan2), plus hypot/dist/dist2/deg_to_rad/rad_to_deg/posmod/wrap/ping_pong/snapped/ move_toward/smoothstep/lerp/remap/sign/floor/ceil/round. - Text.* (complete): upper/lower/trim/repeat/pad, split/join/replace, and the libc-backed queries. - List.* (complete): insert/remove_at/remove/sort plus the earlier ops. - Ease.* (in/out/in_out/back/bounce) and Collide.* (rects/point_rect/ circles/rect_circle). - Phase 3: World/Net/Sys/Save namespaced over the bare builtins (byte- identical IR) and Mem.* (bytes/words/copy/fill/peek/poke). - Screen.* extended (line/circle/fill_circle/triangle/fill_triangle via new runtime primitives; sprite/sprite_scaled aliases), Color.* functions, Random.* (value/int/sign), Time.* (frame/delta/elapsed/now — new game-loop frame counter). - Fix a lexer bug: fixed-point literals with >4 fractional digits overflowed. Docs & tooling: - 129 new per-symbol doc pages; gen.py made data-driven (namespaces discovered from the docs, no hardcoded list); new check-impl.py enforces that every implemented namespace method / keyword / type / phase has a doc page, wired into `x test-tools`. Document the previously-undocumented keywords (break/continue/where/entry/new/public + and/or/not tokens). - LSP: namespaced signature help (ns_method_sig) covering every namespace. Tests: 12 new self-host/regression tests + a golden render for the drawing primitives. All suites green (selfhost 21, regression 45, tools 29). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
172 lines
10 KiB
Markdown
172 lines
10 KiB
Markdown
# Ludic
|
|
|
|
An AI-first, ahead-of-time **compiled** game language with an ECS core, a
|
|
deterministic fixed-point runtime, and a native 2D backend. `ludicc` lowers
|
|
Ludic to LLVM IR itself and emits a native binary — **and `ludicc` is itself
|
|
written in Ludic.**
|
|
|
|
```
|
|
.ludic ──► ludicc ──► LLVM IR ──► object ──► native binary
|
|
(in Ludic)
|
|
```
|
|
|
|
**No C is generated, compiled or linked anywhere in a build.** There is no
|
|
interpreter, no transpiler, and no C runtime: the framebuffer, sprites, PNG
|
|
decoding, TrueType text, the retained UI, the registers and the RNG are all
|
|
written in Ludic (`runtime/native/*.ludic`), and the macOS window is
|
|
hand-written LLVM IR (`runtime/native/cocoa.ll`). Beneath that sits only the
|
|
platform's own ABI — malloc, fwrite, objc_msgSend, CoreGraphics — reached by
|
|
compiler intrinsics, the same floor Rust and Swift stand on.
|
|
|
|
**The compiler is written in Ludic.** `selfhost/*.ludic` is a Ludic compiler —
|
|
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
|
|
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
|
|
paths lived in the old C compiler and are not yet re-implemented on the
|
|
self-hosted native toolchain.)
|
|
|
|
## Layout
|
|
|
|
| Path | What it is |
|
|
|------|-----------|
|
|
| `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/chronorift.ludic` | the JRPG written in Ludic (multi-file via `import`, model-based) |
|
|
| `examples/menu.ludic` | a retained-UI title screen (9-slice, TrueType, focusable buttons) |
|
|
| `examples/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
|
|
|
|
Everything is driven by `bin/x`, the project's task runner — one native binary,
|
|
written in Ludic and compiled by Ludic, that replaces every build/test/bootstrap
|
|
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
|
|
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`
|
|
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
|
|
bin/x app examples/chronorift.ludic
|
|
./build/chronorift # opens a native window
|
|
```
|
|
|
|
Headless render (for testing / CI):
|
|
|
|
```bash
|
|
bin/x app examples/chronorift.ludic --headless
|
|
printf 'ddddwww' | ./build/chronorift_headless # writes out.ppm
|
|
sips -s format png out.ppm --out frame.png
|
|
```
|
|
|
|
(The shared-library and wasm/web build paths lived in the old C compiler and are
|
|
not yet re-implemented on the self-hosted native toolchain, so `bin/x app` builds
|
|
native windowed and `--headless` targets today. See
|
|
**[COMPILING.md](COMPILING.md)** for the pipeline and the runtime protocol.)
|
|
|
|
## Editor support
|
|
|
|
```bash
|
|
bin/x tools # -> bin/ludic-fmt, bin/ludic-lsp
|
|
```
|
|
|
|
`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
|
|
a `ui` block; diagnostics from the compiler itself; go-to-definition and rename
|
|
across `import`ed files; comment-preserving formatting. `ludic-fmt` is the same
|
|
formatter as a CLI, for pre-commit hooks and CI.
|
|
|
|
Plugins for **VS Code** and **JetBrains IDEs** (Community editions included) and
|
|
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
|
|
|
|
- `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/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.
|
|
- **Battle (local co-op):** Player 1 / Knight — `W`/`S` select, `Space` confirm.
|
|
Player 2 / Mage — `I`/`K` select, `J` confirm. Each player takes their own
|
|
turn each round (Attack / Defend / Run; Mage has Attack / Heal / Defend).
|
|
|
|
## Status
|
|
|
|
- [x] Compiler pipeline: Ludic → LLVM IR → native binary / shared library
|
|
- [x] No C generated, compiled or linked in a build; runtime written in Ludic
|
|
- [x] Cross-compilation to ELF (x86-64, aarch64) and Windows COFF
|
|
- [x] ECS runtime (properties, systems, phases, queries, entity pooling)
|
|
- [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
|
|
- [x] Overworld: tilemap, movement, collision
|
|
- [x] Random encounters + turn-based battle (HP/MP, seeded-RNG damage)
|
|
- [x] Party + **local co-op** (P1 Knight, P2 Mage, per-player turns)
|
|
- [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`)
|
|
- [x] Snapshot save / load (full ECS World)
|
|
- [x] Two maps (overworld + dungeon) with map switching via the arch
|
|
- [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)
|