Lanternhold

LANTERNHOLD — Art Bible

Status: iteration 1 first draft.

The look in one line: warm light against cool dark. An indigo night, amber windows and firelight, cyan wisps and crystal, and violet only where something is wrong. Most of the richness comes from systems (lighting, palette, particles, weather, parallax), not from drawings. The asset set is small. The renderer makes it rich.


1. Resolution, tiles, sprites, camera

1.1 Screen and tiles

1.2 Sprite sizes

Sizes are frame size in px. Anchor is bottom-center unless noted.

Thing Frame Notes
Player 12 × 16 body about 7 px wide; collision 6 × 14
Wick (cat) 10 × 8
Clay / Bone / Glass / Root golem 8×10 / 8×12 / 8×11 / 10×12
Wisp 3 × 3 core procedural trail
Gleaner / Arranger / Whisperer / Dimmer 12×7 / 8×20 / 14×12 / 11×7
Frog / moth 6×4 / 3×2
Lantern Tree 48 × 64
The Heart 64 × 64
Keeper skeleton 16 × 12
Machines tile size × 8 e.g. Alembic 16 × 24, Crucible 16 × 16 (GDD §6.2.3)
Item icon 12 × 12 inside 16 × 16 UI slots
Essence glyph 7 × 7 9 × 9 with tier ring

1.3 Camera

2. Palette

64 colors in 8 ramps × 8 shades (dark to light, index = ramp·8 + shade). Each ramp is hue-shifted: dark ends lean indigo and light ends lean warm or cyan by role. Mockup m01 confirmed every ramp is monotonic in OKLab lightness.

Ramp Role 0 1 2 3 4 5 6 7
R0 NIGHT void, sky, shadow, ink #06050d #0b0a1a #11122a #181b3d #212752 #2c3567 #3a467d #4b5a94
R1 SKY moon, clouds, rain, moonlit rims, distant islands #2a3350 #3b4769 #4f5e84 #6779a0 #8396bb #a4b6d4 #c8d6ea #eef3fa
R2 EMBER firelight, windows, brass, warm UI #1f0e0c #3b1712 #632614 #8f3b17 #bb5a1d #e0832b #f5b14a #ffe29a
R3 GLIM wisps, crystal, cool magic, glass #071c24 #0b3138 #0f4d50 #156f6b #20968a #3fbfa9 #82e3cd #d2fbef
R4 FLUX corruption, roots, the Heart, warnings #160a1c #2a0f30 #461549 #661e62 #8c2c7a #b44593 #d76db0 #f3a8d4
R5 EARTH soil, wood, bone, parchment #160f12 #261a1a #3a2720 #533826 #6f4e33 #8f6a48 #b8956f #e0cba3
R6 STONE rock, iron, pipes #0f1018 #1a1c27 #272a39 #363b4e #4a5065 #61687e #7f8699 #a7adbb
R7 LIFE foliage, moss, crops #0a1412 #10221c #173326 #20462f #2c5c38 #3e7542 #5e9150 #8fb46a

2.1 Palette rules

  1. No color outside the palette ever reaches the screen. This includes lighting, UI, particles and warp effects (§3). There is no pure black and no pure white.
  2. Violet means wrong. R4 shades ≥ 3 are reserved for flux, corruption, roots, the Heart, and error and warning states. Nothing cozy uses R4.
  3. Danger is violet, never red. The palette has no true red, and R2 shades 1–3 are rust-brown.
  4. Warm vs cool is a gameplay signal. R2 highlights mean warm light (safety from entities). R3 highlights mean cool light (flux suppression). Never use R2 or R3 shades ≥ 5 on non-emissive decoration.
  5. Value hierarchy:
    • Interactive foreground tiles use shades 2–5.
    • Background walls use the same ramp at −2 shades (minimum 0).
    • Distant parallax uses only R0 and R1 shades 0–3.
    • Emissives use shades 6–7.
    • Gameplay-critical objects (machines, lamps, chests) must keep a shade difference of ≥ 2 from the tile behind them.
  6. Per-sprite budget: at most 12 colors and at most 4 ramps. Tiles use at most 6 colors from at most 2 ramps.

2.2 Essence signature colors

Pipes, glyphs and jars show essences in these colors. The glyph shape is always the primary identifier; color is secondary, for colorblind safety.

