No description
Find a file
Orkuncakilkaya b25dc328a2
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 17s
ci / build-and-test (push) Successful in 1m11s
commit-lint / conventional-commits (push) Successful in 4s
docs / build-and-deploy (push) Successful in 18s
feat(stdlib): add Query.* — ECS spatial queries over the reflection ABI (#42)
Completes the half of #24 that was explicitly deferred as blocked: entity-space
queries to sit alongside the grid-space Grid.*/pathfinding that shipped in
07e5a20. Query.* answers questions about the live entities that carry a
property, built directly on the EV2 reflection ABI (world_query_next/world_get):

  - Query.count(prop) -> int                 how many live entities carry prop
  - Query.first(prop) -> int                 the lowest-id bearer, or -1
  - Query.nearest(prop, pos, xf, yf, x, y)   the bearer closest to (x,y), or -1
  - Query.within(prop, pos, x, y, r, xf, yf) -> []int   every bearer within r

prop is a property id (World.prop_id); the spatial forms read a position from a
coordinate property `pos` at two int field ids (World.field_id), so `prop` can be
a discriminating tag distinct from the position component ("nearest Enemy"), or
the same id to query the coordinate component itself. Distances are exact squared
integers (no sqrt), ties break to the lower entity id, and `within` returns
entities in ascending id order — so every answer is deterministic and replay-safe.

The engine (runtime/native/query.ludic, ~55 lines of Ludic, C-free) is a linear
scan over the entity table — ample for the entity counts Ludic targets, the same
reasoning as the grid pathfinder's open set; a bucketed/quadtree index is a
future optimisation, not a correctness need. It is spliced on demand when the
parser sees Query.* (g_uses_query), which also force-emits the reflection ABI so
a Query program needs no @events of its own (previously the ABI required them).

examples/library/query.ludic asserts 18 cases over five entities at known
positions (count/first with a component filter, nearest with a separate tag vs
position property, within radii incl. r=0 and the empty-property case), wired
into x test (now 63 passed). Docs: a Query section + 4 per-symbol pages,
inventory/coverage green. Seed reseeded; the C-free bootstrap fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-31 13:19:25 +03:00
.claude Baseline: Ludic compiler + toolchain, Phase 1 syntax fixes complete 2026-08-27 15:15:35 +03:00
.forgejo feat(tooling): port the docgen site generator to Ludic (no Python) (#41) 2026-08-31 02:12:32 +03:00
assets Baseline: Ludic compiler + toolchain, Phase 1 syntax fixes complete 2026-08-27 15:15:35 +03:00
changes chore(release): v0.1.0 2026-08-30 23:48:02 +03:00
docs feat(stdlib): add Query.* — ECS spatial queries over the reflection ABI (#42) 2026-08-31 13:19:25 +03:00
examples feat(stdlib): add Query.* — ECS spatial queries over the reflection ABI (#42) 2026-08-31 13:19:25 +03:00
runtime feat(stdlib): add Query.* — ECS spatial queries over the reflection ABI (#42) 2026-08-31 13:19:25 +03:00
selfhost feat(stdlib): add Query.* — ECS spatial queries over the reflection ABI (#42) 2026-08-31 13:19:25 +03:00
tools feat(stdlib): add Query.* — ECS spatial queries over the reflection ABI (#42) 2026-08-31 13:19:25 +03:00
.editorconfig chore(repo): add .editorconfig, reconcile .gitignore, add x clean 2026-08-30 16:46:10 +03:00
.gitignore chore(repo): ignore the dist/ release output 2026-08-30 23:52:25 +03:00
CHANGELOG.md chore(release): v0.1.0 2026-08-30 23:48:02 +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 docs: move design/roadmap docs to the wiki, trim the repo root 2026-08-31 00:36:19 +03:00
CONTRIBUTING.md feat(release): SemVer + ludicc --version, changesets, and x release 2026-08-30 23:46:59 +03:00
LANGUAGE.md docs: move design/roadmap docs to the wiki, trim the repo root 2026-08-31 00:36:19 +03:00
LICENSE docs: add Apache-2.0 LICENSE for the compiler/runtime source 2026-08-30 16:46:39 +03:00
README.md docs: move design/roadmap docs to the wiki, trim the repo root 2026-08-31 00:36:19 +03:00
VERSION chore(release): v0.1.0 2026-08-30 23:48:02 +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.

The design and roadmap material lives on the wiki: the Events, Networking, Scenes, Lifecycle, Mobile and Syntax-redesign design records, the Bootstrap deep-dive, and the Luanti roadmap. The root keeps only this README plus the two user-facing references, LANGUAGE.md and 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 <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. 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.

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.