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 `