Skip to content
Back to skills

Puck Dsl

ASecurity

Maps the .puck language: grammar, let/template/for, map/range builtins, puck compile/decompile/lint/format, PUCKnnn errors. Use for any .puck edit or JSON-to-.puck conversion. Shapes: sdf-authoring.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
testinggoc#nodeexpresstesting

Works with

  • cursor
  • cli

Security analysis

A100/100

Pro scans all 20 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add ByteTerrace/Puck --skill puck-dsl --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Puck Dsl?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Puck Dsl
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/byteterrace-puck-dsl/badge)](https://www.skillsdirectory.com/skills/byteterrace-puck-dsl)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: puck-dsl
description: "Maps the .puck language: grammar, let/template/for, map/range builtins, puck compile/decompile/lint/format, PUCKnnn errors. Use for any .puck edit or JSON-to-.puck conversion. Shapes: sdf-authoring."
---

# The `.puck` authoring language

`.puck` is the one authoring language behind two document vocabularies:
`puck.world.definition.v1` (`Puck.World.Transpiler`) and `puck.cartridge.v1`
(`Puck.GamingBricks.Transpiler`). Both ride one schema-agnostic core,
[`Puck.Transpiler`](../../../src/Puck.Transpiler/README.md): parser, printer,
lowering, diagnostics, module resolver and unit arithmetic. This skill maps that
core and its CLI loop; what a block means is the vocabulary skill's (see the
routing table). When this file and the code disagree, the code wins and this
file is corrected in the same change.

## Where things live

| Thing | Where |
|---|---|
| Parser, printer, lowering, diagnostics | `src/Puck.Transpiler` (`Parsing/PuckParser*.cs`, `Formatting/PuckPrinter*.cs`, `Lowering/DocumentLowering*.cs` and `DocumentBuiltins.cs`, `Diagnostics/PuckDiagnosticCodes.cs`) |
| World vocabulary, its linter and language server | `src/Puck.World.Transpiler` (`Vocabulary/` describes every construct once; `Validation/PuckLinter*.cs`; `Lsp/`) |
| World compile contract | `src/Puck.World.Transpiler/WorldCompiler.cs`; `WorldDocumentMembers.cs` walks the emitted document along `WorldModelShape`: it refuses unconsumed members, lowers authored pins to canonical document identities, and writes each typed value's canonical form (float spelling, rotation sign, convex profile start, zero-is-absent shape members, a write's `value`/`fromState`, model member order) |
| Cartridge vocabulary | `src/Puck.GamingBricks.Transpiler` |
| Expression vocabulary (operators, named functions) | `Puck.State.ExpressionOperators`, `Puck.State.ExpressionVocabulary` |
| CLI verbs | `src/Puck.Cli.Worlds` (`Transpiler/`: compile, decompile, lint, lsp, migrate, embed; `Test/`; `Vocabulary/`), `src/Puck.Cli.Format` (format) |
| Shipped sources | the world packages, `worlds/<id>/` (the debug world: `worlds/debug/**/*.puck`, cartridges under `worlds/debug/cartridges/`), `src/Puck.World.Transpiler/Samples/`; the quarantined worlds under `experimental/` are prior art |

A document's `schema:` field selects its vocabulary, never the filename.

## Grammar that bites

- **One spelling per shape, at every depth.** A literal container is always a
  block with no colon (`host { }`, `cameras [ ]`, `variables [ ]`,
  `arrays [ ]`), inside an object literal too
  (`{ name: "lit", pixels ["00000000", "11111111"] }`). A scalar always takes a colon
  (`name: "cursor"`, `initial: 0`). A colon in front of `{` or `[` is
  **PUCK040**, the most common mistake; the fix is to drop the colon. A value
  written as a call or a name keeps its colon even when it produces a
  container: `pixels: map(range(0, 8), row => "00000000")`, `origin: bounds`.
