{ "brand": "Ludic", "tagline": "A compiled language for 2D games, with the entity system in the syntax.", "repo_url": "https://git.workshopsoft.io/workshopsoft/ludic", "meta": { "title": "Ludic — a compiled game language", "description": "Ludic compiles ahead of time to a native binary. The entity-component system is part of the syntax, the runtime is deterministic fixed-point, and the compiler is written in Ludic — no C is generated, compiled or linked in a build.", "og_title": "Ludic — a compiled game language", "og_description": "Ahead-of-time compiled, with an ECS in the syntax and a deterministic fixed-point runtime. The compiler is written in Ludic; no C is generated, compiled or linked in a build." }, "nav_links": [ { "label": "Features", "href": "#features" }, { "label": "Examples", "href": "#showcase" }, { "label": "Get started", "href": "#start" }, { "label": "API Reference", "href": "api.html" } ], "ref_nav_links": [ { "label": "Home", "href": "index.html" }, { "label": "API Reference", "href": "api.html" } ], "hero": { "title_pre": "A compiled language", "title_accent": "for 2D games.", "lead": "Ludic compiles ahead of time to a native binary — no engine to install, no interpreter, nothing shipped beside the executable. The entity-component system is part of the syntax, the maths is deterministic fixed-point, and ludicc is itself written in Ludic: no C is generated, compiled or linked in a build.", "snippet": "docs/site/snippets/hero.ludic", "snippet_name": "hello.ludic", "primary_cta": { "label": "Get started →", "href": "#start" }, "secondary_cta": { "label": "API Reference", "href": "api.html" }, "pipeline": [ ".ludic", "ludicc", "LLVM IR", "native binary" ], "pipeline_note": "ludicc lowers straight to LLVM IR and links a native binary. There is no C step in between and no runtime to ship alongside — the game is the executable." }, "features": { "kicker": "Why Ludic", "title": "A language shaped around the game.", "intro": "The things you normally bolt on — an entity-component system, a renderer, a deterministic clock, save/load — are primitives of the language itself. One way to do each, so there is little to decide and nothing to wire up.", "cards": [ { "title": "ECS in the syntax", "html": "property, model, and handler are keywords. Query entities with query [A, B, {Tag}] and iterate matches directly — no framework to wire up." }, { "title": "Deterministic runtime", "html": "Q16.16 fixed-point math and a seeded RNG mean the same inputs produce the same frame — byte-for-byte — every run. Ideal for replays and lockstep netcode." }, { "title": "Drawing & named colors", "html": "The Screen API draws rectangles, text and pixels with named arguments; Color.Crimson and 220 more names read like English and cost nothing at runtime." }, { "title": "One self-contained binary", "html": "A game compiles to a single native executable — no engine to install, no interpreter, no runtime shipped beside it. Build, and run the file." }, { "title": "Snapshot save/load", "html": "save() and load() serialize the entire ECS world — every entity, property and program var — in one call." }, { "title": "Scenes & state machines", "html": "scene/layer/become model mutually-exclusive game states with enter/exit hooks; match/machine/state handle dispatch and per-entity FSMs." }, { "title": "Retained UI & assets", "html": "Declare a widget tree as data with ui — panels, labels, buttons, 9-slice skins, keyboard focus. Sprites decode from PNG at runtime; text is real TrueType." }, { "title": "Modules & native calls", "html": "module + @export fn builds a shared library of plain native symbols; extern fn … = \"symbol\" reaches out to any native library when you need the platform." } ] }, "showcase": { "kicker": "Show, don't tell", "title": "Real programs, one toolchain.", "intro": "The same ludicc that builds a JRPG builds a from-scratch Snake and a scene demo. Nothing is hardcoded to a genre — and every token below links into the reference.", "samples": [ { "label": "snake", "name": "snake.ludic", "file": "docs/site/snippets/snake.ludic", "note": "A complete Snake — grid, growth, food, game-over — from primitives. State is named vars, colors are named, and every draw call says what each argument is." }, { "label": "scenes", "name": "scenes.ludic", "file": "docs/site/snippets/scenes.ludic", "note": "One active scene at a time. `become` runs the old scene's on-exit and the new one's on-enter; layers draw in declaration order. State is a plain named var." }, { "label": "lifecycle", "name": "toggle.ludic", "file": "docs/site/snippets/lifecycle.ludic", "note": "Enable/disable at three scopes — entity, model, handler. Disabling never destroys data: a property's values persist, so a later enable restores them." }, { "label": "hello", "name": "hello.ludic", "file": "docs/site/snippets/hero.ludic", "note": "The smallest program that exercises the whole pipeline: properties, a spawn, a queried handler, and a render/quit." } ] }, "philosophy": { "kicker": "The philosophy", "title": "Batteries in the language, not in a framework.", "paras": [ "Most game code is plumbing — registering systems, wiring a renderer, threading state through a framework. Ludic makes those decisions once and bakes them into the language, so the code you write is the game's actual logic.", "That extends to the toolchain. ludicc is written in Ludic and recompiles its own source to a byte-identical binary, rebuilding from a checked-in IR seed with clang and nothing else." ], "stats": [ { "big": "0", "lbl": "lines of C in a build" }, { "big": "1", "lbl": "backend: LLVM IR" }, { "big": "=", "lbl": "byte-exact self-rebuild" } ] }, "start": { "kicker": "Get started", "title": "From clone to a native window.", "intro": "Bootstrap the task runner once from the checked-in IR seed, then build the toolchain and your game. Everything runs from the repo root.", "steps": [ { "title": "Bootstrap from the seed", "html": "bin/ is not checked in, so it is created first; then clang assembles the compiler's own checked-in LLVM IR seed, and that compiler builds bin/x, the task runner. This is the only step Ludic cannot do for itself." }, { "title": "Build the toolchain", "html": "bin/x build produces ludicc, ludic, ludic-fmt and ludic-lsp — all compiled by Ludic, from Ludic." }, { "title": "Compile & run an example", "html": "bin/x app examples/games/snake.ludic turns a .ludic file into a native binary. Run it to open a real window." }, { "title": "Go headless for tests", "html": "--headless renders frames to a .ppm from piped input — deterministic output you can diff in CI." } ], "terminal_name": "zsh — ludic", "terminal": [ { "comment": "bootstrap the task runner (clang + the IR seed, once)" }, { "cmd": "mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc" }, { "cmd": "bin/ludicc tools/x/main.ludic -o bin/x" }, { "blank": true }, { "comment": "build the toolchain, then an example (opens a window)" }, { "cmd": "bin/x build" }, { "cmd": "bin/x app examples/games/snake.ludic" }, { "cmd": "./build/snake" }, { "blank": true }, { "comment": "deterministic headless render for tests" }, { "cmd": "bin/x app examples/games/snake.ludic --headless" }, { "cmd": "printf 'ddddwww' | ./build/snake_headless" }, { "out": "→ writes build/out.ppm" } ] }, "editors": { "kicker": "Editor experience", "title": "One language server, every editor.", "intro": "ludic-lsp speaks LSP 3.17 over stdio: context-aware completion, diagnostics from the compiler itself, go-to-definition and rename across imported files, and comment-preserving formatting. It even understands ```ludic fences in Markdown.", "list": [ "VS Code", "JetBrains IDEs", "Neovim", "Helix", "Emacs", "Sublime Text", "Zed" ], "note": "bin/x tools builds ludic-fmt and ludic-lsp — the same formatter runs as a CLI for pre-commit hooks and CI." } }