Essences Color
Ember R2·5
Dew R1·4
Loam R5·5
Breath R1·6
Glim R3·5
Hush R0·6
Taint R4·5
Compounds a fixed palette index per compound: T1 from R1/R5/R6/R7 mid shades, T2 from shades 4–6 of R2/R3/R5/R7, T3/T4 from R4·6, R3·7, R2·7, R1·7

Identity colors are set in data/essences.json and don't change per seed.

2.3 Depth shifts (by zone)

Each stratum is a lighting zone with its own ambient floor, darkness target ramp and allowed terrain ramps. Terrain gets fewer hues with depth, so lights and flux pop harder the deeper you go.

Zone Ambient floor Darkness falls toward Terrain ramps (+ light ramps)
0 Surface R1·3 × 0.10 R0 R0, R1, R5, R6, R7 (+R2, R3)
1 Fossil R5·3 × 0.04 R5·0 / R0·0 (earthy dark) R5, R6 (+R0, R2, R3)
2 Crystal R3·3 × 0.06 R3·0 / R0·0 (teal dark) R6, R3, R1 (+R0, R2)
3 Roots R4·3 × 0.05 R4·0 (plum dark) R4, R5 (+R0, R3)
4 Heart R4·2 × 0.03 R0·0 R0, R4, R3
5 Tide (any cell under the tide line) R0·4 × 0.02 R0·0 as underlying zone; fog overlay
6 Dawn (Mend endless) R2·7 × 0.60 R1·2 all

2.4 Warp shifts

All warp shifts are post-LUT index remaps, so they stay palette-compliant.

Warp Effect
W ≥ 20 8 px dithered R4·1 vignette at the screen edges (25% coverage); one peripheral shape per 40 s
W ≥ 40 Shadow breath: every 20–40 s, R0·0–2 remap to R4·0–2 inside a random 96 × 64 region for 6 frames
W ≥ 60 Echo entities; one UI glyph swaps for one frame every 30 s
W ≥ 80 A second moon (R4·6 limb, R4·7 core) that has no pond reflection; stars align into an eye shape for 2 s every 5 min

Warp effects never appear inside the hearth sanctuary.

3. Lighting model (central system)

Light is mood and gameplay at once, so a single model drives both.

3.1 Gameplay light (from the sim)

Per tile, a warm and a cool level (0–15), computed by the sim (GDD §6.4).

3.2 Render light field

  1. Upload the tile light grid as an RG8 texture (warm, cool). Sample it bilinearly at tile centers, which gives a smooth falloff per pixel.
  2. Convert to 8 render levels: l = level/15 · 7.
  3. Quantize with a 4 × 4 Bayer ordered dither anchored to world pixel coordinates (not screen coordinates), so the dither doesn't crawl when the camera moves: L = clamp(floor(l + (bayer/16 − 0.47)), 0, 7).
  4. Flame flicker: the warm level of flame sources is modulated ±1 (on the 0–15 scale) by stepped 1D noise at 8 Hz. Wisp light breathes ±1 at 0.5 Hz.

3.3 The LUT (palette-locked lighting)

Every on-screen pixel is: albedo palette index + zone + warm level W (0–7) + cool level K (0–7) → output palette index, through a precomputed lookup table.

for zone z, albedo i, W in 0..7, K in 0..7:
    A = srgb_to_linear(P[i])
    E = amb[z] + (W/7)^1.4 · 1.8 · lin(R2·6) + (K/7)^1.4 · 1.5 · lin(R3·6)
    c = clamp(A ⊙ E, 0, 1)
    j = argmin_j  ΔOKLab(c, P[j]) + penalty(i, j, W, K)
    penalty = 0    if ramp(j) ∈ {ramp(i), darkRamp[z]}
            = 0    if W > K and ramp(j) = R2,  or  K > W and ramp(j) = R3
            = 0.04 otherwise
    post-pass: enforce monotonic lightness in W and in K
               (if L(lut[W][K]) < L(lut[W−1][K]), copy the previous value)

3.4 Darkness rendering

3.5 Atmosphere and halos

