The generated site had the shape of a product launch page: a near-black navy ground with mint/coral radial glows, a gradient-clipped headline, a glowing pill badge, nine emoji feature cards and scroll-reveal animations. None of it told a reader anything about the language. It is now typographic and light-first — a warm paper ground, a serif display face, one ink-blue accent used only where it means something, and rules instead of floating cards. Colour is reserved for code. Dark mode is the same design with the ground inverted, defined once as tokens under a single prefers-color-scheme block. Structurally: - base.css holds the tokens and shared chrome; site.css and docs.css hold what is specific to the landing page and the reference pages. They ship as linked files rather than being inlined into all 900+ pages, which takes the site from 16 MB to 5 MB and means a design change no longer needs a regenerate to be seen. - api.css was dead — the generator never referenced it, rendering the API index with item.css — and is gone. docs.css replaces item.css and covers all four reference page kinds. - Grids draw their separators as cell borders instead of bleeding a ruled background through gaps, so a final row with fewer cards than columns stops cleanly instead of leaving a grey hole. The feature card count is not a multiple of the column count at any breakpoint. - Inline code loses its tinted chip; in a language reference, a box behind every keyword turns a paragraph into confetti. - Fonts are the platform's own, so the site makes no webfont request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
233 lines
9.6 KiB
JSON
233 lines
9.6 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": "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": "<code>bin/</code> is not checked in, so it is created first; then <code>clang</code> assembles the compiler's own checked-in LLVM IR seed, and that compiler builds <code>bin/x</code>, the task runner. This is the only step Ludic cannot do for itself."
|
|
},
|
|
{
|
|
"title": "Build the toolchain",
|
|
"html": "<code>bin/x build</code> produces <code>ludicc</code>, <code>ludic</code>, <code>ludic-fmt</code> and <code>ludic-lsp</code> — all compiled by Ludic, from Ludic."
|
|
},
|
|
{
|
|
"title": "Compile & run an example",
|
|
"html": "<code>bin/x app examples/games/snake.ludic</code> turns a <code>.ludic</code> file into a native binary. Run it to open a real window."
|
|
},
|
|
{
|
|
"title": "Go headless for tests",
|
|
"html": "<code>--headless</code> renders frames to a <code>.ppm</code> 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": "<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": "<code>bin/x tools</code> builds <span class=\"mono\">ludic-fmt</span> and <span class=\"mono\">ludic-lsp</span> — the same formatter runs as a CLI for pre-commit hooks and CI."
|
|
}
|
|
}
|