KILN

KILN notation reference

Units are metres. +Y is up, a model faces +Z (its front), +X is its left, and it stands on y = 0. Angles are degrees. Colours are names (or keys) from the project palette (PAGAN’s .pal.yaml format); "#rrggbb" works but warns (W104).

Project

kiln.toml            settings (kiln config show)
palettes/main.pal.yaml   colours (PAGAN format): models use their names
models/<name>.kiln.yaml  one model per file (name = file name)
styles/<name>.style.toml own styles; styles/<name>/inspiration/*.png|jpg reference images
decals/  imports/        images for decals; imported .glb/.gltf files
BRIEF.md  STYLE.md       what the game needs; how it looks (+ changelog)
# kiln.toml
default_style = "soft_toy"     # kiln style list
default_palette = "main"
output_dir = "build"           # inside the Godot project when export has "godot"
preview_dir = "previews"
export = ["glb", "godot"]
style_version = 1
[lint]
disable = ["W106"]

Model file

name: pilot                    # = file name (pilot.kiln.yaml)
description: bean crew member in a pilot cap
class: character               # size class: picks the triangle budget (style [budgets]) and size checks
tags: [crew]
status: draft                  # draft | approved
style_version: 1
style: soft_toy                # optional: else kiln.toml default_style
palette: main                  # optional: else default_palette
budget: 6000                   # optional: triangles (overrides the style's budget for the class)
lod: [2500, 800]               # optional: extra lower-detail .glb files
parts: [...]                   # the shapes (below)
rig: {...}                     # optional: bones (below)
skin: soft                     # none (static) | rigid | soft; default rigid when there is a rig
animations: {...}              # optional
variants:                      # optional: recoloured copies (<name>.<variant>.glb)
  copilot: { swap: { red: green } }
lint: { disable: [W106] }

Size classes in the built-in styles: prop, character, hero, creature, vehicle, machine, building, nature (and default for anything else). Size sanity (W108, largest side): character 0.2-4 m, prop 0.02-6 m, building 1-200 m…

Parts

Each part is one shape plus options. Parts are combined in order: each one joins (or cuts) what came before.

parts:
  - capsule: { from: [0, 0.3, 0], to: [0, 0.75, 0], radius: 0.3 }   # the shape and its parameters
    id: body                   # optional: which bone/node it belongs to (default: root)
    color: red                 # palette colour name
    material: plastic          # a style material preset (default: default)
    blend: 0.04                # smooth join radius with what's before it (0 = crisp seam)

Shapes

Every shape takes at: [x, y, z] (its centre; default [0, 0, 0]) and rotate: [x, y, z] (degrees, applied x then y then z, about its centre).

shape parameters
sphere radius
ellipsoid radii: [x, y, z]
box size: [x, y, z], round (edge radius; size kept)
cylinder radius, height (along y), round
cone radius (bottom), radius_top (0 = point), height (along y)
capsule from: [x, y, z], to: [x, y, z], radius, radius_to (taper)
tube points: [[x, y, z], ...] (2+), radius, radius_to: ropes, branches, rails, tails
torus radius (ring), thickness (tube radius); ring in the xz plane (rotate for others)
lathe profile: [[r, y], ...] spun around y: vases, bottles, bells, caps, chess pieces, round towers. The profile is a closed outline in the (radius, height) plane: go up the outside and come back down the axis (points with r = 0), e.g. a cap [[0, 1.06], [0.2, 1.02], [0.3, 0.9], [0.3, 0.8], [0, 0.8]]. Corners are rounded (smooth: true, default); smooth: false keeps them sharp
extrude points: [[x, z], ...] outline seen from above, height (along y), round: shields, signs, floor plans, stars
wedge size: [x, y, z]: a roof (ridge along x at the top)

For capsule and tube, the points are in the part’s frame (moved by at, turned by rotate). Everything else in a model (decal at, rig pivots, poses’ offsets) is in model space.

Part options

option meaning
id bone/node name (lowercase). Parts sharing an id move together. No id = root.
color palette colour name (required for adding parts)
material style preset: default, plastic, paint, fabric, skin, leather, rubber, wood, stone, metal, gold, leaf, glass, glow
blend smooth-union radius in metres with everything before it (cuts/intersects: smooth edge)
cut: true remove this shape from everything before it (holes, slits, windows, eye sockets)
intersect: true keep only what’s inside this shape (trim)
mirror: x also on the other side of 0 (x, y, z or a list); same id
mirror_id: arm_r with mirror: x: the copy is a separate part with this id (rig entries are mirrored too)
array: { count: 4, step: [1, 0, 0] } repeat in a row
array: { count: 8, around: y, center: [0, 0, 0], angle: 360 } repeat in a ring
shell: 0.02 hollow, walls this thick
twist: 90 degrees per metre about the shape’s y axis
noise: { amount: 0.01, scale: 0.2, seed: 3 } lumpy surface (rocks, bark, clay): amount in metres, scale = bump size
pattern: {...} a second colour painted on the part (below)
decal: {...} an image on the part (below)

Order matters: details that sit on a surface come after it. A part completely inside later parts shows nothing (W101). Parts thinner than about two mesh cells break up (W102): resolution comes from the style (soft_toy: 8 mm for a ~1 m model, coarser for bigger models).

Patterns

    pattern: { type: stripes, color: gold, size: 0.06, width: 0.3, axis: y }
  • stripes: bands across axis, one every size metres, width = fraction in the second colour.
  • band: one band of thickness size centred at at (one number: metres along axis, in model space): belts, trims, hat bands. E.g. { type: band, color: brown, size: 0.06, at: 0.4, axis: y } = a belt 0.4 m above the ground.
  • checker: checks size metres across.
  • dots: dots on a grid size metres apart, diameter width x size.

Decals

    decal: { image: decals/logo.png, axis: z, at: [0, 0.6, 0.3], size: [0.2, 0.1] }

Projects an image (PNG with transparency, e.g. made with PAGAN) onto the faces of this part that face axis (x, -x, y, -y, z, -z), centred at at (model space, on or near the surface), size = width, height in metres. It shows on that side only (a flag needs a second decal with axis: -z for its back). Transparent pixels show the part’s colour. Small images (<= 256 px) are sampled crisp (pixel art).

Rig and skin

rig:
  leg_l: { pivot: [0.12, 0.26, 0] }                 # joint position; parent defaults to root
  arm_l: { pivot: [0.3, 0.6, 0], parent: root }
  head:  { pivot: [0, 0.8, 0], meta: { hitzone: head } }
  helmet: { parent: head, meta: { detachable: true }, separate: true }
skin: soft
  • Every part id is a bone/node. pivot is where it turns (hip, shoulder, hinge, axle); default: its box centre.
  • A rig entry for arm_l also rigs its mirror_id twin (pivot mirrored; parent mapped to its twin if any).
  • skin: rigid: each id is a separate mesh node that moves as one piece.
  • skin: soft: one skinned mesh; weights come from each part’s distance, so blended joints bend smoothly. separate: true keeps a part a rigid mesh of its own (detachable things).
  • meta: plain values for game code: Godot node metadata (get_meta("extras")) and the manifest.

Animations

animations:
  wave:
    fps: 8
    loop: false                # default true (Godot loops it: glTF name "<name>-loop")
    interpolation: linear      # linear | step
    poses:                     # one per frame; a missing part = its rest pose
      - {}
      - { arm_r: { rotate: [0, 0, -120] } }
      - { arm_r: { rotate: [0, 0, -150] }, root: { offset: [0, 0.02, 0] } }
  walk: { fps: 12, cycle: { type: waddle, legs: [leg_l, leg_r], arms: [arm_l, arm_r] } }

Pose fields per part: offset: [x, y, z] (metres), rotate: [x, y, z] (degrees, about the pivot), scale: s or [x, y, z] (squash and stretch). Signs: positive x tips the part’s top towards the front (+Z), so a hanging leg or arm swings backwards with positive x and forwards with negative x; a drawbridge hinged at its bottom lowers forwards with positive x. About z: negative raises a right arm (-X side), positive raises a left arm. About y: positive turns left (counter-clockwise seen from above).

Cycles (8 frames unless frames:):

type options (defaults)
walk legs [leg_l, leg_r], arms [arm_l, arm_r], body root, swing 30, arm_swing 25, bounce 0.02
waddle as walk + roll 8 (side-to-side), swing 18
hop part root, height 0.2, squash 0.1
bob part, amount 0.05
spin part, axis y, direction 1
swing part, axis y, angle 20
breathe part, amount 0.03
wave arms (the last one waves), raise 150, angle 20
chain parts [ids in order from the root out; default: every id in file order], axis y, angle 15, phase 0.15: a travelling wave for flags, tails, tentacles, ropes

Imported models

name: oak
class: nature
source: { file: imports/tree_oak.glb, licence: CC0-1.0, author: Kenney, url: https://kenney.nl }
process: { height: 4.0, recolor: auto, rotate: [0, 90, 0] }   # or scale: 1.0; recolor keep = file's colours

Static meshes only. Colours snap to the palette (OKLab nearest), the style’s materials and budget apply, the model stands on y = 0, and the build writes CREDITS.md.

Styles

# styles/sky_pirates.style.toml
description = "Chunky toy airships, warm sunset light"
extends = "soft_toy"           # soft_toy | low_poly | toon | clay | diorama | another project style
notes = """Shapes, proportions, colours, materials, lighting in words (agents follow this)."""

[shape]
smooth = true                  # false = faceted (flat normals)
sharp_angle = 50               # smooth styles: edges sharper than this stay crisp
resolution = 0.008             # metres per mesh cell for a ~1 m model (finer = rounder, slower)

[budgets]                      # triangles per class
character = 8000
default = 4000

[materials]                    # roughness, metallic (0..1), emission (glow strength), rim (soft edge light)
plastic = { roughness = 0.42 }

[look]                         # the Godot look kit and preview lighting
shader = "standard"            # standard | toon
toon_bands = 3
outline = 0.0                  # metres (toon outlines), 0 = none
outline_color = "#1a1420"
tonemapper = "filmic"          # linear | filmic | aces | agx
exposure = 1.0
saturation = 1.15
contrast = 1.0
ssao = true
glow = true
ambient = 0.9
sky_top = "#4f8fe0"
sky_horizon = "#bcd7f2"
ground = "#a9c48f"
sun_pitch = -48.0              # degrees (negative = from above)
sun_yaw = 30.0
sun_energy = 1.1
sun_color = "#fff6e8"
shadows = true
fog = 0.0
fog_color = "#c8d8e8"

Lint

Errors: E000 usage/config, E001 file/YAML, E002 unknown field, E003 bad value, E004 unknown colour, E005 unknown material, E006 style problem, E007 rig problem, E008 animation problem, E009 nothing to mesh, E010 import file problem, E011 decal image missing.

Warnings: W101 part has no visible surface, W102 part thinner than the mesh resolution, W103 separate pieces, W104 colour not in the palette, W106 not standing on y = 0, W107 made under an older style_version, W108 size unusual for the class, W109 animated part without a pivot, W110 well over the triangle budget (patterns cost triangles: every stripe border is cut into the mesh; prefer few, large patterns, or model details as parts). Disable in kiln.toml [lint] disable or per model lint: { disable: [...] }.

Commands

kiln init [dir] --style soft_toy · kiln new model <name> --template bean|prop|empty --class C · kiln lint [models] · kiln preview <model> [--closeup ID|NUMBER] [--pose ANIM] [--vs] [--engine] [--variant V] · kiln lineup [models] · kiln gallery · kiln build [--verify-godot] · kiln status · kiln mark <models> --approved|--draft [--current] · kiln style list|show|new|bump · kiln config show|get|set · kiln import <file> --licence L · kiln credits · kiln doctor · kiln skill install · kiln mcp. Every command takes --json (before or after the command). --closeup takes a part id (root = parts without one) or a part’s number in the file (--closeup 3).

Build output

<output_dir>/models/<name>.glb (+ .<variant>.glb, .lod<N>.glb), with “godot”: models/<name>.tscn (glb + style materials) and looks/<style>.tscn (WorldEnvironment + Sun), manifest.json, CREDITS.md (imports). Manifest per model: glb, godot_scene, class, style, status, triangles, bounds and size (metres), skin, parts (parent, pivot, meta, separate), animations (gltf_name, fps, frames, loop, interpolation, duration), variants, lods, colors, source (imports).

In Godot:

var pilot: Node3D = load("res://assets/kiln/models/pilot.tscn").instantiate()
add_child(pilot)
pilot.get_node("AnimationPlayer").play("walk")
add_child(load("res://assets/kiln/looks/soft_toy.tscn").instantiate())   # once per scene: the style's light