3.6 Implementation (WebGL2)

  1. Index pass: draw all world sprites and tiles into an R8UI color target holding the palette index, plus an R8 flags target (emissive, entity, UI, unlit). Use integer positions only.
  2. Resolve pass (full-screen): read the index, flags, light texture (bilinear), zone texture (tile resolution, nearest), Bayer texture, LUT texture and palette texture. Output RGB.
  3. Post-index remap: warp effects, the tide fog, lens overlays (overlays are drawn as indices with the "unlit" flag).
  4. UI pass: the same index buffer with the unlit flag, so UI is palette-exact.

This pipeline is the M1 renderer. Mockup M02 (planned) will prototype the LUT in Python before the build.

4. Environment

4.1 Sky (code)

4.2 Clouds (code)

Three layers built from two octaves of value noise, thresholded into 3 bands:

Wind sets direction and speed (×(1 + strength)). Rain fronts darken the layers by one shade and cover the moon.

Poisoned clouds (gameplay signal, GDD §15.3): when the flux reservoir R is high, cloud bands swap their R1 shades for the same shades of R4 (one band per 2,000 motes of R). A violet sky means the next rain will carry vented flux back down.

4.3 Rain (code)

4.4 Parallax stack

Layer Speed (×camera) Lit
Sky gradient 0 —
Stars 0.02 —
Moon 0.03 —
Far clouds 0.10 —
Distant islands 0.15 —
Mid clouds 0.30 35%
Island (tiles, sprites) 1.0 100%
Near rain 1.0 100%
Near clouds 1.15 35%

Distant islands (code) are procedural silhouettes 20–60 px wide in R0·3–4 with a dangling root fringe and 1–2 window dots of R2·6. One window goes dark every 3 Nights (GDD §13.2).

4.5 The void below

4.6 Tilt rendering

The island stays grid-aligned. Every procedural background layer (sky gradient, stars, clouds, distant islands, horizon) is evaluated in rotated coordinates at −θ around the screen center. Because these layers are procedural, rotation stays crisp with no rotated textures and no jaggies. Hanging lamps and chains swing to stay vertical in world space.

4.7 Strata looks

Stratum Terrain Light character Signature detail
Surface Grass R7·2–4 with R1·4 moonlit top edge and 1–2 px tufts overhanging the edge; soil R5·1–3; clay R5·3–4 with R6·3 flecks; house planks R5·2–4; shingle roof R6·2–3; window panes R2·7 emissive with R2·5 mullions moonlit, warm pools at windows the lit window seen from the island edge
Fossil Shale R6·1–3 with horizontal 1 px banding of R5·2; limestone R5·4–6, pale enough to bounce lantern light so walls glow warm; bone inclusions R5·6–7 with R5·4 shadows; bone moss R3·4 dots pitch dark between lamps; earthy darkness a skeleton seen first as a pale shape at the edge of the lantern
Crystal Stone R6·2–5; crystal facets R3·4–6 with R1·7 glints and emissive R3·7 cores; glass lichen R3·5 specks; tainted crystal R4·4–6 self-lit teal ambience the cave that is bright before you bring any lamps
Roots Root-wood R4·1–3 with fibrous 1 px R5·3 striations; a slow pulse of a +1 shade wave travelling along connected root tiles every 6 s (palette cycling); root-blooms R1·6 / R3·6 emissive plum dark a pipe bent into a curve
Heart Heartstone R0·1–3 with R4·4 veins — the Heart (64 × 64, hand-finished): the left half is R3 crystal facets, the right half R4 root knots, with a central emissive R3·7 / R4·7 seam; the inverse light is a ring of −3 levels that breathes on a 12 s cycle
Tide dithered R0·0–2 fog sliding horizontally at 3 px/s; lamp halos look clipped — —

5. Characters, creatures and entities

5.1 Silhouette rules

5.2 Animation frame counts

Frame rates are in fps; animation is stepped.

