Opaque surfaces under the additive light — the law bends: the dark occludes; the light still commutes above it. v0.3: two beats. v0.4: a steerable light. v0.5: light is a place; bodies throw shadows. v0.6: the world reflects. v0.7: occlusion complete. v1.0: walkable. Feature-frozen — the GPU edition is the next engine.
mirrorPlanecreateWorld({ …, mirrorPlane: 0, mirrorAlpha?: 0.35 })still water — every face and living light above the plane reflects beneath it, re-shaded at its mirrored place, dimmedpickpick(sx, sy, planeY = 0): V3 | nullscreen point → the ground, through the frame's camera basis — a tap becomes a place (the walkable city runs on it)surfaceget surface(): HTMLCanvasElementthe canvas this world draws on — for scenes that wire their own inputlightsworld.lights.push({ p: V3, intensity, falloff })point lights — Lambert × inverse-square falloff + a facet specular glint toward the camera; steerable per frameshadowPlanecreateWorld({ …, shadowPlane: 0 })every face, static and living, casts onto the plane along lights[0] (or the directional) — near-black fills in the same painter sortlightworld.light = { dir: V3, ambient, diffuse, signed }steerable like cam — faces shade at render time; the default is pixel-identical to the sky shade; signed:true gives true Lambert with a dark sideoccludeLightcreateWorld({ …, occludeLight: true })static glows join the painter sort — the dark can hide them (glows only; lines and living light still ride above)faceface(a: V3, b: V3, c: V3, { hue, sat?, light?, alpha?, group? })a static opaque triangle — painter-sorted far to near each frame, filled before the light passesdrawFacedrawFace(a: V3, b: V3, c: V3, FaceOpts)an opaque triangle that LIVES — laid from a grow hook, gathered into this frame's dark pass, sorted with the static facesslope shadelight × (0.55 + 0.45 · |normal.y|)computed once at lay time — a face is lit by how much it looks at the sky; winding never matters