No description
Find a file
Orkuncakilkaya 005cc39394 feat(compiler): resolve the install root from the binary's location
The engine runtime and the bundled ludic.* packages belong to the toolchain,
not to the project, so the compiler has to know where the toolchain lives. It
derived that from the directory the binary sits in, which is `bin` — so an
in-repo build only found runtime/native/cocoa.ll when the caller set
LUDIC_HOME=., and an installed compiler had no way to find it at all.

ludic_home() derives it properly instead: $LUDIC_HOME when set, else the
binary's directory with a trailing `bin/` stripped, else a $PATH scan for
argv[0] (an install is invoked by bare name, which carries no directory). Both
layouts that exist then have the same shape — ~/.ludic/{bin,runtime,packages}
and a repo checkout — so the same rule serves both and LUDIC_HOME becomes an
override rather than a requirement.

`import "ludic.core/…"` also falls back to $LUDIC_HOME/packages, after the
project's own ludic_modules/, so a project that has not fetched its own copy
gets the packages that shipped with the toolchain instead of a symlink farm.

The `ludic` multi-call name is dropped from the compiler: that name now belongs
to the CLI, and --run is the flag it drives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 22:01:39 +03:00
.claude refactor(docs): rebuild the site around reading, not launching 2026-09-05 01:47:36 +03:00
.forgejo docs: rewrite the README for someone meeting the language 2026-09-05 14:25:07 +03:00
assets feat(stdlib): Tiled P6 — infinite/chunked maps, .world stitching, base64+zstd (#74) 2026-09-01 16:07:10 +03:00
changes fix(docs): restore the token cards on the generated site 2026-09-05 14:41:10 +03:00
docs refactor(docs): rebuild the site around reading, not launching 2026-09-05 01:47:36 +03:00
examples refactor(runtime,packages,examples): named constants, package enums, idiom sweep 2026-09-05 01:12:26 +03:00
packages refactor(runtime,packages,examples): named constants, package enums, idiom sweep 2026-09-05 01:12:26 +03:00
runtime refactor(runtime,packages,examples): named constants, package enums, idiom sweep 2026-09-05 01:12:26 +03:00
selfhost feat(compiler): resolve the install root from the binary's location 2026-09-05 22:01:39 +03:00
tools fix(docs): restore the token cards on the generated site 2026-09-05 14:41:10 +03:00
.editorconfig chore(repo): add .editorconfig, reconcile .gitignore, add x clean 2026-08-30 16:46:10 +03:00
.gitignore fix(release): per-artifact checksums so a release can span hosts 2026-09-05 14:14:00 +03:00
CHANGELOG.md chore(release): v0.4.0 2026-09-05 01:52:18 +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 fix(docs): make the documented bootstrap work on a fresh clone 2026-09-05 01:48:01 +03:00
CONTRIBUTING.md fix(release): per-artifact checksums so a release can span hosts 2026-09-05 14:14:00 +03:00
LANGUAGE.md docs: correct stale references across LANGUAGE, README, COMPILING, CONTRIBUTING 2026-09-05 01:12:26 +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: rewrite the README for someone meeting the language 2026-09-05 14:25:07 +03:00
VERSION chore(release): v0.4.0 2026-09-05 01:52:18 +03:00

Ludic

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.

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.

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

Getting started

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:

mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc && bin/ludicc tools/x/main.ludic -o bin/x

Then build the toolchain and run a game:

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

Rendering is deterministic, so a frame can be produced without a window — this is what CI diffs:

bin/x app examples/games/snake.ludic --headless
printf 'ddddwww' | ./build/snake_headless    # writes build/out.ppm

bin/x help lists every command. examples/ 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 <expr> { … }; 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 is the full reference; the API reference 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:

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

See docs/PACKAGES.md for the manifest and lockfile model.

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

Status

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

The web/wasm32 backend is not currently available. The browser platform layer is in-tree under 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.

Releases follow SemVer and are cut from changesets by x release, then built and published by CI from the tag; see CHANGELOG.md.

Contributing

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

License

The compiler and runtime are licensed under the Apache License 2.0 (SPDX-License-Identifier: Apache-2.0).

The bundled Kenney art under assets/kenney/ is 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.