Character Animations Method
Player (keeper, hooded coat, scarf) idle 2 @1, walk 6 @10, jump 1, fall 1, climb 4 @8, use/dig 3 @12, sleep 1. A held item is drawn at a per-frame hand anchor, and the held lantern is a real light source. AI base + hand frames (D01)
Wick idle 2 @1, walk 4 @8, sleep 2 @0.5, hiss 2 @6 (arched back, R1·7 eye glints), pet 2 @4 hand (D02)
Golems walk 4 @6, work 2 @6, head-turn 2 (they turn to watch you), wait-facing-dark 1. Eyes are R3·6 in light and R4·5 in darkness. AI body + code puppet legs (stubs drawn by code, which gives deliberately stiff motion)
Wisps a 3 × 3 core (R3·7 center, R3·5 cross), a trail of 4 fading R3·5/R3·4 pixels, sine bob ±2 px at 0.7 Hz, a 1 px R3·6 sparkle every 1.5 s code
Gleaner skitter 4 @12, carry 4 @12 (the stolen item icon rides on its back, dimmed one shade) AI body + code legs
Arranger idle 1 plus a code "fold" (vertical squash in 1 px steps), walk 2 @3, work 3 @4 AI base
Whisperer 1 frame plus 3–6 procedural tendrils (1 px lines, R4·2) that curl toward the player AI base + code
Dimmer wing flap 2 @8. Wings are R0·0 with an R4·3 rim and a pale R1·3 "eye" spot on each wing. code
Echoes (hallucinations) identical to the real sprites sim flag
Frogs idle 2, hop 2 hand (D07)
Moths 2 px bodies, 1 px wings flickering code
Keepers' remains 6 poses, 16 × 12 (Ilse: 24 × 12, curled) AI + hand polish of Ilse (D06)

6. Machines and pipes: the visual language of automation

6.1 Materials (fixed ramp per material)

6.2 Silhouette by function

Function Shape cue
Distill (Alembic) tall, with a bulb
Combine (Crucible) squat, a bowl
Store box or jar
Move (Bucket Lift, Chute) vertical rails
Purify (Scrubber) vents and fins
Protect (Ward) a monolith with a glyph
Bind (Pylon) a cage of bars

Each function owns one silhouette family, so a player can identify a machine from its shape alone at 1×.

6.3 Pilot light

Every machine has a 2 × 2 pilot light at its top-right, which is emissive:

State Display
Working steady R2·6
Starved R2·6 blinking at 1 Hz
Output blocked / full steady R3·6
Flux-strained / error R4·6 blinking at 2 Hz
Off R0·2

6.4 Working motion

Code overlays add 2–4 frame motion only while a machine is working: bubbles rising in glass (2 px, R3·6), a flame (3 frames), and a gear (5 × 5, 4 rotation frames drawn by code). A machine that isn't moving isn't working. That's the single most important readability rule.

6.5 Pipes (code, 8 × 8 autotile, 16 connection masks)

6.6 Containers and golem routes

6.7 Lamps

7. Flux, mutation and warp visuals: eerie, never gory

7.1 Air flux (no lens)

Particles in R4, never solid color:

Band Look
Trace (10–49) 1 dim drifting mote per 128 px² (R4·1, 1 px). A lamp-held workshop at capacity sits here (S01: 3–14 motes), so a working factory visibly breathes without looking poisoned.
Tinged 1 sparkle per 64 px² (R4·5, 1 px, lives 0.5 s)
Fouled drifting haze: 1 px R4·2/R4·3 dither dots at 1/16 coverage, sinking 1 px/s (flux is heavy)
Thick 1/4 coverage plus slow curls
Rift 2–4 px tear lines of R4·6 bordered by R0·0, which open and close over 2 s

7.2 Soil and water flux

7.3 Crystal taint

Charge < 0.5 swaps the R3 shades for R4 shades of the same index, and the emissive core dims to R4·5.

7.4 Mutation (palette and shape, never flesh)

7.5 Hallucinations

Identical sprites, but they never darken light, never appear in reflections, and never leave a flux wake. The visual rules guarantee the tells (GDD §6.6). There are no "glitch" shaders and no chromatic aberration. The pixel grid always stays intact.

7.6 Collapse and cracks

8. UI style, fonts and icons

8.1 Fonts (no CC0, no purchase)

