Godot Lab · 2D · light-as-mechanic stealth · verified playthrough, desktop + mobile

Featherfilch

A magpie is robbing a museum at night, and the only thing that can catch her is light. The watchmen's lanterns are real shadow-casting Light2D cones; the display cases and columns carry LightOccluder2D polygons; and the detection test raycasts that same geometry. What you see shaded is literally the stealth state — step behind the case that blocks the light and you have blocked the guard's eyes too.

Play it now

Arrows / WASD to move · Shift to sneak · E to pry a case · X to flutter-dash (the guards hear it) · P pause · M reduced motion. Five jewels home to the nest before three catches.

▶  Play

The concept is the contract

Three concept sheets were generated before a line of code: night key art, a magpie character sheet and a museum prop callout board. Every named thing on those sheets is a real authored asset in the shipped game — 24 rows, no skips. The engine frame beside the key art is the live build, not a mock-up.

Featherfilch concept key art
Concept key art — the contract the piece is audited against
Featherfilch live in the engine
The same hall live in the engine: lantern cone, occluder shadow, the magpie in the dark behind a case

Gray boxes before art

The whole heist ran as gray rectangles first — patrol routes, cone geometry, occluder raycasts, the pry hold, the carry, the third guard arriving after the third jewel, the dawn restart. Three blockout rounds; round 1 failed because the autopilot parked 118 px from a case, outside the 78 px pry radius, and the fix was to path to the case centre and stop at 62 px. Only then did any art get made.

blockout gray boxes
Blockout: the hall as rectangles — the cone lights and occluders are already the real ones
the same hall with its art on
The same hall with its Blender art on

Every sprite is a Blender render — twice

There are no drawn sprites in this piece. The magpie and the guards are built, rigged and rendered in Blender at a fixed 64 px/metre, and every single frame is rendered twice: once for colour and once through a camera-space normal shader. The engine feeds both halves into a CanvasTexture, so a lantern sweeping past genuinely relights the bird's back and the parquet's bevels. Directions are never mirrored — flip_h does not re-sign a normal map's X channel, so all four facings are rendered for real.

magpie sprite sheet beside its normal-map pass
The magpie's 56-frame sheet (idle / walk-hop / sneak / dash / pry × 4 directions) beside its matching normal-map pass

The guards walk on real motion capture

The watchmen's gait is not hand-posed: CMU clip 02_01 is retargeted onto the guard rig through the platform's mocap kit and sampled into six frames per direction, with the long stationary clip 15_04 mined for the idle. The lantern arm is the one authored exception — a night watchman carries a lamp, he does not swing it like a free hand, so that arm's baked curves are stripped and hand-set while the mocap still drives the whole body.

guard walk frames, four directions
Guard: CMU 02_01 walk, four directions, lantern carried
face overlay sheet
Faces act: eyes, brows and mouth are separate parts under the head pivot, rendered as their own overlay sheet — neutral, alert, angry and a two-stage blink

The carried light is pinned to the drawn lamp

A carried light that floats near the lantern instead of on it is a defect class this lab has already paid for. Here the lantern-glass pixel is measured in Blender for every frame of every direction and written into char.json; in the engine both the warm pool and the detection cone are re-pinned to that anchor each frame. The cone the renderer draws and the ray the detection test casts start from the same point — there is no second, invisible geometry.

lantern anchors in four facings
The same anchor in all four facings — light on the glass, in every direction

Measured, honestly

The lab audits with Playwright on this host's software renderer — no GPU — so the frame rate below is the floor, not the ceiling. Unflattering rows included.

MeasurementValue
Export payload (pck)1.67 MB
Engine runtime (wasm, fixed)33.7 MB
Authored art in the payload1.29 MB
Audio stems0.46 MB
Blockout rounds3 (r1 failed: autopilot parked outside the pry radius)
Art audit rounds7 — r1-r5 found 10 visual defects, r6 clean, r7 confirming
Sprite frames rendered152 (76 diffuse + 76 camera-space normal)
Mocap clips usedCMU 02_01 walk, 15_04 idle (guards); the magpie is procedural
Selftest playthrough5/5 jewels, win at t=126 s
Frame rate in auditsoftware renderer, no GPU - a fraction of a frame per second
Known environment artifactthe software rasterizer stops presenting the world canvas after ~5 min of continuous play; the HUD survives. Investigated in cycle 1, not a scene-graph defect.

Real engine features, earned

Shadow-casting Light2D

Each guard's lantern is a texture-masked cone PointLight2D with shadows on. The cases and columns are LightOccluder2D — the shadows you see are the hiding places.

Normal-mapped sprites

Every frame ships a matching camera-space normal pass through CanvasTexture, so the bird, the guards, the parquet and the case bevels all take the lantern light.

Detection = the same geometry

Lit means: inside the cone angle, inside range, and a clear raycast on the occluder layer — cast from the drawn lantern glass.

TileMapLayer floor

Block-parquet, carpet runner and marble border tiles, each rendered top-down in Blender with a real relief normal map.

AStarGrid2D

Guard patrols and the selftest autopilot path the same grid, built from the actual collider footprints.

GPUParticles2D

Feather bursts on the dash and on a catch, dust motes drifting inside the skylight moonbeams.

Tuned constants

walk 240 px/s sneak 130 px/s — halves detection flutter-dash 620 px/s for 0.22 s, 1.4 s cooldown lantern cone 330 px, half-angle 0.42 rad Light2D energy 1.25 (1.65 blew out over normal maps) Light2D height 72 — normals go black at 0 detection fills in 1.10 s, decays at 0.9/s pry hold 1.6 s, radius 78 px noise radius 380 px — the dash is loud CanvasModulate 0.47 / 0.48 / 0.56 3 catches end the night; the first is free blink every 3–8 s, two-stage lid 64 px per metre, one PPU everywhere max_physics_steps_per_frame 30