- `let name = expr` is a compile-time constant; `base with { member: value }`
  derives a record from an object (shallow: members replaced or added, a nested
  override spelled by nesting, `speed: kit.speed with { value: 5 }`) and lowers
  to the object written out; `function name(p, q = d) = expr` names a value
  or a guard, inlined where it is called (a document value, a gate, an
  effect), never recursive; `template name(p, q = d) { }` a
  parametric block; `rule template name(p) { }` a block of rule-body
  statements (locals, `when`, `if`, effects) a rule body calls and splices
  (`worlds/standard/seat.puck`'s `seatPress`); `import "name" [as alias]`, `export` and
  `use m as a(args)` compose modules.
- **An import names, never spells a file.** `import "skies"`, not
  `import "skies.puck"` (**PUCK121**). The named file decides what comes in: a
  module library (a source emitting no document) hands over its `let`s and
  templates; anything else is a document the importer composes at load, whose
  own `let`s stay its own. A library importing a document is refused (PUCK017).
  A source of imports alone hands over nothing of its own, so it is a document
  fragment that composes what it imports, not a library (`worlds/standard/standard.puck`).
- **Compile-time `for` generates rows; builtins generate values.**
  `for i in range(0, n) { }` and `for (item, index) in [...] { }` expand at
  compile time, and the document carries the rows they produce. Inside one
  value, use the collection builtins: `range`, `length`, `concat`, `map`,
  `filter`, `reduce`, `distinct`, `sort`, `groupBy`, each taking a lambda
  (`i => 0`). An array of n zeros is `map(range(0, n), i => 0)`; it runs once
  at compile time, the compiled JSON still holds every value, and play-time
  behavior is unchanged. A lambda anywhere else is **PUCK042**; an unevaluable
  call **PUCK041**; a `for` over something not known at compile time
  **PUCK044**.
  `range(start, count)` takes a count: `range(16, 16)` emits 16 through 31.
- **A `for` inside a rule body unrolls into the body.** A loop
  over an n-element array that declares `local $"name{i}" = …` compiles to a
  rule carrying n separate locals, `name0` through `name<n-1>`, each with its
  element substituted as a literal; the document holds no loop. Later
  statements read each local by its resolved name, and each is priced like a
  hand-written local. A `for` reads any compile-time array: a `let`, a
  literal, or a builtin's result.
  World locals precede every gate and effect and evaluate in declaration
  order. A late declaration is refused; move it before the first gate or
  effect to make its pre-effect read explicit.
  A rule has one gate and one decision, so a `when`, `decision` or nested
  `rule` inside a rule-body `for` is **PUCK114**; write the condition in the
  rule's own `when`, or in an `if` inside the loop.
- Units (`s ms min h m cm mm hz % pct deg rad`) convert in the core; a unit on
  a field the vocabulary does not dimension is **PUCK024**, an inadmissible
  unit **PUCK025**.
- Strings: plain `"..."`, interpolated `$"...{expr}..."`, raw `"""..."""`, raw
  interpolated `$"""..."""`. An interpolated string computes a name, key,
  number or text, never an expression (**PUCK112**). Backquote a name the bare
  form cannot carry (`` `seat-1` ``).
- **Hashes have one authored spelling.** Write `hash: auto` for automatic
  hashing, or `hash: "sha256/<64 lowercase hex>"` for a digest. Quoted `"auto"`,
  bare digests and `sha256-64/` pins are refused by name during compilation.
  A null document hash carries no pin: decompilation omits that member, rather
  than inventing an empty digest or an automatic hash.
  Font pins cover the font bytes; canonical document rows (creations, patches, tunes,
  music and tables) pin their canonical document, whose compiled identity is
  still the canonicalizer's digest. The printer restores the authored prefix.
- Generated names use `$` inside a document and `~` in file names
  (`Puck.State.GeneratedName`); an author name using them is **PUCK113**.
- **Control flow is core-parsed; each vocabulary decides what its rule shape
  carries.** The parser accepts `if`/`else`, `repeat`/`break`, call-form gates
  and compound assignment for every vocabulary. A world rule lowers `if`/`else`
  to a conditional effect, but its body is straight-line, so `repeat` and
  `break` have nothing to lower onto (**PUCK037**). The refusal names the
  alternative: a rule already runs once per matching subject, so iterate with
  the rule's `forEach`; a fixed count unrolls with a compile-time `for`, or
  folds into one effect (`x += 3`). A world rule carries `=` (`setState`) and
  `+=` and `-=` (`addState`, negating subtraction's operand). Other compound
  operators lower to `setState` over an expression; a computed destination key
  is evaluated once when that effect runs. A cartridge-only `key(...)` gate in a world is
  **PUCK038**. Cartridge rules admit `if`, `repeat`, `break`, `key(...)` gates
  and compound assignment.
- **A `test`'s `expect` reads one world, or names the document it reads.** A line
  reads the booted world; a block named for a sibling document the world
  references (`garden { bodyState(body, 0, size) == 0.25 }`) is judged in the
  instance a crossing starts there, passes on any tick the claim holds, and fails
  when nothing crossed. An absent body reads every register as 0, so a claim of
  `0` there also claims presence
  ([testing a world](../../../docs/authoring/testing-a-world.md#crossing-into-another-document)).
- **`allow CODE "why"` acknowledges a deliberate warning or note** over the
  statement on the next line (a row, a `let`, a template call), never an error.
  It changes nothing in the document; an unknown code, a missing reason or
  nothing to cover is **PUCK124**. A diagnostic is reported against the file and
  line that authored it, an imported library's or a basis's included, and
  `puck lint <dir>` prints one placed in another file once.
- **Spellings that mean the same compile to one document.** The
  spelling-equivalence corpus
  (`tests/Puck.World.Transpiler.Tests/SpellingEquivalence/`) holds each pair; a
  new spelling lands with its pair, and a pair that fails is a compiler defect.
  Constants fold before lowering: a name bound to a number, an enum member, an
  exact operator over constants and a ternary on a constant condition become
  their value, in gates and values alike.
- Gates (`when`, `if`, `expect`) accept ordinary expressions. Zero and absence
  are false; nonzero numeric values are true. `and` and `or` short-circuit;
  `not` negates truth. They yield Int 0/1 and bind below comparisons. Bitwise
  `&`/`|` bind above comparisons. `compareState(...)` and `compareValue(...)`
  in a source gate are refused by name; write a comparison such as `left == right`.
- **One bracket rule.** `row.key` names a literal key; `row[expression]`
  evaluates. Names in brackets resolve lexically, so an unknown bare word is
  an error. Use `row["key"]` for a quoted literal key. Read a local as `name`,
  never `local(name)`; use `row[x]`, never `row[(x)]` or `row[slot[$value]]`.
  Functions are recognized only in call position, so a local may be named `sign`.

[references/grammar.md](references/grammar.md) has the full grammar with
examples; [references/diagnostics.md](references/diagnostics.md) the codes
authors hit most. `PuckDiagnosticCodes.cs` declares every code once, with its
meaning as the XML doc comment.

## Authoring a console command

The owner's ruling: a console command composed from existing verbs is authored
in a world document, never as C# work. A `commands` row lowers through the
generic value path (no sugar); its full contract is
[document-authored commands](../../../docs/reference/commands.md#document-authored-commands):

```text
commands [
  {
    name: "gallery.show"                  // namespaced; never an engine verb's name
    help: "Stands a body at the gallery and picks a layout."
    parameters [
      { name: "layout", kind: Choice, values ["play", "front"], default: "play" }
      { name: "body", kind: Body, default: "0" }  // a default is written as typed
    ]
    lines [
      "body.pose spawn:gallery {body}"    // {name} substitutes; {{ and }} are literal braces
      "view.override layout {layout}"
    ]
  }
]
```

- Kinds: `Text` (default), `Number`, `Integer`, `Choice` (`values`), `Body`
  (0..4095), `Spawn` (a `spawnPoints` id). Arguments are positional plain
  tokens; required parameters come first; every parameter appears in a line.
- A line's first token (the verb) is literal; it may be another authored
  command (no loops) or any verb a binding may name. Operator verbs,
  unbindable verbs (`quit`, `world.save`, `storage.*`), engine-verb names and
  verbs no boot shape registers are refused by `puck compile --validate`
  (against the catalog `puck verbs` generates) and again when the World
  validates the document (boot, `world.reload`). A rendered-only verb
  (`world.debug-view`, `world.slice`) is fine in a line: a headless boot
  admits it and refuses only that line when it runs ("not available in this
  host"). Regenerate the catalog with `puck verbs` after adding or changing a
  verb.
- `commands` is keyed by `name`, so each imported document authors its own
  rows. Bind one like any verb (`command: "debug.go", text: "chess"`), or run
  `run <name> [args]`. Verify by booting the world: `help --names <prefix>`
  lists it, and the command answers its lines' output.

## The verbs

```text
puck format <file> --check
puck lint <file> --strict
puck compile <file> --validate        # writes <source>.world.json (and its .puckb) or <source>.cartridge.json
puck test <path-or-dir>               # boots each test block headless, one verdict a line
```

- Compilation refuses unknown document members by name, including members
  supplied through templates and bound objects. Converter-owned values retain
  their own shape rules, and reserved document extension keys remain data.
  `puck compile --validate` additionally runs the engine's semantic validation.
  `--output` names the destination; a world source declaring `world` entries
  writes one document per declaration and reads `--output` as a directory.
  A world document also gets its compiled world (`.puckb`) beside it.
  `--watch` recompiles on change; `--tree` mirrors a directory of sources.
- `puck decompile` is **one-way**: `let`, `template` and `for` never come back.
  Decompile once to import one hand-written JSON document, then edit the
  `.puck`. A row a `ground`, `test`, `border`, `door` or aliased `use`
  generated is refused by name: those are written in source, never printed
  from a document (decision P1).
- `puck format` prints `.puck` through `PuckPrinter` (two-space indent, lines
  broken past 120 columns, no options). A list of numbers written one to a line
  prints on one line when it fits and in filled rows otherwise; strings and
  rows of several elements keep the author's lines; the language server and `puck
  decompile` print through the same printer. Formatting is a fixed point and
  never moves a compiled document; reformat sources after a printer change.
- `puck lsp` serves completion, hover, symbols, formatting and diagnostics.
- `puck migrate <name> <path> [--check]` applies one registered source rewrite
  and proves each rewritten source compiles to the document it did before.
  A syntax migration must also visit retained write-target operands
  (`RowRefNode.Operand`), including keys nested inside them. Compile the
  composed entry document after migrating modules: isolated module checks can
  miss a literal key accidentally resolving to another module's row.
- `puck embed <path>` resolves authored `embed(...)` text into committed
  `.embeddings.json` lock files.
- `puck vocabulary [--check]` regenerates the world vocabulary page.

**Source and generated documents.** The owner's ruling treats cartridge JSON
twins as derived caches, never committed or byte-compared against a saved copy.
Compile the authored sources when needed; semantic round trips compare fresh
compilations. Imported modules contribute templates, not standalone documents.
A world source also has no committed document: the build compiles it. `--world foo.puck` boots straight from source,
`world.reload` recompiles it, and `world.save` writes each live edit into the
source that authors it, an import's row in the import and a value read from a
`let` in that `let`, keeping everything else as written; generated rows refuse
by name, and simulation `state` stays as authored.

After editing a world, run it (`dotnet run --project src/Puck.World -c Release
-- --world worlds/debug --devices none --exit-after-seconds 2`); verify a cartridge in a world (`rom-forge`).

## Route adjacent work

| Skill | Route there for |
|---|---|
| `puck-world` | World-document sugar (`rule`/`decision`/effects, `forEach`), section rules, world refusals, booting `--world`. |
| `sdf-authoring` | Creation shapes, materials and rigs in `.puck`. |
| `rom-forge` | The cartridge vocabulary and the compile/`forge.play` loop. |
| `gaming-bricks` | Emulator behavior once a cartridge is compiled. |
| `symbol-analysis`, `content-search` | C# semantics and text search; `.puck` name resolution goes through `puck lint` or the language server. |

Files in this skill

  • SKILL.md16 KB
  • evals/p01-json-to-puck-cartridge/graders/arrays-without-colon.md70 B
  • evals/p01-json-to-puck-cartridge/graders/cursor-row.md114 B
  • evals/p01-json-to-puck-cartridge/graders/map-range-builtin.md169 B
  • evals/p01-json-to-puck-cartridge/graders/no-colon-before-a-bracket.md209 B
  • evals/p01-json-to-puck-cartridge/graders/scalar-colons-kept.md131 B
  • evals/p01-json-to-puck-cartridge/graders/skill-fired.md36 B
  • evals/p01-json-to-puck-cartridge/graders/variables-without-colon.md73 B
  • evals/p01-json-to-puck-cartridge/prompt.md354 B
  • evals/p02-puck040-and-puck037/graders/colon-refused-at-every-depth.md146 B
  • evals/p02-puck040-and-puck037/graders/core-parser-accepts-repeat.md144 B
  • evals/p02-puck040-and-puck037/graders/distinct-fixes.md106 B
  • evals/p02-puck040-and-puck037/graders/foreach-or-unrolled-effects.md323 B
  • evals/p02-puck040-and-puck037/graders/if-is-not-refused.md117 B
  • evals/p02-puck040-and-puck037/graders/puck037-nothing-to-lower-onto.md203 B
  • evals/p02-puck040-and-puck037/graders/puck040-drop-the-colon.md161 B
  • evals/p02-puck040-and-puck037/graders/skill-fired.md36 B
  • evals/p02-puck040-and-puck037/prompt.md474 B
  • evals/p03-180-zeroes/graders/compile-time-only.md120 B
  • evals/p03-180-zeroes/graders/compiled-json-keeps-the-values.md104 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.