Fonts are defined as bitmap data in data/fonts/*.json (glyph rows as strings), and the build agent authors them.

8.2 Icons

8.3 HUD placement (480 × 270)

Element Position and size
Hotbar bottom center, 10 × 16 px slots with 2 px gaps
Depth ruler right edge, 12 × 160 px
Warp sigil top left, 16 × 16
Watch and moon top right, 24 × 16
Keeper's Log bottom left, 3 lines × 10 px

Lenses use their own overlay inks: flux in R4, light in R2 and R3, structure in R6.

9. Animation and effects guidelines

  1. Stepped animation only: 8–12 fps for characters and 4–8 fps for machines. No sub-pixel sprite motion; positions are whole internal pixels.
  2. Never rotate or scale sprites. Pre-draw frames, or use procedural geometry (§4.6).
  3. Palette cycling (shifting indices within a ramp) is the main ambient-animation tool: water shimmer, star twinkle, crystal sparkle, root pulse, Heart breath, pipe flow.
  4. Particle budget: 2,000 live particles. Every particle is 1–2 px, palette-indexed and lit through the LUT unless it's emissive.
  5. Light is an animation. Flame flicker, wisp breathing and lamp strain are changes in the light field, not sprite changes.
  6. No flashes faster than 3 Hz, nothing full-screen, and screen shake of at most 1 px. New threats fade in over at least 3 s (sim rule) and are never instant.
  7. Effects list:
    • lamp ignition (light grows over 1 s)
    • wisp catch (a 6-pixel sparkle)
    • successful Crucible test (a ring of R3·6 pixels expanding 8 px)
    • failed test (a violet puff of 30 haze particles)
    • gleaner theft (the item icon fades as it's carried)
    • arrangement (chalk spiral draws in over 1 s)
    • crack, collapse, tide rise (the fog edge advances), rain splash
    • the Visitor's passage

10. Production plan (no purchased or commissioned assets)

10.1 Methods

10.2 Full asset list

Each row belongs to exactly one milestone (i4 split the old multi-milestone rows; see sims/S10_scope.results.md). "×N" counts distinct assets. Tiles count as one type each, because their variants and autotile edges come from rules.

ID Asset Size / frames Method MS
A01 Sky gradient + dithered bands screen a M1
A02 Stars (3 kinds, twinkle) 1–3 px a M1
A03 Moon, phases, halo, reflection 40 px a M1
A04 Clouds ×3 layers noise a M1
A05 Distant islands + windows 20–60 px a M1
A06 Rain streaks + splashes 1×3/1×4, 2 fr a M1
A08 Void gradient + deep shapes screen a M1
A10 Light field + LUT resolve — a M1
A11 Atmospheric halos (parallax lighting) — a M1
A12 Flames (candle, lantern, hearth) 3 fr each a M1
A13 Wisps + trails 3×3 a M1
A14 Flux haze: trace, sparkles, drift, tears particles a M1
A15 Soil flux veins overlay 8×8 masks a M1
A18 Water surface, shimmer, reflections tile a M1
A20 Pilot lights 2×2 a M1
A21 Machine overlays (bubbles, flames, gears) 2–4 fr a M1
A27 UI panels, frames, buttons (9-slice) — a M1
A28 Fonts ×3 (Lantern 5×7, Wick 3×5, Keeper Hand) data a M1
A29a Essence glyphs, M1 set ×10 (6 primals, Growth, Ash, Salt, Glass) + tier rings 7×7 a M1
A30a Lens overlays ×2 (flux, light) — a M1
A31a Watch and moon icon 24×16 a M1
A32 Moths, motes 1–3 px a M1
A37a Plants: silverleaf (3 stages), nightshade ×2 8×8 b (L-system) M1
B01 Grass/topsoil + tufts 8×8 b M1
B02 Soil 8×8 b M1
B03 Clay 8×8 b M1
B07a Bedrock (M1 floor of the slice; becomes Stone in M3) 8×8 b M1
B11 Planks 8×8 b M1
B13 Glass 8×8 b M1
B14a Background walls ×2 (soil, plank) 8×8 b (−2 shade rule) M1
B15 Ladder, platform ×2 8×8 b M1
B17 Roof shingles 8×8 b M1
C01 Player base pose 12×16 c → D01 M1
C02 Wick base 10×8 c → D02 M1
C04 Gleaner body 12×7 c M1
C07 Lantern Tree 48×64 c → D08 M1
C08 Alembic (the M1 pipeline test) 16×24 c (pure pipeline, no hand edit before the test) M1
C09a Research Desk (with crucible bowl), Jar, Chest ×3 various c M1
C10a Hearth, bed, table, door, workbench ×5 various c (Hearth → D05) M1
C11a Lamps: candle, lantern, wisp lantern ×3 8×8 c M1
C14a Item icons, M1 set ×16 12×12 c (one batch sheet) M1
A16 Crack overlays 3 stages a M2
A17 Dust / rubble particles 1 px a M2
A19 Pipes autotile + flow 16 masks a M2
A23 Whisperer tendrils lines a M2
A25 Chalk marks, spirals, routes 3×5, 5×5 a M2
A29b Essence glyphs, remaining ×21 7×7 a M2
A30b Lens overlay: dark paths, structure ×2 — a M2
A31b Warp sigil (5 stages) 16×16 a M2
A34 Map view 1 px/tile a M2
A36 Mutation transforms (swaps, eye stamps, hollow fill) — a M2
A37b Crops: moonwheat, emberpepper ×2 8×8, 3 stages b (L-system) M2
A38 Broken-machine variants (keeper sites) from C-machines a (crack/missing-pixel/rust remap) M2
B04 Shale (banded) 8×8 b M2
B05 Limestone 8×8 b M2
B06 Bone inclusion stamps 8×8 b M2
B12 Stone brick 8×8 b M2
B14b Background wall: stone 8×8 b M2
B15b Rope, beam, scaffold ×3 8×8 b M2
B16 Rubble 8×8 b M2
B18a Moss and fern stamps 8×8 b M2
C03a Golems: clay, bone ×2 8×10, 8×12 c M2
C05 Arranger 8×20 c M2
C06 Whisperer body 14×12 c M2
C09b Prism add-on, shelf ×2 various c M2
C12a Machines ×13 (Crucible, Cistern, Golem Workshop, Spoil Chute, Vent, Scrubber, Ward Stone, Separator, Pond Intake, Rain Barrel, Valve, Chute, Bucket Lift head) per GDD c M2
C13 Keeper remains ×5 poses 16×12 c M2
C14b Item icons ×20 12×12 c M2
C15 Journal doodles ×12 32×32, 2 colours (R5·1 on R5·6) c M2
A07 Mist / tide fog dither field a M3
A24 Dimmer 11×7, 2 fr a M3
A26 Warp effects (vignette, remaps, second moon, peripheral shapes) — a M3
A30c Lens overlay: buoyancy — a M3
A31c Depth ruler 12×160 a M3
A35 Tilt-rotated background evaluation — a M3
B07 Stone 8×8 b M3
B08 Crystal (+ tainted via remap) 8×8 b M3
B18b Glass lichen stamps 8×8 b M3
C03b Golem: glass 8×11 c M3
C11b Lamps: ember lamp, crystal lamp ×2 8×8 c M3
C12b Machines ×4 (Auger, Crystal Tap, Assembler, Lift Bladder) per GDD c M3
C14c Item icons ×16 12×12 c M3
A22 Root tendrils / growth L-system a M4
A33 Heart pulse + inverse light — a M4
B09 Root-wood (+ pulse) 8×8 b M4
B10 Heartstone 8×8 b M4
B14c Background wall: root 8×8 b M4
C12c Machines ×3 (Crystal Nursery, Lodestone Keel, Root Stake) per GDD c M4
C14d Item icons ×8 12×12 c M4
C16 The Heart (base) 64×64 c → D03 M4
C17 Fetter Pylon, Dawn Lamp ×2 (hero) 16×24, 8×8 c → D18 M4

Cut from 1.0 (i4, GDD §19): A09 The Visitor (gameplay cut; the A08 "deep shapes" keep the atmosphere), the Root golem body, the Lure, the Root Graft, and the Glowfruit Lamp.

10.3 Hand-edit budget (≤ 20; 18 allocated)

# Asset
D01 Player animation sheet
D02 Wick sheet
D03 The Heart
D04 Title wordmark
D05 Hearth
D06 Ilse's remains
D07 Frogs
D08 Lantern Tree
D09 Gleaner
D10 Arranger
D11 House window and door details
D12 Keeper remains set
D13–D16 Four item icons, reserved for fixes
D17 Golem head (shared by all golems)
D18 Fetter Pylon and Dawn Lamp

10.4 The cleanup tool: lanternforge (the build agent must create it)

Python 3.11+, Pillow, NumPy. Deterministic, with pytest coverage for every step.

CLI

lanternforge clean   <in.png> --spec <spec.json> --out <out.png>   # AI image -> game sprite
lanternforge check   <dir>                                        # CI gate, nonzero on failure
lanternforge lut     --zones data/zones.json --out assets/lut.png # §3.3
lanternforge preview <sprite.png> --tiles <atlas.png> --light hearth|wisp|dark --out <png>
lanternforge sheet   <dir> --out <contact.png>                    # review sheets, blind tests

Spec (per asset)

{ "id": "alembic", "size": [16, 24], "frames": 1, "anchor": "bottom-center",
  "ramps": ["EMBER", "STONE", "GLIM", "EARTH"], "max_colors": 12,
  "max_shades_per_ramp": 4, "outline": "selective", "background": "magenta",
  "emissive": { "auto": true, "force_rects": [], "exclude_rects": [] } }

clean pipeline (in order)

  1. Key out the background. Remove pixels within ΔOKLab 0.12 of #ff00ff and despill magenta-tinted edge pixels toward their neighbors. If the input has real alpha, use it.
  2. Crop to the alpha bounding box.
  3. Detect the grid. If the image is pseudo-pixel-art, estimate its block size b from 2 to 32 by maximizing the autocorrelation of horizontal and vertical gradient energy at period b, then take the median color of each block. Otherwise, resize to fit the target (minus a 1 px outline margin) with an area-average in linear light.
  4. Place in the frame by the anchor rule.
  5. Binarize alpha at 0.5. Fill 1 px holes surrounded by ≥ 6 opaque pixels.
  6. Quantize. Deterministic k-means in OKLab (k = max_colors, k-means++ with seed 0), then map each centroid to the nearest palette color among the allowed ramps. Ramp coherence: inside each ramp, the clusters' luminance order must map to increasing shades (resolve collisions by shifting shades), and clusters that map to the same index merge. If a ramp uses more than max_shades_per_ramp shades, merge its closest adjacent shades.
  7. Clean up.
    • Orphans: a non-emissive pixel that differs from all 8 neighbors takes the mode of its neighbors.
    • Outline jaggies: remove doubled L-corners on 1 px outlines.
    • Thin-feature warning: flag any part narrower than 2 px whose total area is under 6 px.
  8. Outline (selective). Every transparent pixel that is 4-adjacent to the opaque sprite becomes outline. Its color is shade max(0, s−3) of the adjacent interior pixel's ramp, or max(0, s−2) when that interior pixel faces top-left (the light side). The interior is never black-lined. The same rule applies to tile edges (B-assets: exposed top edges get a +1 highlight, exposed sides and bottoms get −2 or −3), so sprites and tiles share one edge language.
  9. Emissive mask. Automatically mark interior (non-outline) pixels at R2·6–7, R3·6–7, R1·7, plus anything in force_rects, minus exclude_rects.
  10. Export an indexed PNG (PLTE = the 64 colors plus index 64 transparent via tRNS) and <id>.meta.json containing size, frames, anchor, the emissive mask (RLE), ramps used and color count.
  11. Report a before/after/preview strip at ×4 plus warnings: colors merged, contrast against the stratum's tiles below 2 shades, thin features.

check (CI) — every PNG under /assets must:

Prompt template (works with any text-to-image model):

Pixel art game sprite of {subject}, side view, {W}×{H} pixel sprite shown at 16× with hard square pixels, limited palette of {ramp words: e.g. "warm brass, slate iron, teal glass"}, flat even lighting from the top left, no cast shadow, no glow, no background scenery, solid flat magenta #FF00FF background, centered, full object visible, cozy dark-fantasy alchemy, clean readable silhouette. Negative: anti-aliasing, gradients, blur, glow, bloom, text, watermark, frame, 3D render, photo.

Generate 4–8 candidates, pick the one with the best silhouette, run clean, then preview it under the 3 lights.

The albedo rule: AI sprites are unlit albedo. The engine applies the night look through the LUT. Never prompt for "night", "moonlit" or "glowing". Emissive parts come from the mask.

10.5 Why this style survives a small asset set

11. Planned mockups

ID Mockup Status
m01 Palette swatch + vignette done. Ramps are monotonic; warm/cool/violet separate clearly on indigo; rain and halo dithering read well at 4×.
m02 LUT lighting test the same wall/tile strip under hearth, wisp, both, and darkness, for every zone
m03 Procedural tiles B01–B03, B11 with autotile edges
m04 Surface cross-section scene 480 × 270, composited with code layers plus procedural tiles; mood histogram per GDD M1 acceptance 2
m05 lanternforge prototype run on a synthetic "AI-like" input and compare with tiles under the LUT
m06 Flux haze and warp remaps —