Getting started meant cloning the repository, bootstrapping a compiler and
learning a task runner called `x`. That is a contributor's workflow handed to
everyone who wants to try the language.
Installing is now one command:
curl -fsSL https://workshopsoft.pages.workshopsoft.io/ludic/install.sh | sh
install.sh puts a complete toolchain — compiler, CLI, engine runtime, bundled
ludic.* packages, formatter, language server — in ~/.ludic and adds it to PATH.
Prebuilt artifacts are checksum-verified; where a platform has none, or the
release predates this layout, it bootstraps from the compiler's own IR seed with
clang. The docs site publishes the script beside the pages that quote it, so the
page and the script can never come from different releases.
`x` becomes `ludic`, and the surface splits by audience. A user of the language
sees `new`, `run`, `build`, `test`, `add`, `fmt`, `lsp`, `doctor`, `upgrade`;
`ludic new` scaffolds a project that builds and plays as it stands. Everything
the toolchain repo needs moved under `ludic dev` — build, test, reseed,
bootstrap-cfree, docs-gen, release — unchanged apart from the namespace. Those
tasks read arguments one position further along, so dispatch_dev sets a shift
and commands use arg_n()/arg_total() rather than each knowing its own depth.
Release artifacts become complete install roots (bin/ beside runtime/, packages/
and VERSION) rather than bare binaries, which is what the installer unpacks.
`ludic dev test` asserts the whole shape: it stages an install, puts it on PATH
with no LUDIC_HOME, and runs new -> build -> test through it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
239 lines
10 KiB
JSON
239 lines
10 KiB
JSON
{
|
|
"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 <b>part of the syntax</b>, the maths is <b>deterministic fixed-point</b>, and <code>ludicc</code> is itself written in Ludic: <b>no C is generated, compiled or linked in a build</b>.",
|
|
"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": "<code>ludicc</code> 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": "<code>property</code>, <code>model</code>, and <code>handler</code> are keywords. Query entities with <code>query [A, B, {Tag}]</code> and iterate matches directly — no framework to wire up."
|
|
},
|
|
{
|
|
"title": "Deterministic runtime",
|
|
"html": "Q16.16 <code>fixed</code>-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 <code>Screen</code> API draws rectangles, text and pixels with named arguments; <code>Color.Crimson</code> 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": "<code>save()</code> and <code>load()</code> serialize the entire ECS world — every entity, property and program <code>var</code> — in one call."
|
|
},
|
|
{
|
|
"title": "Scenes & state machines",
|
|
"html": "<code>scene</code>/<code>layer</code>/<code>become</code> model mutually-exclusive game states with enter/exit hooks; <code>match</code>/<code>machine</code>/<code>state</code> handle dispatch and per-entity FSMs."
|
|
},
|
|
{
|
|
"title": "Retained UI & assets",
|
|
"html": "Declare a widget tree as data with <code>ui</code> — panels, labels, buttons, 9-slice skins, keyboard focus. Sprites decode from PNG at runtime; text is real TrueType."
|
|
},
|
|
{
|
|
"title": "Modules & native calls",
|
|
"html": "<code>module</code> + <code>@export fn</code> builds a shared library of plain native symbols; <code>extern fn … = \"symbol\"</code> 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 <code>ludicc</code> 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. <code>ludicc</code> 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": "One command to install. One to play.",
|
|
"intro": "Install the toolchain with a single command — it brings the compiler, the <code>ludic</code> CLI, the engine runtime and the editor tooling, and needs nothing else on your machine but a C toolchain to link with. Then create a project and run it.",
|
|
"steps": [
|
|
{
|
|
"title": "Install",
|
|
"html": "The installer downloads a verified toolchain for your platform into <code>~/.ludic</code> and puts it on your <code>PATH</code>. Nothing else is touched; uninstalling is <code>rm -rf ~/.ludic</code>. On a platform with no prebuilt toolchain it bootstraps from the compiler's own IR seed instead — same result, a few seconds longer."
|
|
},
|
|
{
|
|
"title": "Create a project",
|
|
"html": "<code>ludic new mygame</code> writes a manifest, a program that already moves something on screen, a test, and an <code>assets/</code> directory. There is no scaffolding to choose and no build file to write."
|
|
},
|
|
{
|
|
"title": "Run it",
|
|
"html": "<code>ludic run</code> compiles <code>src/main.ludic</code> to a native binary and launches it. <code>ludic build</code> stops at the binary — one self-contained executable, with nothing to ship beside it."
|
|
},
|
|
{
|
|
"title": "Test it, headlessly",
|
|
"html": "<code>ludic test</code> compiles and runs every <code>test</code> block in the project. <code>--headless</code> renders frames to a <code>.ppm</code> from piped input, so a game is deterministic enough to diff in CI."
|
|
}
|
|
],
|
|
"terminal_name": "zsh — ludic",
|
|
"terminal": [
|
|
{
|
|
"comment": "install the toolchain (macOS, Linux)"
|
|
},
|
|
{
|
|
"cmd": "curl -fsSL https://workshopsoft.pages.workshopsoft.io/ludic/install.sh | sh"
|
|
},
|
|
{
|
|
"out": "→ installed ludic 0.4.0 → ~/.ludic"
|
|
},
|
|
{
|
|
"blank": true
|
|
},
|
|
{
|
|
"comment": "a project that builds and plays as it stands"
|
|
},
|
|
{
|
|
"cmd": "ludic new mygame"
|
|
},
|
|
{
|
|
"cmd": "cd mygame"
|
|
},
|
|
{
|
|
"cmd": "ludic run"
|
|
},
|
|
{
|
|
"out": "→ a native window, running your game"
|
|
},
|
|
{
|
|
"blank": true
|
|
},
|
|
{
|
|
"comment": "tests, and a deterministic headless render for CI"
|
|
},
|
|
{
|
|
"cmd": "ludic test"
|
|
},
|
|
{
|
|
"cmd": "ludic build --headless"
|
|
},
|
|
{
|
|
"cmd": "printf 'ddddwww' | ./build/mygame_headless"
|
|
},
|
|
{
|
|
"out": "→ writes build/out.ppm"
|
|
}
|
|
]
|
|
},
|
|
"editors": {
|
|
"kicker": "Editor experience",
|
|
"title": "One language server, every editor.",
|
|
"intro": "<code>ludic-lsp</code> speaks LSP 3.17 over stdio: context-aware completion, diagnostics from the compiler itself, go-to-definition and rename across <code>import</code>ed files, and comment-preserving formatting. It even understands <code>```ludic</code> fences in Markdown.",
|
|
"list": [
|
|
"VS Code",
|
|
"JetBrains IDEs",
|
|
"Neovim",
|
|
"Helix",
|
|
"Emacs",
|
|
"Sublime Text",
|
|
"Zed"
|
|
],
|
|
"note": "Editors spawn <code>ludic lsp</code> — it ships with the toolchain, so there is nothing extra to build or install. The same formatter runs as <code>ludic fmt</code> for pre-commit hooks and CI."
|
|
}
|
|
}
|