No description
Find a file
Orkuncakilkaya 1c1192e7c0
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 21s
ci / build-and-test (push) Successful in 49s
ci: add build + test + bootstrap-cfree workflows for the Forgejo runner
Until now the only workflow was docs.yml — nothing gated a change on the
compiler even building, on `x test` / `x test-tools`, or on the headline C-free
self-rebuild reproducing the seed. Add two Forgejo Actions workflows on the same
`docker` runner the docs job uses.

The toolchain is macOS-first: the self-hosted compiler emits the Darwin libc
standard-stream globals (`__stdoutp`/`__stderrp`), the one thing that stops its
IR from linking on Linux. Everything else is portable — clang-16 assembles the
seed cleanly and the C-free bootstrap reproduces it byte-for-byte on Linux too.
So rather than require a macOS runner (none is registered), bridge that single
gap with a tiny **C-free LLVM-IR shim** (tools/ci/linux_stdio_shim.ll) that
defines the Darwin-named globals over glibc's stdout/stderr, injected into every
clang link via LUDIC_CC. The language keeps its no-C-compiler guarantee.

Workflows:
- ci.yml — bootstrap the toolchain from the seed, then `x test` + `x test-tools`
  + the docs-cover-the-implementation checks, on push to main and PRs.
- bootstrap.yml — `x bootstrap-cfree`: assert the seed rebuilds itself
  byte-for-byte (returns non-zero on drift).

Make the suites host-aware so a Linux run is green without hiding anything: a
new is_darwin()/skip() pair (tools/x/prelude.ludic) makes the cases that are
genuinely macOS-ABI bound — the Cocoa-windowed `ludicc -o` link, the golden
render hashes (blessed on macOS; text raster differs by a hair elsewhere), the
Os known-folder/uname surface, Fs.list and the LSP workspace walk (both read the
BSD dirent layout) — print a visible `skip` off Darwin instead of failing. On
macOS every one of them still runs: suites stay 56 / 29 / 29 green there, and
run 51 / 28 (+skips) on Linux, bootstrap-cfree byte-identical on both.

The formatting gate is ludic-fmt *idempotence* (already in `x test-tools`), not
`fmt(x) == x`: this codebase deliberately preserves hand alignment, so a strict
"already formatted" check would fight that contract.

A prebuilt CI image with clang-16 + python3 baked in is the obvious follow-up
speed-up (ties into the packaging work in #33).

Closes #32

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 23:23:23 +03:00
.claude Baseline: Ludic compiler + toolchain, Phase 1 syntax fixes complete 2026-08-27 15:15:35 +03:00
.forgejo ci: add build + test + bootstrap-cfree workflows for the Forgejo runner 2026-08-30 23:23:23 +03:00
assets Baseline: Ludic compiler + toolchain, Phase 1 syntax fixes complete 2026-08-27 15:15:35 +03:00
docs refactor(stdlib): consolidate System.* onto Os.*, retire duplicated members 2026-08-30 22:54:28 +03:00
examples feat(stdlib): add Fs.* / Path.* / Mime.* — filesystem, paths, content types (#10) 2026-08-30 22:43:50 +03:00
runtime chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
selfhost refactor(stdlib): consolidate System.* onto Os.*, retire duplicated members 2026-08-30 22:54:28 +03:00
tools ci: add build + test + bootstrap-cfree workflows for the Forgejo runner 2026-08-30 23:23:23 +03:00
.editorconfig chore(repo): add .editorconfig, reconcile .gitignore, add x clean 2026-08-30 16:46:10 +03:00
.gitignore chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
BOOTSTRAP.md refactor(lang): rename the ptr/ptrs types to pointer/pointers 2026-08-30 01:58:54 +03:00
CODE_OF_CONDUCT.md docs: add CONTRIBUTING, code of conduct, and Forgejo templates 2026-08-30 16:46:30 +03:00
COMPILING.md chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
CONTRIBUTING.md docs: add CONTRIBUTING, code of conduct, and Forgejo templates 2026-08-30 16:46:30 +03:00
EVENTS-DESIGN.md chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
LANGUAGE.md chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
LICENSE docs: add Apache-2.0 LICENSE for the compiler/runtime source 2026-08-30 16:46:39 +03:00
LIFECYCLE-DESIGN.md chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
LUANTI-ROADMAP.md refactor(lang): rename builtins flr->floor and fx->fixed 2026-08-30 02:01:56 +03:00
MOBILE-DESIGN.md chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
NETWORKING-DESIGN.md chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
README.md docs: rewrite README as an accurate entry point (#35) 2026-08-30 18:57:39 +03:00
SCENES-DESIGN.md chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00
SYNTAX-REDESIGN.md chore(repo): DX cleanup — categorise examples, text-diffable golden, build/ output (#27 #28 #30) 2026-08-30 18:54:21 +03:00

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.

  .ludic  ──►  ludicc  ──►  LLVM IR  ──►  object  ──►  native binary
             (in Ludic)

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 — five win_* functions — is hand-written LLVM IR against the platform ABI (runtime/native/cocoa.ll), the same floor Rust and Swift stand on.

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 <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.

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:

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:

bin/x build                                  # -> bin/{ludicc,ludic,x,ludic-fmt,ludic-lsp}
bin/x app examples/games/snake.ludic         # compile + open a native window
./build/snake

Render a frame headlessly (what CI checks) — output lands in build/, never the repo root:

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

Run the suites:

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 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 text baseline of render-output hashes (replaces binary .ppm fixtures); regenerate with bin/x golden.
tools/x/*.ludic the task runner, written in Ludic — one binary (bin/x) that builds, tests, bootstraps and reseeds the project, replacing every shell script.
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/ the browser platform layer: platform.js (the <canvas> window), wasm.ll (the libc-free floor), index.html.
examples/ the example tour, grouped by intent — games/, rendering/, ecs/, events/, networking/, lang/, library/. See examples/README.md.
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/ plugins for VS Code and JetBrains, plus config for Neovim, Helix, Emacs, Sublime and Zed.
docs/ the per-symbol API reference, regenerated into the docs site.
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 for the full reference, and examples/README.md for runnable demos of each feature.

Editor support

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 imported 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/.

Chrono Rift — the flagship game

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/. Its art is CC0 Kenney sprites, decoded from PNG at runtime by the Ludic-written PNG/DEFLATE decoder — no zlib, no external dependency.

  • 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.

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.

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:

Contributing

See 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/.

License

The Ludic compiler and runtime source are licensed under the Apache License 2.0 (SPDX-License-Identifier: Apache-2.0) — a permissive license with an explicit patent grant.

The bundled Kenney 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.