From c9ee303b696540cb6aa3c8babd510b06330653cf Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sat, 5 Sep 2026 14:25:07 +0300 Subject: [PATCH] docs: rewrite the README for someone meeting the language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README opened with three restatements of "no C", then a limitations table, then a repo-layout map, and closed with Chrono Rift's keybindings — P2/Mage `I`/`K` select, `J` confirm — which is a game manual, not a language README. It never showed the language itself. It now leads with what Ludic is, a compiling code sample, how to build, what the language offers, and an honest status. The layout table is contributor material and CONTRIBUTING already covers that ground. Two things were not merely stylistic: - Nine links pointed at wiki pages that no longer exist. - "Language at a glance" advertised `system` with `reads`/`writes`, plus `requires`/`ensures`. None of those are keywords — the grammar has `handler`, and `System.*` is a namespace. That bullet described a vocabulary retired several releases ago. The sample is verified to compile, every internal link resolves, and every command named is one `x help` actually offers. Also makes commit-lint survive a force-push: it linted `event.before..sha` without checking that `before` still resolves, so rewriting or gc'ing that commit failed the job with "Invalid revision range" on a push whose messages were all valid. Co-Authored-By: Claude Opus 5 --- .forgejo/workflows/commit-lint.yml | 7 +- README.md | 252 ++++++++++++----------------- changes/commit-lint-force-push.md | 7 + changes/readme-rewrite.md | 9 ++ 4 files changed, 128 insertions(+), 147 deletions(-) create mode 100644 changes/commit-lint-force-push.md create mode 100644 changes/readme-rewrite.md diff --git a/.forgejo/workflows/commit-lint.yml b/.forgejo/workflows/commit-lint.yml index ff1c8c00..bbccce89 100644 --- a/.forgejo/workflows/commit-lint.yml +++ b/.forgejo/workflows/commit-lint.yml @@ -35,10 +35,15 @@ jobs: # - pull_request: base branch .. this commit # - push: the pushed range (event.before .. this commit) # - new branch / unknown: just the tip commit + # `event.before` is only usable if it still resolves: a force-push + # rewrites (and a gc can remove) the commit it names, which made this + # job fail with "Invalid revision range" on an otherwise clean push. + # Fall back to the tip commit in that case. if [ -n "${BASE:-}" ]; then git fetch --quiet origin "${BASE}" 2>/dev/null || true RANGE="origin/${BASE}..${GITHUB_SHA}" - elif [ -n "${BEFORE:-}" ] && ! printf '%s' "$BEFORE" | grep -qE '^0+$'; then + elif [ -n "${BEFORE:-}" ] && ! printf '%s' "$BEFORE" | grep -qE '^0+$' \ + && git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then RANGE="${BEFORE}..${GITHUB_SHA}" else RANGE="${GITHUB_SHA}~1..${GITHUB_SHA}" diff --git a/README.md b/README.md index ee6e88b5..c4e8c503 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,149 @@ # Ludic -Ludic is an **ahead-of-time compiled** language for 2D games with an -entity-component core, a deterministic fixed-point runtime, and its graphics -stack built into the language. `ludicc` lowers Ludic straight to LLVM IR and -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**. +A compiled language for 2D games. The entity-component system is part of the +syntax, the runtime is deterministic fixed-point, and `ludicc` lowers Ludic +straight to LLVM IR — **no C is generated, compiled or linked in a build.** -``` - .ludic ──► ludicc ──► LLVM IR ──► object ──► native binary - (in Ludic) +The compiler is written in Ludic. It compiles its own source to a byte-exact +fixpoint and rebuilds from a checked-in IR seed with clang alone; CI asserts +that on every push. + +- **Documentation:** +- **API reference:** +- **Issues:** + +```ludic +program Hello { + + property Position { column: int = 0, row: int = 0 } + property Velocity { delta_x: int = 0, delta_y: int = 0 } + + handler SpawnEnemies phase Start { + spawn Enemy { Position { column: 3, row: 4 }, Velocity { delta_x: 1, delta_y: 0 } } + spawn Enemy { Position { column: 10, row: 2 }, Velocity { delta_x: 0, delta_y: 1 } } + } + + # a handler declares the entities it touches; the body runs + # once per match, with each property bound by name. + @Queries(these: [Position, Velocity]) + handler AdvancePositions phase FixedUpdate { + Position.column += Velocity.delta_x + Position.row += Velocity.delta_y + } +} ``` -**No C is generated, compiled or linked in a build.** No interpreter, no -transpiler, no C runtime: the framebuffer, sprites, PNG/DEFLATE decoding, -TrueType text, the retained UI, the registers and the RNG are all written in -Ludic (`runtime/native/*.ludic`); only the window seam — the `win_*` functions -for the window, keys, mouse, cursor, gamepad and touch — is hand-written LLVM IR -against the platform ABI (`runtime/native/cocoa.ll`), -the same floor Rust and Swift stand on. +## Getting started -## Backends - -| Backend | Status | -|---|---| -| **Native 2D** (macOS/Cocoa window; headless render for CI) | **Shipping** — the default `bin/x app` target. | -| **Web / wasm32** | **In progress.** The browser platform layer is in-tree and documented — `runtime/web/` (the `` 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). | - -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. - -## Quick start - -`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. -Bootstrap it once from a clean checkout (the only step Ludic can't do for -itself, since compiling Ludic needs a compiler) with clang alone: +`bin/x` is the project's task runner: one native binary, written in Ludic and +compiled by Ludic, that replaces every build and test script. Bootstrapping it +is the only step Ludic cannot do for itself, since compiling Ludic needs a +compiler — clang assembles the checked-in IR seed: ```bash mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc && bin/ludicc tools/x/main.ludic -o bin/x ``` -Then build the whole toolchain and run a game: +Then build the toolchain and run a game: ```bash -bin/x build # -> bin/{ludicc,ludic,x,ludic-fmt,ludic-lsp} -bin/x app examples/games/snake.ludic # compile + open a native window +bin/x build # -> bin/{ludicc,ludic,x,ludic-fmt,ludic-lsp} +bin/x app examples/games/snake.ludic # compile and open a native window ./build/snake ``` -Render a frame headlessly (what CI checks) — output lands in `build/`, never the -repo root: +Rendering is deterministic, so a frame can be produced without a window — this +is what CI diffs: ```bash -bin/x app examples/games/chronorift.ludic --headless -mkdir -p build && printf 'ddddwww' | ./build/chronorift_headless # writes build/out.ppm -sips -s format png build/out.ppm --out frame.png +bin/x app examples/games/snake.ludic --headless +printf 'ddddwww' | ./build/snake_headless # writes build/out.ppm ``` -Run the suites: +`bin/x help` lists every command. [`examples/`](examples/README.md) is a tour +grouped by intent: games, rendering, ECS, events, networking, language features +and the standard library. + +## The language + +- **ECS in the syntax.** `property`, `model` and `handler` are keywords. Query + with `for (a, b) in query [A, B, {Tag}] where { … }`; `spawn` and + `despawn` recycle entity slots; `@`-annotations drive lifecycle hooks. +- **Deterministic by construction.** Q16.16 `fixed` arithmetic and a seeded RNG + give the same frame byte-for-byte on every run — the basis for replays, + lockstep netcode and golden-image tests. +- **Scenes and state machines.** `scene` / `layer` / `become` model + mutually-exclusive game states with enter and exit hooks; `match` / `machine` + / `state` handle dispatch and per-entity FSMs. +- **Events and networking.** A cancellable event bus (`event` / `emit` / `@On`) + and networking primitives (`@Sync`, ownership, RPCs) over a built-in transport. +- **Batteries in the language.** Framebuffer primitives, PNG sprites, TrueType + text and a retained `ui` widget tree declared as data, plus a namespaced + standard library (`Math`, `Text`, `List`, `Random`, `Crypto`, `Tiled`, …). +- **Whole-world snapshots.** `save()` and `load()` serialize every entity, + property and program `var` in one call. + +[LANGUAGE.md](LANGUAGE.md) is the full reference; the +[API reference](https://workshopsoft.pages.workshopsoft.io/ludic/api.html) +documents every symbol on its own page. + +## Packages + +Dependencies are identified by URL, resolved with minimal version selection, and +cached in a content-addressed store: ```bash -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 +bin/x add git.workshopsoft.io/user/pkg # resolve, fetch, link into ludic_modules/ +bin/x get # install from package.ludic, write the lock +bin/x verify # check locked packages against the store ``` -Add a dependency (URL-as-identity, MVS resolution, content-addressed store): - -```bash -bin/x add git.workshopsoft.io/user/pkg # resolve + fetch + link into ludic_modules/ -bin/x get # install everything in package.ludic, write the lock -bin/x verify # check the locked packages against the store -``` - -See [`docs/PACKAGES.md`](docs/PACKAGES.md) for the manifest, lockfile and package model. - -## 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 `` 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. | - -The design and roadmap material lives on the **[wiki](https://git.workshopsoft.io/workshopsoft/ludic/wiki)**: -the [Events](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Design/Events), -[Networking](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Design/Networking), -[Scenes](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Design/Scenes), -[Lifecycle](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Design/Lifecycle), -[Mobile](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Design/Mobile) and -[Syntax-redesign](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Design/Syntax-Redesign) -design records, the [Bootstrap deep-dive](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Bootstrap), -and the [Luanti roadmap](https://git.workshopsoft.io/workshopsoft/ludic/wiki/Roadmap/Luanti). -The root keeps only this README plus the two user-facing references, -[`LANGUAGE.md`](LANGUAGE.md) and [`COMPILING.md`](COMPILING.md). - -## 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 { … }`, - `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. +See [`docs/PACKAGES.md`](docs/PACKAGES.md) for the manifest and lockfile model. ## Editor support ```bash -bin/x tools # -> bin/ludic-fmt, bin/ludic-lsp +bin/x tools # -> bin/ludic-fmt, bin/ludic-lsp ``` `ludic-lsp` speaks LSP 3.17 over stdio, so one binary serves every editor: -context-aware completion, diagnostics from the compiler itself, -go-to-definition and rename across `import`ed files, and comment-preserving -formatting. `ludic-fmt` is the same formatter as a CLI, for pre-commit hooks and -CI. Both also understand ```` ```ludic ```` fences in Markdown. Plugins and -drop-in config are in [`tools/editors/`](tools/editors/README.md). +completion, diagnostics from the compiler itself, go-to-definition and rename +across imports, and comment-preserving formatting. `ludic-fmt` is the same +formatter as a CLI, for pre-commit hooks. Both understand ```` ```ludic ```` +fences in Markdown. Plugins and drop-in config for VS Code, JetBrains, Neovim, +Helix, Emacs, Sublime and Zed are in [`tools/editors/`](tools/editors/README.md). -## Chrono Rift — the flagship game +## Status -[`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. +The native 2D backend ships: a Cocoa window on macOS, a headless renderer for +CI, and the whole runtime — framebuffer, PNG/DEFLATE decoding, TrueType +rasterizer, retained UI, RNG — written in Ludic under +[`runtime/native/`](runtime/native/). Only the window seam (`win_*`: window, +keys, mouse, cursor, gamepad, touch) is hand-written LLVM IR against the +platform ABI, the same floor Rust and Swift stand on. -- **Overworld:** `WASD` move, `K` save, `L` load. -- **Battle (local co-op):** P1/Knight `W`/`S` select, `Space` confirm; - P2/Mage `I`/`K` select, `J` confirm. +The **web/wasm32 backend is not currently available.** The browser platform +layer is in-tree under [`runtime/web/`](runtime/web/), but emitting wasm was a +capability of the retired C compiler and has not been re-wired on the +self-hosted toolchain. `--target` cross-compilation and `--shared` libraries are +in the same position. See [COMPILING.md](COMPILING.md). -## Status & roadmap - -The compiler self-hosts to a byte-exact fixpoint and rebuilds from its IR seed -with no C compiler; the ECS runtime, windowed + headless 2D rendering, the event -bus, the deterministic networking stack, scenes, and save/load are all in place -and covered by `bin/x test`. CI gates every push and PR on the build, the test -suites, and that C-free fixpoint. The toolchain is versioned with SemVer -(`ludicc --version`); releases and the `CHANGELOG.md` are cut from changesets by -`x release`. Upgrading from 0.2? See the -[0.2 → 0.3 migration guide](docs/MIGRATION-0.2-to-0.3.md). - -Active work and proposals — the standard library, a fuller type system, -rendering/animation/lighting extras, input, filesystem/IO, testing, and -re-wiring the web/wasm and cross-compile backends — are tracked as issues, not -inlined here: - -- **Issues & proposals:** -- **Docs site (API reference):** -- **Wiki (design & roadmap):** +Releases follow SemVer and are cut from changesets by `x release`, then built +and published by CI from the tag; see [CHANGELOG.md](CHANGELOG.md). ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for the development loop -(`bin/x reseed` → `bin/x bootstrap-cfree` → `bin/x test`), the code and commit -conventions, and how the bootstrap fixpoint works. Issue and pull-request -templates live under [`.forgejo/`](.forgejo/). +[CONTRIBUTING.md](CONTRIBUTING.md) covers the development loop, the commit and +code conventions, how the bootstrap fixpoint works, and what a self-hosted CI +runner needs. Issue and pull-request templates are under +[`.forgejo/`](.forgejo/). ## License -The Ludic compiler and runtime source are licensed under the -[Apache License 2.0](LICENSE) (`SPDX-License-Identifier: Apache-2.0`) — a -permissive license with an explicit patent grant. +The compiler and runtime are licensed under the +[Apache License 2.0](LICENSE) (`SPDX-License-Identifier: Apache-2.0`). 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 -own `License.txt`. Code and assets are licensed separately: Apache-2.0 covers -the source, not the art. +third-party and released under **CC0 1.0**; each pack keeps its own +`License.txt`. Code and assets are licensed separately — Apache-2.0 covers the +source, not the art. diff --git a/changes/commit-lint-force-push.md b/changes/commit-lint-force-push.md new file mode 100644 index 00000000..49247c0a --- /dev/null +++ b/changes/commit-lint-force-push.md @@ -0,0 +1,7 @@ +bump: patch +type: ci +The commit-lint workflow survives a force-push. It linted +`${{ github.event.before }}..${{ github.sha }}` without checking that `before` +still resolves, so rewriting or garbage-collecting that commit failed the job +with `fatal: Invalid revision range` on a push whose messages were all valid. It +now falls back to linting the tip commit when `before` is gone. diff --git a/changes/readme-rewrite.md b/changes/readme-rewrite.md new file mode 100644 index 00000000..b15ea39e --- /dev/null +++ b/changes/readme-rewrite.md @@ -0,0 +1,9 @@ +bump: patch +type: docs +The README is rewritten around what a reader needs first: what the language is, +a code sample, how to build it, and an honest status. Removed the repo-layout +table and the Chrono Rift keybindings (a game manual in a language README), the +nine links to wiki pages that no longer exist, and a "language at a glance" +bullet describing a retired vocabulary — it advertised `system`, `reads`, +`writes`, `requires` and `ensures`, none of which are keywords; the declaration +keyword is `handler`.