{"schema_version":1,"slug":"homepage-desk-scene","title":"The coastal home, a technical record","project":{"title":"The Desk That Learned Depth","url":"/projects/homepage-desk-scene/","source":"_projects/homepage-desk-scene.md"},"record_url":"/projects/homepage-desk-scene/record/","json_url":"/assets/json/design-records/homepage-desk-scene.json","repository":"https://github.com/DylanTao/dylantao.github.io","branch":"main","updated":"2026-10-15","summary":"How the homepage's 3D coastal home is built: the Blender scripts that author the cave house, the cliff, five cartoon versions of me and their animal neighbours; the browser runtime that loads, lights and animates them; and the light lab that grew out of the onsen. Each technique names its files, the research it borrows from, what it cannot do yet, and the next move worth trying.","scope":"The home is an original, stylized browser world in an imagined La Jolla, not a surveyed place. Several techniques adapt published graphics research in small, bounded forms, and each entry says where it simplifies. Measurements come from one Windows desktop unless an entry says otherwise; they are not phone benchmarks.","credit":"Compiled on October 15, 2026 by Anthropic's Claude from the code, the scene notes, the provenance files and the October studio log, for Sirui Tao to review and extend.","read_first":[{"path":"docs/homepage-desk-scene-brief.md","why":"The active brief. Priority order, interaction contracts, the San Diego routine and the acceptance evidence map."},{"path":"artwork/coastal-home/PROVENANCE.md","why":"What was authored, generated or referenced, and every rebuild command."},{"path":"assets/js/home-scene/CONTACT-LIGHTING.md","why":"Contact-light equations, controlled comparisons and measured cost."},{"path":"assets/js/home-scene/ONSEN-WATER.md","why":"The height-field pool, its hand coupling and its numerical evidence."},{"path":"assets/js/home-scene/PHYSICS.md","why":"The ocean spectrum, sea reflection, sun position and record mechanics."},{"path":"assets/js/home-scene/SKIN-DIFFUSION.md","why":"The skin diffusion profile, curvature and its limits."},{"path":"assets/js/home-scene/THERMAL-STEAM.md","why":"Steam transport, volume compositing around glass and the light-well mask."},{"path":"docs/static-light-field.md","why":"The traced sky and lamp bounce, its probes and its cost."},{"path":"docs/coherent-wind-equations.md","why":"The curl wind field, leaf attachment and particle integration."},{"path":"docs/studio-log-2026-10.md","why":"The October design moves in full, with dimensions, backtalk and concepts."},{"path":".codex/skills/homepage-desk-scene/SKILL.md","why":"The agent overlay that routes desk-scene work."}],"constraints":["The homepage opens in 2D on every screen. 3D loads only after a visitor chooses it, a deliberate choice holds for the session, and any WebGL or model failure returns the 2D desk.","Keep the five adult male Sirui likenesses (long black hair, round glasses, clean-shaven), one at a time and new on each refresh.","The routine runs on `America/Los_Angeles`. Previewing an hour never lets a later clock tick seize the camera, and Now clears both previews.","One connected house, cliff and Pacific. No pedestal, detached arch, image backdrop or scenic photograph; the cutaway lifts only the local ceiling.","The selected record, its spin, the discovered cards and the avatar survive every mode switch.","Reduced motion and Pause motion compose still poses. Hidden or offscreen views stop their loops and add no catch-up time when they return.","Every new material, pass or override draw joins `program-warmup.mjs`, or its first draw blocks the page again.","Render-cost changes must change 0 pixels in seeded, paused-clock captures. Look changes need a matched A/B.","Compare performance variants interleaved, never batch against batch.","A new camera angle on the kitchen or lounge looks through a door opening, not a mullion.","Rerun `bin/build_loading_sketch.cjs` after a room's geometry or camera changes, and the bedding refit after an avatar's body or sleep clip changes.","Keep the Draco decoder marked binary in `.gitattributes`."],"tests":[{"path":"test/visual/desk-scene.spec.js","covers":"Nonblank WebGL, visible orbit and zoom changes, room continuity, records, recovery, reduced motion and the four viewports.","command":"npm.cmd run test:visual:scene"},{"path":"test/visual/scene-groups.json","covers":"The scene cases grouped for one-worker runs (scene-core-1, scene-exploration, scene-footer-light and others).","command":"node bin/run_visual_scene_group.cjs scene-core-1 --project desktop-1440"},{"path":"test/coastal-routine.test.mjs","covers":"Schedule boundaries, weekends, daylight saving, previews and Now.","command":"node --test test/coastal-routine.test.mjs"},{"path":"test/test_coastal_assets.py","covers":"Exports and editable sources, room anchors, activity clips, the first scene's compressed budget and the local Draco decoder.","command":"python -m unittest test.test_coastal_assets"},{"path":"test/test_scene_program_warmup.py","covers":"The shader warmup, its batches and core throttle, and the loading bar's sources of progress.","command":"python -m unittest test.test_scene_program_warmup"}],"systems":[{"id":"shared-desk","name":"Two views, one desk","summary":"The 2D collage and the 3D home share one record player, one discovery pile and one chosen avatar. The 3D home is an optional layer on top of a page that works without it.","techniques":[{"id":"shared-state","name":"One state across 2D and 3D","status":"shipped","since":"2026-06-17","what":"The 2D | 3D switch changes the view, not the state, so the selected record, its spin, the discovered cards and the avatar carry across.","how":"`assets/js/home.js` owns the 2D desk and the shared record state. Choosing 3D lazy-loads `home-scene/controller.mjs` through a small adapter, so the default page never downloads Three.js or the house. The choice is kept in `sessionStorage` for the session. If WebGL or a model fails, the controller dispatches `home-scene-unavailable` and the page returns to the 2D desk with its state intact. A cached back/forward return pauses and restores the controllers instead of rebuilding them.","files":[{"path":"assets/js/home.js","role":"The 2D desk, the shared record state and the lazy 3D adapter."},{"path":"assets/js/home-scene/controller.mjs","role":"The 3D runtime. Loading, mixer, camera, interactions, lifecycle and the album adapter."},{"path":"_includes/home/world_controls.liquid","role":"Scene controls, status line and loading bar."},{"path":"_sass/_coastal-home.scss","role":"Scene composition, the feathered edge and responsive layout."}],"tests":["test/visual/desk-scene.spec.js","test/record-keyboard.test.mjs"],"sources":[],"limits":["`controller.mjs` is one module of about 110 KB, so most scene work lands in the same file and parallel edits collide.","Room, avatar, activity and time controls exist only behind `?scene-lab=1`; ordinary visitors get the 2D album, the 2D | 3D switch and Look around."],"next":[]},{"id":"record-mechanics","name":"A driven record player","status":"shipped","since":"2026-10-01","what":"The platter is a driven rotor and the tonearm two critically damped coordinates, shared by the 2D player, its CSS fallback and the 3D study platter.","how":"`record-motion.mjs` targets 33⅓ RPM with a first-order motor, ω(t+dt) = ω_target + (ω − ω_target)·e^(−dt/τ), and integrates the angle exactly, so the result does not depend on frame rate. The motor responds in 0.72 s and brakes in 0.30 s. Each tonearm coordinate follows a critically damped equation with an exact update; the arm must lift clear of the disc before it swings, and a one-sided contact keeps the stylus above the record. All time is active time, so hiding the player adds no impulse. The inertia, 0.0036 kg·m², is illustrative rather than measured.","files":[{"path":"assets/js/home-scene/record-motion.mjs","role":"Rotor and tonearm mechanics."},{"path":"assets/js/home-scene/PHYSICS.md","role":"Equations and evidence."}],"tests":["test/coastal-physics.test.mjs","test/coast-wave-motion.test.mjs"],"sources":[],"limits":["A controlled motor model, with no measured drive torque, bearing friction, wow and flutter, or stylus dynamics.","The record is a visual spinner. Nothing plays audio, by design."],"next":[]},{"id":"active-time","name":"One active clock","status":"shipped","since":"2026-09-14","what":"Walks, clips, water, steam, particles and wildlife all advance on active elapsed time, never on the frame count.","how":"The controller's clock stops while the scene is paused, hidden, offscreen or under reduced motion, and its timestamp resets on recovery. Solvers take fixed ticks and accept at most 0.25 s per call; anything beyond that is reported as dropped time rather than replayed, so a stalled tab never returns with a catch-up queue. Route and clip timing follow active time, so a software renderer with seconds between frames does not stretch a walk. Animal acting and the record are sampled from absolute active time, so they cannot drift.","files":[{"path":"assets/js/home-scene/controller.mjs","role":"The active clock, suspension and recovery."},{"path":"assets/js/home-scene/wind-particles.mjs","role":"Fixed ticks with a bounded stall budget."},{"path":"assets/js/home-scene/onsen-water.mjs","role":"The pool's bounded time partition."}],"tests":["test/coastal-physics.test.mjs","test/coastal-wind.test.mjs","test/onsen-water.test.mjs"],"sources":[],"limits":["A long stall is deliberately not reproduced, so a particle or a wave after a stall is not the uninterrupted trajectory."],"next":[]}]},{"id":"loading","name":"Loading and the first frame","summary":"What happens between choosing 3D and the first composed view. Models stream in a fixed order, shader programs compile without freezing the page, a loading bar reports only finished work, and a pen sketch fills the wait.","techniques":[{"id":"streaming-order","name":"Streaming order and Draco decoding","status":"shipped","since":"2026-09-12","what":"The shell, the coast, the occupied room and the selected avatar load first; the other rooms and the clifftop planting stream afterwards.","how":"Startup never draws a partial house while the occupied room or the avatar is still loading. The models are Draco-compressed GLBs exported from the Blender sources. `model-loader.mjs` decodes them through a pinned decoder served as ordinary same-origin files, because the loader's default inline blob worker conflicts with the site's Content-Security-Policy. Streamed rooms join the house only once their shader programs are ready, and the clifftop planting (`flora.glb`, 2.2 MB) loads the first time a visitor goes outside.","files":[{"path":"assets/js/home-scene/model-loader.mjs","role":"Same-origin Draco decoding."},{"path":"assets/js/home-scene/controller.mjs","role":"Load order and room streaming."},{"path":"bin/prepare_draco_worker.py","role":"Extracts the static worker from the pinned loader."},{"path":"assets/js/vendor/three-r164/README.md","role":"Vendored loader sources and their hashes."},{"path":"assets/models/home/manifest.json","role":"Asset paths, room anchors and camera views."}],"tests":["test/test_coastal_assets.py"],"sources":["threejs-r164","draco"],"limits":["The shell (`home-shell.glb`, about 1.0 MB) and the selected avatar (0.5 to 0.85 MB) still gate the first view."],"next":[]},{"id":"program-warmup","name":"Shader programs compiled before the first draw","status":"shipped","since":"2026-10-12","what":"Every program the first view needs is created ahead of the draw and compiled on the driver's worker threads, in small batches, so the page keeps scrolling while the house compiles.","how":"Three.js compiles a program the first time it draws a material, and the draw blocks until the driver finishes. On ANGLE/D3D11 each of this scene's programs took 0.1 to 1.8 s, and the first view froze the page for about 13 s. `program-warmup.mjs` reproduces each draw's compile state exactly (target, lights, fog, object, shadow side), so the proxy it compiles is the program the real draw later finds. With `KHR_parallel_shader_compile` it asks whether a program is done instead of waiting. It creates programs in batches of six materials with a task boundary between them, and submits the next batch only while no more than half the logical cores' worth of programs are compiling. Polling uses the browser scheduler because test clocks freeze timers. Software rasterizers skip the warmup.","files":[{"path":"assets/js/home-scene/program-warmup.mjs","role":"Batched, throttled program creation and completion polling."},{"path":"assets/js/home-scene/environment.mjs","role":"One environment generator for the home's lifetime, with its programs created before the first capture."},{"path":"assets/js/home-scene/realism.mjs","role":"The finish passes register their extra programs with the warmup."},{"path":"bin/profile_coastal_startup.cjs","role":"Cold-context startup profiler; set `COASTAL_PROFILE_SEED` for comparable A/B captures."},{"path":"docs/evidence/coastal-startup-2026-10-12/README.md","role":"The October 12 startup checkpoint."}],"tests":["test/test_scene_program_warmup.py"],"sources":["khr-parallel-shader-compile","gregg2020systems"],"limits":["A material, pass or override draw that skips the warmup blocks the page on its first draw.","Throttling trades readiness for responsiveness. Half the cores moved median readiness from 4.66 to 5.17 s; a quarter removed almost every long task but cost 1.3 s."],"next":[{"move":"Pick the compile throttle per device class (core count, software renderer) from interleaved measurements of readiness and the longest task.","readiness":"needs-measurement"}]},{"id":"loading-bar","name":"A loading bar that reports finished work","status":"shipped","since":"2026-10-13","what":"A five-segment progress bar fills only from real events, so it can never run ahead of the work or move backward.","how":"`world_controls.liquid` renders a `role=\"progressbar\"` with five segments: the house, the coast, the room, Sirui, and the view's programs. `controller.mjs` fills the first four from each model's streamed bytes and parse, and the last from submitted warmup batches and the programs the driver reports finished. The bar hides once the first view is drawn. Harrison and colleagues found that the same wait feels shorter when a bar keeps moving toward the end; the honest way to earn that is to turn slow, invisible work into visible steps.","files":[{"path":"_includes/home/world_controls.liquid","role":"The progress bar markup."},{"path":"assets/js/home-scene/controller.mjs","role":"Segment progress from load and compile events."}],"tests":["test/test_scene_program_warmup.py","test/visual/desk-scene.spec.js"],"sources":["harrison2007progress"],"limits":["GitHub Pages serves the models gzipped, so `Content-Length` is the compressed size and a model's segment holds just short of full until its last byte arrives."],"next":[]},{"id":"loading-sketch","name":"A pen sketch while the house loads","status":"shipped","since":"2026-10-14","what":"Pen strokes draw the sea with today's sun or moon, rise into the cliff, open into the cut-away house and settle into the occupied room, then trace the first rendered frame as it fades in.","how":"`loading-sketch.mjs` hands an OffscreenCanvas to a worker so model parsing never stalls the strokes, and draws on the main thread where a worker cannot. The drawings are traced offline by `bin/build_loading_sketch.cjs` from object-ID and surface-normal passes of the authored views: object outlines weighted by how much of the frame each object fills, creases sharper than about 57°, and long straight runs favored by the structure tensor's coherence, after Saito and Takahashi's line drawings from geometry buffers. Eight drawings pack into 45 KB at three bytes a stroke. Strokes fly on a damped spring (ζ 0.68) along slight arcs, stretch along their velocity, pair with their next targets in Hilbert-curve order so a morph flows instead of tangling, and boil on twos once they land. The render is never held back more than 1.4 s. Reduced motion shows each drawing still, and automation sees the plain loading state unless the URL carries `?cinematic=live`.","files":[{"path":"assets/js/home-scene/loading-sketch.mjs","role":"Page side, worker hand-off and the first-frame trace."},{"path":"assets/js/home-scene/loading-sketch-core.mjs","role":"Stroke motion, Hilbert pairing and edge tracing."},{"path":"assets/js/home-scene/loading-sketch-worker.mjs","role":"Draws off the main thread."},{"path":"bin/build_loading_sketch.cjs","role":"Traces the drawings from the authored views."},{"path":"assets/models/home/loading-sketch.json","role":"The packed drawings."}],"tests":["test/loading-sketch.test.mjs"],"sources":["saito1990comprehensible"],"limits":["The drawings are fixed captures of the authored views and go stale when a room or camera changes.","Characters, P and the animals are left out of the traced drawings; the live first frame supplies whoever is home."],"next":[{"move":"Rerun `node bin/build_loading_sketch.cjs` after any room or camera change and check the hand-over on desktop and phone.","readiness":"ready"}]}]},{"id":"camera-navigation","name":"Camera and navigation","summary":"How visitors move the viewpoint. One envelope bounds every input, camera motion keeps its velocity through changes, zoom moves from room to house to coast, and the animals outside can be visited up close.","techniques":[{"id":"camera-envelopes","name":"Camera envelopes from the finished terrain","status":"shipped","since":"2026-09-14","what":"One set of limits applies to pointer, touch, wheel, keys and camera settling, built from a height grid ray-cast against the finished terrain.","how":"`bin/coastal_camera.py` ray-casts the final authored surfaces into a terrain-height proxy and writes per-view camera envelopes and cutaway tags into the manifest; a miss is sea at the renderer's mean level, never invented land. `camera.mjs` applies those limits to every input and to interpolated transitions. Front-room orbits dolly in before reaching a side wall or the gallery slab, the glass front may be crossed but the camera never rests close enough to slice a pane, and a room-to-room move projects an intersecting near plane toward the Pacific opening.","files":[{"path":"bin/coastal_camera.py","role":"Terrain-height proxy and camera envelopes."},{"path":"assets/js/home-scene/camera.mjs","role":"Applies the envelopes to every input."}],"tests":["test/coastal-camera.test.mjs"],"sources":[],"limits":["The height proxy is static and does not sample the animated wave surface.","Interior views use bounded arcs and a near-plane clearance proxy, not general camera collision."],"next":[]},{"id":"camera-motion","name":"Critically damped camera motion","status":"shipped","since":"2026-10-09","what":"Camera coordinates follow an analytic critically damped response, so a changed destination keeps the current velocity instead of restarting the ease.","how":"`camera-motion.mjs` integrates each coordinate in closed form, with no substep loop and no backlog after a slow frame. Reduced motion composes the target immediately, pause and hidden views clear any residual velocity, and the room clearance bounds cancel velocity at a constraint.","files":[{"path":"assets/js/home-scene/camera-motion.mjs","role":"Closed-form damped coordinates."},{"path":"assets/js/home-scene/camera.mjs","role":"Clearance bounds that cancel velocity."}],"tests":["test/coastal-camera-motion.test.mjs"],"sources":[],"limits":[],"next":[]},{"id":"semantic-zoom","name":"Semantic zoom and panning","status":"shipped","since":"2026-10-14","what":"Zooming changes what is shown. Pushing past a room's widest view opens the whole house, then the coast, and zooming in walks back into the room under the pointer.","how":"A view changes only after a further push within 0.7 s (the zoom factors' logarithms summing past 0.32), so one hard flick still stops at the edge. Shift-drag, right- or middle-drag, two fingers and Shift+arrows move the viewpoint across the floor, within 1 m of a room's authored target, 6 m of the house's and 26 m outside. In the whole house, hovering names a room and a click or the keys 1 to 6 visit it. ⌘/Ctrl+scroll, which a trackpad pinch sends, zooms without focusing the canvas; a plain scroll still scrolls the page and shows a hint at most three times. The idea is Perlin and Fox's Pad and Bederson and Hollan's Pad++, with the home's own hierarchy (room, house, coast) as the zoom axis.","files":[{"path":"assets/js/home-scene/controller.mjs","role":"Gesture interpretation, view changes and room hover."},{"path":"assets/js/home-scene/camera.mjs","role":"Zoom and pan limits per view."}],"tests":["test/visual/desk-scene.spec.js"],"sources":["perlin1993pad","bederson1994padpp"],"limits":["Every view settles on its authored framing, because keeping the visitor's heading when zooming out framed the house badly.","Room pans stop at 1 m; 1.8 m pushed the camera into walls."],"next":[]},{"id":"animal-inspection","name":"Close views of the coastal neighbours","status":"shipped","since":"2026-10-02","what":"Clicking an animal outside opens a model-sized orbit around it; N cycles the neighbours, Enter restores the coastline and Escape returns inside.","how":"Each of the seventeen animal instances caches conservative inspection bounds and named eye and muzzle anchors once. Arrival visibility scores candidate camera positions with exact local triangle subsets (`occlusion-region.mjs`) and counts the other animals and P from their own scene roots, without altering the rendered terrain or plants. Close inspections enable the room's contact-light pass; the sea gets a contact-only exemption, so its undisplaced override plane cannot invent contacts.","files":[{"path":"assets/js/home-scene/wildlife.mjs","role":"Inspection bounds, anchors and arrival scoring."},{"path":"assets/js/home-scene/occlusion-region.mjs","role":"Exact local triangle subsets for visibility rays."}],"tests":["test/occlusion-region.test.mjs","test/coastal-camera.test.mjs"],"sources":[],"limits":["Visibility is checked on arrival; it is not continuous occlusion avoidance while the animal moves."],"next":[]}]},{"id":"rendering-light","name":"Rendering and light","summary":"Light is layered. A Cycles bake supplies static contact and bounce, a traced probe field relights the sky and lamps, a screen-space pass adds fine contacts, and analytic terms handle the sun, the lamps, the light installations and the light well. A shared finish tone-maps the result.","techniques":[{"id":"baked-vertex-light","name":"Baked contact and bounce light","status":"shipped","since":"2026-09-14","what":"Cycles bakes soft ambient occlusion and indirect diffuse light into vertex colors of the editable house, and the browser adds the time-of-day light on top.","how":"`bin/bake_coastal_light.py` renders a 24-sample neutral Cycles bake of contact occlusion and indirect diffuse light into the source's color attributes, as a small clamped modulation, so no time of day is baked in. The glass, the doors and each bed variant are baked with the others hidden, so none shades the rest. `bin/optimize_coastal_exports.py` then exports byte vertex colors and Draco geometry without overwriting the high-detail source. Both take `-- --room=\u003cid\u003e` to rebake and export one room.","files":[{"path":"bin/bake_coastal_light.py","role":"The Cycles bake into vertex colors."},{"path":"bin/optimize_coastal_exports.py","role":"Compact web export from the retained source."},{"path":"artwork/coastal-home/coastal-home.blend","role":"The editable house."}],"sources":["blender"],"limits":["Baked vertex light plus procedural normals, not a baked normal-map texture set.","Vertex density limits how sharp a baked contact can be; small contacts rely on the screen-space pass."],"next":[]},{"id":"contact-occlusion","name":"Horizon-based contact light","status":"shipped","since":"2026-10-01","what":"A visibility buffer computed before the beauty pass darkens only indirect diffuse light near contacts, instead of multiplying the finished image.","how":"`contact-occlusion.mjs` projects the geometric normal into four fixed view-aligned slices and integrates the cosine-weighted visibility above each slice's two horizons in closed form, Jimenez and colleagues' inner integral, with at most 32 depth probes per pixel within 0.36 m. A 3 × 3 reconstruction keeps neighbors on the same tangent plane and rejects separate depth layers and creases. The paper's cubic fit adds back the light a pale neighborhood would bounce, so linen and stone corners do not turn grey. The result scales only `indirectDiffuse` inside each material; direct sun, emission and reflections are untouched. There is no random rotation or temporal history, so a still composes identically every time.","files":[{"path":"assets/js/home-scene/contact-occlusion.mjs","role":"Horizon integral and edge-aware reconstruction."},{"path":"assets/js/home-scene/realism.mjs","role":"The room finish and its material hooks."},{"path":"assets/js/home-scene/CONTACT-LIGHTING.md","role":"Equations, controlled comparisons and cost."},{"path":"assets/js/home-scene/contact-lab.html","role":"The unlisted comparison page."},{"path":"assets/js/home-scene/gpu-timer.mjs","role":"Optional GPU timing for the lab."}],"tests":["test/coastal-contact-lighting.test.mjs","test/coastal-contact-rendering.test.mjs"],"sources":["jimenez2016gtao","ext-disjoint-timer-query"],"limits":["Screen-space, so it cannot see hidden or offscreen geometry and overestimates thin occluders.","Four fixed slices keep a directional bias, and the 480-pixel buffer limits contacts smaller than its footprint.","No bent normals or specular occlusion; the paper's GTSO is not implemented."],"next":[]},{"id":"static-light-field","name":"Traced sky and lamp bounce","status":"shipped","since":"2026-10-02","what":"A CPU ray tracer bakes static sky visibility and one diffuse bounce into seven probe cages once, then relights them from the actual sky and lamp powers without tracing again.","how":"`static-light-field.mjs` builds a balanced triangle BVH (`light-trace-core.mjs`) over the house's static opaque meshes, traces eight cosine-weighted sky rays per primary hit, and stores irradiance as first-order spherical harmonics in 56 probes with octahedral distance moments, after Ramamoorthi and Hanrahan and the probe visibility of Majercik and colleagues' DDGI. Unit sky and lamp bases let a time-of-day change recombine the cache with zero rays (0.27 ms in the CPU proof). The field replaces Three's diffuse hemisphere and environment irradiance once; direct light, PMREM reflections and refraction stay native. The bake runs in yielded slices (3.67 s with an 18.7 ms longest task in the CPU proof) and releases its roughly 37 MB accelerator afterwards.","files":[{"path":"assets/js/home-scene/static-light-field.mjs","role":"Probe cages, bake and shader binding."},{"path":"assets/js/home-scene/light-trace-core.mjs","role":"Triangle BVH and diffuse quadrature."},{"path":"docs/static-light-field.md","role":"Equations, ownership and cost."},{"path":"docs/warm-house-checkpoint.md","role":"Native scene evidence."}],"tests":["test/static-light-field.test.mjs","test/visual/static-light-field.spec.js"],"sources":["ramamoorthi2001irradiance","majercik2019ddgi","pbrt4-bvh"],"limits":["Static and one bounce. People, props and wind-moved plants do not affect it.","Four coefficients lose directional contrast, and sparse probes can leak at thin walls.","Base colors supply the bounce albedo; texture and procedural detail are not baked again."],"next":[]},{"id":"skin-diffusion","name":"Preintegrated skin diffusion","status":"shipped","since":"2026-10-02","what":"Skin redistributes direct diffuse light across a curved patch with a normalized diffusion profile, so a terminator turns warm instead of hard.","how":"`skin-diffusion-profile.mjs` integrates Christensen and Burley's normalized two-exponential profile over a locally spherical patch with 96-node Gauss–Legendre quadrature and stores the result in a 257 × 49 half-float table (about 100 KB) per finish. `skin-diffusion.mjs` measures mean curvature in meters from view-space derivatives of the skinned position and normal, then replaces only the direct diffuse term of Three's `RE_Direct_Physical` with a 70/30 mix of diffused and ordinary response. The default distances, 3.2, 1.2 and 0.65 mm for red, green and blue, are authored values, not a measured fit to anyone's skin. Specular, sheen, hair, glasses and clothes keep their shaders.","files":[{"path":"assets/js/home-scene/skin-diffusion-profile.mjs","role":"Profile integration and the lookup table."},{"path":"assets/js/home-scene/skin-diffusion.mjs","role":"Curvature and the material hook."},{"path":"assets/js/home-scene/SKIN-DIFFUSION.md","role":"Derivation, integration and limits."}],"tests":["test/skin-diffusion.test.mjs","test/coastal-transport-rendering.test.mjs"],"sources":["christensen2015profiles","jimenez2015separable","chen2014pdss"],"limits":["A local convex-sphere approximation with pointwise shadows. No transport across shadow edges, albedo changes or mesh boundaries, and no ear or backside transmission.","Anisotropic shapes reduce to scalar mean curvature."],"next":[{"move":"A spatial, diffuse-only buffer filtered with separable kernels would carry light across shadow edges, at the cost of extra geometry and lighting buffers for small animated subjects.","readiness":"needs-research"}]},{"id":"daylight","name":"Sun position and sky","status":"shipped","since":"2026-10-01","what":"The sun, its shadows and the procedural sky follow NOAA's solar equations for La Jolla at the scene's Pacific time.","how":"`daylight.mjs` uses NOAA's fractional-year approximation at 32.83° N, 117.27° W, with Pacific daylight saving resolved at local noon. `environment.mjs` models the sky as geometry and captures it into a prefiltered environment map at twenty-minute steps, keeping one generator for the home's lifetime so captures never relink programs. On the coast the sun's shadow map follows the camera, 12 m to each side close up, snapped to whole texels so edges never crawl. Night's key light is an authored moon.","files":[{"path":"assets/js/home-scene/daylight.mjs","role":"Solar position."},{"path":"assets/js/home-scene/environment.mjs","role":"Sky, environment captures, sea and haze."}],"tests":["test/coastal-physics.test.mjs","test/sun-arc-math.test.mjs"],"sources":["noaa-solar"],"limits":["Not a high-precision ephemeris or an atmospheric scattering model; the clear-day haze is authored.","Specular reflections can lag the sun within a twenty-minute step, and there is no lunar astronomy."],"next":[]},{"id":"warm-practicals","name":"Lamps that follow the routine","status":"shipped","since":"2026-10-02","what":"Eight point lights, at four modeled lamps and four fixtures, brighten where Sirui is and dim to a glow elsewhere; the bedside lamp doubles as a dawn lamp.","how":"`warm-practicals.mjs` places eight inverse-square point lights at the modeled fixtures and drives each from the routine. The study lamp is at full while reading, working or coding and at 30 % otherwise; the gym light is at full only for workouts; the bedside lamp ramps up over the half hour before waking and stays a night light while Sirui sleeps. No practical casts a shadow map; the static light field carries their traced bounce.","files":[{"path":"assets/js/home-scene/warm-practicals.mjs","role":"Fixtures and schedules."},{"path":"docs/warm-house-checkpoint.md","role":"Fixture placement and evidence."}],"tests":["test/light-installations.test.mjs"],"sources":[],"limits":["Direct practical light is unshadowed; only its cached bounce sees the walls."],"next":[]},{"id":"light-installations","name":"Light installations that keep the house's hours","status":"shipped","since":"2026-10-14","what":"A terrace festoon, a fire bowl, a gallery soffit, stair step lights and a pool light switch with the time of day and the routine.","how":"`light-installations.mjs` shades every emitter analytically in every material. Line emitters use the exact irradiance of a uniformly glowing segment on a matte surface, clipped to the surface's horizon; the test checks it against brute-force integration on 40 random layouts and against the infinite-line limit 2I/h. Each emitter is bounded by a box, so the festoon lights the terrace and the front of the living floor through the glass but not the cliff under the ledge. Schedules fade in and out and wrap past midnight: the festoon from dusk to 23:30, the fire bowl from 19:30 to 22:45, the step lights at 45 % all night. By day one uniform skips the whole term. The layers follow Richard Kelly's ambient luminescence, focal glow and play of brilliants.","files":[{"path":"assets/js/home-scene/light-installations.mjs","role":"Emitters, closed-form line irradiance and schedules."},{"path":"bin/coastal_facade.py","role":"Writes the terrace's festoon and fire positions into the manifest."}],"tests":["test/light-installations.test.mjs"],"sources":["kelly1952lighting"],"limits":["No installation casts shadows or adds a baked bounce.","A material reads at most ten line and four point emitters."],"next":[{"move":"Caustics from the pool light onto the onsen vault, the hard light paths the light lab was built to study.","readiness":"needs-research"}]},{"id":"glazing","name":"Anti-reflective glass front","status":"shipped","since":"2026-10-14","what":"The cave front and the terrace balustrade use low-iron, anti-reflective glass, about 1 % reflectance head-on, so the cut-away house stays readable by day.","how":"`glazing.mjs` draws each pane over what is behind it with premultiplied blending: its color is only what it reflects, and the view through is dimmed by that share plus a little absorption. A Schlick term still turns the pane into sky toward grazing angles, as real glass does. The panes are not transmissive, because a transmission material would refract a copy of the opaque scene and lose the transparent onsen water and steam behind the front. Glass casts no shadow and does not occlude the contact pass, the volume depth or the light field; its frames do.","files":[{"path":"assets/js/home-scene/glazing.mjs","role":"The anti-reflective glass material."},{"path":"bin/coastal_facade.py","role":"The frame, panes and sliding doors."}],"sources":[],"limits":["Ordinary glass (4 %) is physically right and turned the cut-away view into sky by day; the 1 % coating is a legibility choice."],"next":[]},{"id":"light-well","name":"A light well with traced shaft light","status":"shipped","since":"2026-10-14","what":"A shaft drilled from the clifftop brings direct sun into the onsen, and the beam, its sun patch, the shaft's skylight and the lit steam all share one visibility function.","how":"`bin/coastal_lightwell.py` drills a shaft of 0.42 m radius, 1.67 m through the rock, leaning 28° toward the noon sun, and exports a shadow-only copy of the onsen's vault, so the bath receives direct sun only through the shaft even while the cutaway lifts the roof. `light-well.mjs` follows four light paths. The sun's shadow map carries the moving patch on the floor or water. Dust in the beam is single scattering with a mildly forward Henyey–Greenstein phase, marched only inside the shaft's elliptic cylinder, and the same visibility masks the steam's sunlight. Quasi-Monte Carlo path tracing of the drilled cylinder, on a fixed Halton sequence so it does not flicker, gives the radiance leaving the aperture whenever the sun moves. Lambert's closed form for a uniformly bright polygon then lights the bath from the aperture and from the sun patch as two small area lights. `?onsen-light=open` restores the open sky for comparison.","files":[{"path":"bin/coastal_lightwell.py","role":"The drilled shaft and the shadow-only vault."},{"path":"bin/refresh_coastal_lightwell.py","role":"Rebuilds the shaft in the retained house."},{"path":"assets/js/home-scene/light-well.mjs","role":"Shaft visibility, dust beam, traced aperture radiance and area lights."},{"path":"assets/js/home-scene/steam-volume.mjs","role":"Masks the steam's sunlight with the shaft visibility."}],"tests":["test/light-well.test.mjs"],"sources":["wronski2014fog"],"limits":["Only 5.5 % of the sky passes a shaft four radii long (the tracer matches the analytic view factor to within 0.4 %), so outside midday the grotto is darker than the open cutaway was.","At plausible dust densities the beam is invisible; it shows because the bath air is humid."],"next":[{"move":"Choose between the grotto and an open onsen with the well seen only from the clifftop.","readiness":"needs-decision"}]},{"id":"look-and-finish","name":"Tone curve, bloom and fabric sheen","status":"shipped","since":"2026-10-13","what":"The finish tone-maps with ACES Filmic, blooms only true light sources and gives woven fabric a sheen; URL flags preview alternatives for matched comparisons.","how":"`realism.mjs` owns the room finish: the contact pass, beauty, an optional bloom and the output pass. Bloom is the vendored r164 `UnrealBloomPass`, loaded on demand, glowing HDR values above 1.6 before tone mapping. That is above sunlit walls, so midday frames are unchanged while the evening lamps glow. Linen, textile and jute use Three's Charlie sheen, after Estevez and Kulla. `look.mjs` keeps session-long preview flags (`?tone=aces`, `agx` or `neutral`, `\u0026bloom=0|1`, `\u0026sheen=0|1`, `?look=reset`), and exposure factors keep AgX and Khronos PBR Neutral within 2 to 3 % of ACES's mean brightness, so a comparison is not decided by brightness alone.","files":[{"path":"assets/js/home-scene/look.mjs","role":"Session-long look flags."},{"path":"assets/js/home-scene/realism.mjs","role":"Finish pipeline, bloom and sheen materials."},{"path":"assets/js/home-scene/controller.mjs","role":"Exposure factors per tone curve."}],"tests":["test/scene-look.test.mjs"],"sources":["estevez2017sheen","agx","khronos-pbr-neutral","threejs-r164"],"limits":["The chosen curve is a judgment from one matched sheet. Neutral kept the truest coast colors at noon but lifted evening shadows into grey; AgX greyed the sandstone and whitened lamplight."],"next":[]},{"id":"hair-highlights","name":"Strand highlights on hair","status":"shipped","since":"2026-10-13","what":"Hair gets two shifted highlight bands across the strands instead of a round spot.","how":"`strandHighlights` in `realism.mjs` adds two lobes to every avatar's hair material: a near-white primary shifted toward the tips and a broader, hair-tinted secondary shifted toward the roots. They are the R and TRT lobes of Marschner and colleagues in Scheuermann's real-time form, on Kajiya and Kay's strand model, broken up by slow clumps. The avatars carry no strand tangents, so each strand direction is combed from the crown of the hair mesh's bind pose along the surface and skinned like a normal. The lobes join Three's per-light loop, so shadows and every lamp apply. Widening the lobe by the normal's variance across the pixel, while keeping its energy, removed sparkle at the hairline (geometric specular antialiasing).","files":[{"path":"assets/js/home-scene/realism.mjs","role":"The strand-highlight material hook."}],"sources":["kajiya1989fur","marschner2003hair","scheuermann2004hair","kaplanyan2016normals","tokuyoshi2019gsaa"],"limits":["Direct light only; reflections of the environment on hair stay a plain rough response."],"next":[{"move":"Measure sparkle on other bumpy surfaces before applying the same lobe widening everywhere.","readiness":"needs-measurement"}]},{"id":"procedural-materials","name":"Physical-scale surface detail","status":"shipped","since":"2026-09-13","what":"Wood grain, fabric weave, sandstone bedding, honeycomb weathering and wet intertidal rock are computed in the shader from physical coordinates, with no texture downloads.","how":"`realism.mjs` derives detail from world-space meters, so its scale survives batching and the many UV layouts in the glTF exports. Close-range sandstone gets bedding planes of uneven thickness that show only where weathering picks them out and fade before they are thinner than a pixel, honeycomb weathering (tafoni) from Worley cells in patches on steep faces, runoff streaks, and a soil-and-grass tint on the clifftop. Rock the high tide reaches stays wet, darker and glossier, with a band of algae low and barnacle specks above. Bumps use derivative bump mapping filtered below a pixel.","files":[{"path":"assets/js/home-scene/realism.mjs","role":"Procedural surface detail for every finished material."},{"path":"assets/js/home-scene/materials.mjs","role":"Shared material art direction."}],"sources":["worley1996cellular"],"limits":["Procedural detail is not included in the light field's bounce albedo."],"next":[{"move":"Bark plates for the Torrey pines.","readiness":"ready"}]}]},{"id":"water-atmosphere","name":"Water and atmosphere","summary":"Two bodies of water with different models. The Pacific is a bounded analytic spectrum with a live mirror and a lit shoreline; the onsen is a small simulated height field driven by the bather's hand. Steam rises from the bath as transported scalar fields.","techniques":[{"id":"ocean-spectrum","name":"A bounded Gerstner ocean","status":"shipped","since":"2026-10-01","what":"Ten deterministic wave components displace the sea geometry and its pixel normals together, and twelve short ripples tilt the normals up close.","how":"`ocean-spectrum.mjs` authors ten choppy Gerstner components from 24.7 m to 6.1 cm with gravity–capillary dispersion, ω² = gk + (σ/ρ)k³, and analytic tangents of the actual displacement, after Finch's GPU Gems chapter. Keeping the steepness sum Σqak under 0.3 prevents foldover. Each amplitude fades with a Gaussian weight of the pixel's footprint, and the slope energy it loses widens the GGX lobe, so a distant sea neither flattens nor sparkles: a scalar version of Bruneton, Neyret and Holzschuch's transition from geometry to BRDF. Twelve normal-only ripples, spread over the wind's half-plane by the golden angle from 0.9 m to 2.4 cm, break up the ruled stripes that single plane waves made at walking distance.","files":[{"path":"assets/js/home-scene/ocean-spectrum.mjs","role":"Wave components, footprint filtering and the CPU wave sampler."},{"path":"assets/js/home-scene/environment.mjs","role":"The sea shader."},{"path":"assets/js/home-scene/PHYSICS.md","role":"Equations and evidence."}],"tests":["test/coastal-physics.test.mjs","test/coast-wave-motion.test.mjs"],"sources":["finch2004gerstner","bruneton2010ocean","arcblanc2025"],"limits":["A sparse authored spectrum on a finite 400 m surface, with no FFT, measured spectrum, cascades or weather.","The roughness widening is a scalar isotropic fit, not the paper's anisotropic slope statistics."],"next":[{"move":"A spectral FFT ocean after Tessendorf, kept consistent with the CPU sampler that floats objects and aims arrival rays.","readiness":"needs-research"}]},{"id":"shoreline","name":"Swash, wet sand and lit foam","status":"shipped","since":"2026-10-13","what":"Where the sea meets the beach, a lacy swash band breathes with each wave's run-up, broken fronts travel in and fade, thin water shows wet sand, and foam is lit like a white surface.","how":"In the sea shader, foam comes from compression of the horizontal displacement Jacobian and from crest height, masked by the authored shoreline. A swash band follows a periodic run-up, and a broken front starts about 3 m out and fades as it travels. Within a metre or two of the waterline the water shows wet sand under a Beer–Lambert-shaped depth attenuation. Foam takes the pixel's own diffuse light, so it dims at sunset and turns moonlit blue instead of glowing. Beach width and shoreline samples come from the manifest values Blender also uses (`shore.mjs`).","files":[{"path":"assets/js/home-scene/environment.mjs","role":"Foam, swash and shallow-water color."},{"path":"assets/js/home-scene/shore.mjs","role":"Exported shoreline rows and support polygons."}],"sources":[],"limits":["Art-directed. No bathymetry, foam transport, conservation of foam or breaking-wave solver.","The authored beach profile sits about 1.5 m off the visible waterline on the left side."],"next":[{"move":"Crest glow and sun glitter, which need back light the default camera rarely sees.","readiness":"needs-research"}]},{"id":"sea-reflection","name":"Planar sea reflection","status":"shipped","since":"2026-09-13","what":"A 384-pixel live mirror reflects the actual cliff and house in the sea, with exact water Fresnel on indirect specular only.","how":"`reflection.mjs` renders the scene from a camera mirrored across the water plane with an oblique near plane, after three.js r164's Water, in linear radiance, and filters it with five taps by roughness. Unpolarized dielectric Fresnel with n = 1.333 replaces only the indirect specular term: about 2 % head-on, rising to one at grazing. The mirror skips transmissive meshes (the onsen water and the coffee glass), because each one made Three redraw the whole opaque scene at full canvas size even inside the mirror.","files":[{"path":"assets/js/home-scene/reflection.mjs","role":"The mirror camera, clipping and Fresnel."}],"tests":["test/coastal-physics.test.mjs"],"sources":["threejs-r164"],"limits":["A flat reference plane with slope-based distortion, not a ray-traced curved reflector; five taps approximate the blur."],"next":[]},{"id":"onsen-water","name":"A height-field onsen","status":"shipped","since":"2026-10-02","what":"The onsen is a 40 × 40 shallow-water height field. Waves carry water between cells, reflect off the rim and the bather, and fade.","how":"`heightfield-water.mjs` follows section 2.1 of Chentanez and Müller: staggered heights and face velocities, conservative upwind depth fluxes, pressure from the free-surface gradient and reflecting solid faces, at fixed 1/120 s ticks with CFL subdivision. `pool-geometry.mjs` measures the real basin with five agreeing interior rays (about 0.127 m deep). The bather is a one-way circular obstacle whose excluded volume is redistributed, so total volume holds to about 10⁻¹⁴. `pool-stroke.mjs` drives a slow anatomical hand skim, and the palm's submerged horizontal velocity relaxes nearby face velocities inside each CFL step, so resting hands add no energy. `onsen-water.mjs` packs elevation, slopes and wet weight into a 40² texture for the existing material. The solver runs only when the camera can see the pool.","files":[{"path":"assets/js/home-scene/heightfield-water.mjs","role":"The conservative shallow-water solver."},{"path":"assets/js/home-scene/onsen-water.mjs","role":"Renderer adapter, palm coupling and evidence."},{"path":"assets/js/home-scene/pool-geometry.mjs","role":"Measures the native basin."},{"path":"assets/js/home-scene/pool-stroke.mjs","role":"The prescribed hand skim."},{"path":"assets/js/home-scene/ONSEN-WATER.md","role":"Equations, coupling limits and numerical evidence."}],"tests":["test/onsen-water.test.mjs","test/pool-geometry.test.mjs","test/pool-stroke.test.mjs"],"sources":["chentanez2010water"],"limits":["First-order semi-Lagrangian velocity advection, and no wet and dry terrain, open boundaries, buoyancy, splashes or caustics.","One-way coupling. The water never pushes the hand or the bather.","Shallow-water dispersion is inaccurate for wavelengths near the depth."],"next":[{"move":"A steady drip from the bamboo spout into the solver at the spout's mouth.","readiness":"ready"}]},{"id":"thermal-steam","name":"Thermal steam","status":"shipped","since":"2026-10-02","what":"Bath mist is two transported scalar fields, droplet concentration and temperature, rendered with emission-free single scattering.","how":"`steam-density.mjs` advects an 18³ concentration and temperature field at a fixed 30 Hz with midpoint semi-Lagrangian backtracing, after Stam, and lets warmer cells rise faster, after Fedkiw, Stam and Jensen's separate smoke density and temperature. It diffuses and cools them with exact exponential factors and relaxes the warm surface sources. Wind inside the bath is the shared curl field at 0.16 strength. `steam-volume.mjs` marches up to 40 samples with Beer–Lambert extinction and a normalized Henyey–Greenstein phase, after Wronski, and splits each ray once at the nearest glass or water interface so the gas behind and in front of it composes in order.","files":[{"path":"assets/js/home-scene/steam-density.mjs","role":"Concentration and temperature transport."},{"path":"assets/js/home-scene/steam-volume.mjs","role":"Single-scattering ray march and the interface split."},{"path":"assets/js/home-scene/volume-depth.mjs","role":"Opaque and nearest-transmissive depth for the split."},{"path":"assets/js/home-scene/THERMAL-STEAM.md","role":"Model, light integration and cost."}],"tests":["test/steam-transport.test.mjs"],"sources":["stam1999stable","fedkiw2001smoke","wronski2014fog"],"limits":["Prescribed buoyant rise, with no pressure solve, phase change or mass-conserving gas solver.","Single scattering and short-range attenuation only; beyond the light-well mask there is no opaque sun-shadow query."],"next":[]}]},{"id":"wind-plants","name":"Wind and plants","summary":"Inside, one divergence-free wind field moves the leaves, the dust, the spray and the bath steam. The clifftop planting still sways on its own simpler rule.","techniques":[{"id":"curl-wind","name":"A divergence-free wind field","status":"shipped","since":"2026-10-02","what":"A six-mode curl-noise field gives the room plants, dust, spray and bath steam one coherent wind.","how":"`wind-field.mjs` takes the analytic curl of a bounded vector potential, a small Fourier basis in place of Bridson, Hourihan and Nordenstam's Perlin potentials, so the velocity is exactly divergence-free and its speed is bounded by 0.647 m/s with the defaults (a 0.45 m/s gust over a small mean drift). Wavelengths run from 1.2 to 7 m. The same field runs in GLSL (`coastalWind`) and on the CPU, which agree within the 8-bit quantization step in native fixtures. `coastal-wind.mjs` owns one field and one active clock for the room.","files":[{"path":"assets/js/home-scene/wind-field.mjs","role":"The curl-potential field, CPU and GLSL."},{"path":"assets/js/home-scene/coastal-wind.mjs","role":"One field and clock for the room's plants and particles."},{"path":"docs/coherent-wind-equations.md","role":"Field, bound, attachment and particle equations."}],"tests":["test/coastal-wind.test.mjs"],"sources":["bridson2007curl"],"limits":["Procedural, not a pressure-projected flow. No obstacles, building collision or turbulence spectrum, and a spatial mask would break the divergence guarantee."],"next":[]},{"id":"rooted-leaves","name":"Leaves attached to real stems","status":"shipped","since":"2026-10-02","what":"Room-plant leaves bend in the wind around their actual attachment points, with corrected normals and matching shadows.","how":"`plant-motion.mjs` finds each leaf component's attachment once, from nearby support vertices of the real stems, and stores a root, a weight and its gradient per vertex. Each component follows the steady periodic response of a critically damped oscillator to the wind, at most 11.7 mm of horizontal deflection, and normals use the inverse transpose of the bend's Jacobian. The identical hook runs on beauty, depth and distance materials, so shadows bend with the leaves. 110 of 132 olive-leaf components bend; hanging vines and agave stay rigid.","files":[{"path":"assets/js/home-scene/plant-motion.mjs","role":"Attachments, damped response and the shared shader hook."},{"path":"assets/js/home-scene/coastal-wind.mjs","role":"Binds plants to the field."}],"tests":["test/coastal-wind.test.mjs","test/coastal-transport-rendering.test.mjs"],"sources":["bridson2007curl"],"limits":["An authored damped response, not calibrated leaf elasticity.","The indirect contact buffer sees the rest pose, a documented mismatch below 12 mm."],"next":[]},{"id":"clifftop-sway","name":"Clifftop planting in the breeze","status":"shipped","since":"2026-10-14","what":"Sage, pine needles, grass, lemonade berry and palm fronds sway by a weight stored in their UVs, leaning inland in the onshore breeze.","how":"`bin/coastal_flora.py` writes a sway weight into each leaf, needle and blade's UV x, 0 at the plant's base and 1 at its top. `swayInBreeze` in `realism.mjs` displaces vertices by that weight squared: a small inland lean plus two sines with a per-plant phase taken from world position. The motion freezes with the scene clock. Because it moves, the planting stays out of the static light field.","files":[{"path":"bin/coastal_flora.py","role":"Writes the sway weights."},{"path":"assets/js/home-scene/realism.mjs","role":"The sway shader hook."}],"sources":[],"limits":["A separate sine sway that does not sample the curl wind field, so the clifftop and the room plants move in two different winds.","Only the beauty material sways; shadow and depth passes see the rest shape."],"next":[{"move":"Drive the clifftop sway from the same wind field as the room plants, particles and steam, in the beauty and depth materials.","readiness":"ready"}]},{"id":"wind-particles","name":"Dust and spray in the same wind","status":"shipped","since":"2026-10-02","what":"42 dust motes and 140 surf droplets advect through the shared wind field on fixed ticks; spray also falls under gravity and drag.","how":"`wind-particles.mjs` integrates dust with midpoint RK2 in both position and time, and spray ballistically with gravity, air drag, seeded launches and lifetimes, at fixed 1/60 s ticks, so different frame rates give the same history. A deterministic prewarm from negative time gives a mature still composition, including under reduced motion. The original 18 steam points were replaced by the thermal field.","files":[{"path":"assets/js/home-scene/wind-particles.mjs","role":"Bounded particle integration."},{"path":"assets/js/home-scene/environment.mjs","role":"Emitters, bounds and drawing."}],"tests":["test/coastal-wind.test.mjs"],"sources":["bridson2007curl"],"limits":["Bounds respawn particles; there is no collision with the house."],"next":[]}]},{"id":"characters","name":"Characters and animation","summary":"Five cartoon versions of me live one at a time on an authored San Diego routine. Their walks, activities, eyes and bed are fitted to the house's actual geometry.","techniques":[{"id":"avatars","name":"Five original avatars","status":"shipped","since":"2026-09-12","what":"Lizard, South Park, Simpsons, Ghibli and Rick and Morty interpretations share one bone convention and fourteen authored clips; one appears at a time, new on each refresh.","how":"`bin/coastal_characters.py` builds the four human models with shaped jaws, inset eyes and side-parted hair. `coastal_hair.py` and `coastal_clothing.py` make continuous hair and garment surfaces. `coastal_anatomy.py` lengthens long bones in rest space to adult proportions while keeping hands, feet, bone names and activity endpoints. `coastal_hands.py` adds Ghibli's twenty finger joints and anatomical wrist targets for the pull-up and dip grips, and `coastal_expression.py` adds paired eyelid surfaces with `BlinkL` and `BlinkR` morphs. Likeness is judged in front, profile and full-body Cycles renders, then at homepage size during an activity.","files":[{"path":"bin/coastal_characters.py","role":"The four human models."},{"path":"bin/coastal_anatomy.py","role":"Adult rest proportions."},{"path":"bin/coastal_hair.py","role":"Continuous swept-back hair."},{"path":"bin/coastal_clothing.py","role":"Continuous garments."},{"path":"bin/coastal_hands.py","role":"Finger joints and grip wrist targets."},{"path":"bin/coastal_expression.py","role":"Eyelids and blink morphs."},{"path":"artwork/coastal-home/portraits/","role":"Front, profile and full-body review renders."}],"tests":["test/coastal-routine.test.mjs","test/test_coastal_assets.py"],"sources":["blender"],"limits":["Authored stylization, not a likeness scan; my own judgment is the acceptance test.","No physical hair or cloth simulation on the bodies."],"next":[{"move":"Film-studio character surfacing and close-up detail, which the brief names as the higher visual target.","readiness":"needs-research"}]},{"id":"routine","name":"An authored San Diego routine","status":"shipped","since":"2026-09-12","what":"A pure clock maps Pacific time to an activity and a room. Breakfast comes late, coding runs into the night, and the onsen faces the Pacific.","how":"`routine.mjs` resolves `America/Los_Angeles` with daylight saving and reads weekday and weekend schedules from the manifest; weekends extend coding to 04:00 and sleep to 12:30, and the evening drink rotates by date. The theme panel's sun arc can preview an hour through `setTimePreview(minute)`, and Now clears both previews. A theme chosen anywhere else never sets a preview. It is a little imagined day, not a location feed.","files":[{"path":"assets/js/home-scene/routine.mjs","role":"The pure clock and exploration state."},{"path":"assets/models/home/manifest.json","role":"Schedules, activities and room anchors."},{"path":"assets/js/sun-arc.mjs","role":"Hour previews from the theme panel."}],"tests":["test/coastal-routine.test.mjs"],"sources":[],"limits":["Authored fiction, with no telemetry by design."],"next":[{"move":"A sunset hour on the new terrace.","readiness":"needs-decision"}]},{"id":"activities","name":"Activities fitted to equipment","status":"shipped","since":"2026-09-14","what":"Coffee, strength sets, typing, meals and sleep use exported equipment contacts, so cups and weights travel between surfaces and hands.","how":"`activities.mjs` runs short deterministic phases against equipment anchors from `bin/coastal_furnishing.py`: grinding, brewing, pickup, carrying, drinking and returning the same cup; pull-ups, dips, a rack-to-hand dumbbell transfer and rests. A runtime arm correction follows the mixed pose to remove residual drift, and `grip-targets.mjs` lets an articulated avatar name its own anatomical wrist target per clip. `bin/review_coastal_models.py` measures deformed source vertices and wrists at five key times per clip.","files":[{"path":"assets/js/home-scene/activities.mjs","role":"Activity phases and contact correction."},{"path":"assets/js/home-scene/grip-targets.mjs","role":"Per-avatar wrist targets."},{"path":"bin/coastal_furnishing.py","role":"Kitchen and gym equipment and their contacts."},{"path":"bin/review_coastal_models.py","role":"Contact measurements on the deformed sources."}],"tests":["test/coastal-camera.test.mjs","test/coastal-grip-targets.test.mjs"],"sources":[],"limits":["Authored poses with a small contact correction, not whole-body dynamics."],"next":[]},{"id":"locomotion","name":"Walking on the real floor and treads","status":"shipped","since":"2026-09-13","what":"Walks follow authored corridors and every stair tread, and a short two-bone solve keeps each foot on its support.","how":"`navigation.mjs` routes between any two rooms along straight segments that stay inside the corridor, through the 15-riser stair and its landing; a spline could cut through furniture or overshoot the first riser. `locomotion.mjs` constrains the mixer's gait to the exported floor polygons and treads, so a climbing foot never passes through a riser or hovers above it.","files":[{"path":"assets/js/home-scene/navigation.mjs","role":"Authored circulation over floors and treads."},{"path":"assets/js/home-scene/locomotion.mjs","role":"Two-bone foot fitting."},{"path":"bin/coastal_section.py","role":"Exports the floors, treads and corridors."}],"tests":["test/coastal-navigation.test.mjs"],"sources":[],"limits":["Authored paths, not a general collision or crowd solver."],"next":[]},{"id":"character-performance","name":"Eyes, blinks and attention","status":"shipped","since":"2026-10-02","what":"An additive layer adds asymmetric blinks, quiet breathing and a short eyes-first glance at the visitor's pointer, then returns to the routine.","how":"`character-performance.mjs` turns the pointer into a ray through the real camera, aims at a point halfway between the eyes and the visitor in the head's own optical frame, and lets the eyes lead the head with a short hold and habituation, after the eye and head bandwidths in Pan and colleagues' robot gaze work. Contact-sensitive clips (strength, walking, coffee, carrying) cancel head motion; eating and drinking allow the eyes only. Sleep closes the lids, and pause, reduced motion and hidden views compose neutral lids. Breathing moves the head joint at most 0.65 mm, and no spine, wrist, finger, foot or root transform changes.","files":[{"path":"assets/js/home-scene/character-performance.mjs","role":"Blinks, breathing and attention."},{"path":"artwork/coastal-home/CHARACTER_PERFORMANCE.md","role":"Eyelid authoring and retention checks."},{"path":"docs/evidence/character-performance-2026-10-02/README.md","role":"Acceptance evidence."}],"tests":["test/character-performance.test.mjs"],"sources":["pan2020gaze"],"limits":["Pointer proximity stands in for a visitor; there is no camera perception or face detection.","There is no continuous eyelid and glasses collision proof."],"next":[]},{"id":"fitted-bedding","name":"Bedding fitted to each sleeper","status":"shipped","since":"2026-10-14","what":"Six bed variants, a made bed and one fitted pillow and duvet per avatar, replace a rigid pillow that heads sank into and a cover that cut through chests.","how":"`bin/refresh_coastal_bedding.py` dents each down pillow under its avatar's head with vertical ray casts against the sleep clip at its deepest and shallowest breath. `coastal_bedding.py` drops each duvet over its sleeper with an original position-based cloth solver, colored Gauss–Seidel distance constraints over the upper envelope of sleeper, pillow, mattress, frame and bedside table, then trims it across the chest, quilts it and gives it 3 cm of fill. The page shows the made bed until the sleep clip plays, then the active avatar's pair.","files":[{"path":"bin/coastal_bedding.py","role":"The position-based drape."},{"path":"bin/refresh_coastal_bedding.py","role":"Builds the six variants in the retained house."},{"path":"bin/review_coastal_models.py","role":"Asserts the body, cover and pillow measurements."}],"sources":["muller2007pbd","fratarcangeli2016vivace"],"limits":["Authored drapes, not runtime cloth. The duvets do not move with breathing; the 3 cm fill covers that.","The sleep room grew from 57 KB to 260 KB."],"next":[]}]},{"id":"wildlife","name":"Wildlife","summary":"Brush rabbits, California sea lions, harbor seals, a raccoon, western gulls and sandpipers share the coast. Their acting is authored and sampled from time, and their feet meet the same terrain the house stands on.","techniques":[{"id":"animal-masters","name":"Articulated animal masters","status":"shipped","since":"2026-09-14","what":"Every animal is an original Blender model with a continuous body, named acting pivots and vertex-color coat marks.","how":"`bin/build_coastal_wildlife.py` builds the rabbit, sea lion and seal masters, and `bin/build_coastal_neighbors.py` builds the raccoon, gull and sandpiper with canonical head, eye, muzzle, limb and wing pivots. Marine flipper roots overlap the torso through sampled acting extremes. Habitat choices follow the City of San Diego's Children's Pool information: sea lions on rock haul-outs, harbor seals on sheltered sand.","files":[{"path":"bin/build_coastal_wildlife.py","role":"Rabbit, sea lion and seal masters."},{"path":"bin/build_coastal_neighbors.py","role":"Raccoon, gull and sandpiper masters."},{"path":"artwork/coastal-home/NEIGHBORS.md","role":"Neighbour assets, pivots and rebuild commands."},{"path":"assets/js/home-scene/wildlife.mjs","role":"Instances, habitats and inspection."}],"tests":["test/wildlife-assets.test.mjs"],"sources":["sandiego-childrens-pool"],"limits":["Authored acting, not measured biomechanics or a trained animal model."],"next":[]},{"id":"animal-acting","name":"Acting sampled from active time","status":"shipped","since":"2026-10-02","what":"Rabbits hop with held contacts, the raccoon plants its paws, sandpipers probe the wet edge, and gulls glide between perches.","how":"`wildlife-motion.mjs` holds rabbit travel at takeoff and landing and advances only while airborne, with quintic travel so velocity and acceleration never jump. `wildlife-neighbor-motion.mjs` keeps the raccoon's stance paws on world anchors while two-joint legs follow, pivots sandpipers gradually at each zero-speed reversal, and joins gull flight and perch with C2-continuous transfers and headings from the flight derivative. Nothing integrates frame by frame, so a pause or a slow renderer cannot accumulate drift. The rabbit loops follow clear scrub edges sampled against the finished ground (`rabbit_habitats.py`).","files":[{"path":"assets/js/home-scene/wildlife-motion.mjs","role":"Rabbit and marine acting."},{"path":"assets/js/home-scene/wildlife-neighbor-motion.mjs","role":"Raccoon, sandpiper and gull acting."},{"path":"bin/rabbit_habitats.py","role":"Clifftop rabbit loops."}],"tests":["test/wildlife-motion.test.mjs","test/wildlife-neighbors.test.mjs"],"sources":[],"limits":["Deterministic choreography, with no aerodynamics, general collision solver or feather simulation."],"next":[]},{"id":"perch-contact","name":"Gull feet fitted to real perches","status":"shipped","since":"2026-10-02","what":"Gull soles fit bounded patches of the retained coast and the gallery rail, with fixed tarsus lengths and hidden hip accommodation.","how":"`bin/build_coastal_perch_support.py` exports 51 local support triangles in four patches, matching the GLB's tessellation, and 192 projected sole samples per foot. `wildlife-perch-contact.mjs` fits each webbed foot to that surface through flare and toe-off with a one-sided clearance. Across 337 sampled poses the worst penetration was 1.91 micrometres. Plants are excluded from perch support, because gulls had sampled grass blades as footing.","files":[{"path":"bin/build_coastal_perch_support.py","role":"Exports the perch patches and sole samples."},{"path":"assets/js/home-scene/wildlife-perch-contact.mjs","role":"Runtime foot fitting."},{"path":"assets/models/home/wildlife-perch-support.json","role":"The exported contact data."}],"tests":["test/wildlife-perch-contact.test.mjs"],"sources":[],"limits":["Sampled geometric checks, with no guarantee between samples or over future terrain."],"next":[]}]},{"id":"authoring","name":"Terrain, architecture and flora authoring","summary":"Everything on the page is exported from editable Blender sources by Python scripts run in Blender 4.5 LTS's background mode, then baked, optimized and reviewed. Focused refresh scripts change one aspect of the retained house without rebuilding it.","techniques":[{"id":"blender-pipeline","name":"Rebuildable Blender sources","status":"shipped","since":"2026-09-12","what":"One build script writes the house, the characters, their GLBs and review portraits; bake, export and review scripts follow every geometry change.","how":"`bin/build_coastal_home.py` writes `coastal-home.blend`, the character sources, the GLBs and review portraits in one run, and flags such as `--house-only`, `--avatar=ghibli`, `--section-only`, `--coast-only` and `--export-only` rebuild part of it. After geometry changes, `bake_coastal_light.py` and then `optimize_coastal_exports.py` rebake and export. `render_coastal_review.py` renders neutral-clay and material views from the manifest cameras, and `review_coastal_models.py` records contacts, hashes and triangle counts under `artwork/coastal-home/reviews/`. Focused `refresh_coastal_*.py` scripts change finishes, decoration, the facade, the planting or the light well in the retained house.","files":[{"path":"bin/build_coastal_home.py","role":"The full authoring entry point."},{"path":"bin/coastal_sculpt.py","role":"Continuous sculpt surfaces."},{"path":"bin/coastal_craft.py","role":"Floorboards, joinery, shelves and small objects."},{"path":"bin/render_coastal_review.py","role":"Matched-camera review renders."},{"path":"artwork/coastal-home/reviews/","role":"Review renders and measurement reports."},{"path":"artwork/coastal-home/PROVENANCE.md","role":"Authorship, references and rebuild commands."}],"sources":["blender"],"limits":["Blender ran through its Python API; no hand sculpting in the interface is claimed.","Generated concept boards guided direction and are not finished models."],"next":[]},{"id":"section-and-terrain","name":"One section for house, cliff and beach","status":"shipped","since":"2026-09-13","what":"Two storeys at 0 and 2.6 m are carved into one continuous mainland cliff that meets a sloping beach about seven meters below the lower floor.","how":"`bin/coastal_section.py` carves the cave from closed solids with exact Boolean operations, welds and caps the mainland and the removable roof, and rejects nonmanifold edges; only the local ceiling lifts for cutaways. `coastal_landscape.py` composes headlands, coves and ledges and exports habitat paths, shoreline samples, perches and tidal basins into the version-5 manifest, so the browser's animals and surf stand on the same coast. Planting, paths and perches are ray-cast against the carved mesh. The sandstone layering and fallen rock follow UC San Diego's beach-walk notes and Scripps' cliff-failure research as reference, not as a survey.","files":[{"path":"bin/coastal_section.py","role":"The carved two-storey section."},{"path":"bin/coastal_landscape.py","role":"Headlands, beach and exported contact data."},{"path":"assets/models/home/manifest.json","role":"Shared floors, treads, habitats and shoreline."}],"sources":["sio15-beachwalk","scripps-cliffs"],"limits":["An imagined home, not a surveyed reconstruction of a real place or building."],"next":[]},{"id":"facade-terrace","name":"Glass front, terrace and alcove screens","status":"shipped","since":"2026-10-14","what":"A bronze-framed glass front sits inside the cave lip, a corbelled sandstone terrace is carved below it, and cedar screens turn the bedroom and onsen into alcoves.","how":"`bin/coastal_facade.py` sets the frame 0.4 m inside the rock lip, where a ray survey found the jamb continuous (a cut-stone pier fills one eroded notch), and puts the two lift-and-slide doors where the kitchen and lounge cameras look in; their view frusta cross the glass line at x −3.3 to −0.9 m and 2.1 to 3.7 m. By day the browser slides the doors into the rock. The terrace is 3.1 m deep against the sheltered south rock and narrows to a walk at the north, with a bench and fire bowl, loungers, a bistro table, an olive, rosemary and a festoon. The plan runs from prospect to refuge, after Christopher Alexander's Intimacy Gradient, Six-Foot Balcony and Half-Open Wall patterns.","files":[{"path":"bin/coastal_facade.py","role":"Frame, doors, terrace and screens."},{"path":"bin/refresh_coastal_facade.py","role":"Rebuilds them in the retained house."}],"sources":["alexander1977pattern"],"limits":["The shell grew by about 140 KB."],"next":[]},{"id":"decor","name":"River stones and things on the walls","status":"shipped","since":"2026-10-14","what":"24 rounded river stones replace the onsen's faceted slabs, and each room gains objects chosen for its use.","how":"`bin/coastal_decor.py` adds a bamboo spout, a bucket and stool and a cedar slat panel to the onsen, a framed seascape over the bed, a pinboard of sketches above the desk, and a coiled-rope sun and three thrown pots in the lounge. Each piece sits on a wall the room's authored camera actually sees, found by casting rays through that camera's image, and the pieces join into 14 meshes, one per room and material. A pinboard of sketches says researcher before any label does, which is Henry Jenkins's environmental storytelling.","files":[{"path":"bin/coastal_decor.py","role":"Stones and decoration."},{"path":"bin/refresh_coastal_decor.py","role":"Replaces them in the retained house."}],"sources":["jenkins2004narrative"],"limits":["Re-exporting the onsen re-quantized its stone and moved the solver's measured depth by 0.02 mm, which a five-decimal test caught."],"next":[]},{"id":"clifftop-flora","name":"A clifftop planted from photographs of the bluffs","status":"shipped","since":"2026-10-14","what":"Sage scrub, sagebrush, wild oats, lemonade berry, prickly pear, yucca, iceplant, daisies, Torrey pines and one Canary Island date palm, each placed by its distance from the cliff edge.","how":"`bin/coastal_flora.py` builds code-authored plants (leaf sprays on woody stems, needle tufts on leaning trunks, grass tufts, succulent mats) and keeps them off the doorway, the stepping stones, the rabbit paths, the perches and the 28 agaves. The second planting draws from its own random stream, so every plant from October 14 stays where it was. Species and October colors follow photographs of Torrey Pines, Goldfish Point and the Coast Walk, credited in the provenance file, and Cabrillo National Monument's bluff plant communities: succulents at the rim, grey scrub behind them, lemonade berry in sheltered pockets.","files":[{"path":"bin/coastal_flora.py","role":"Plant builders and placement."},{"path":"bin/refresh_coastal_flora.py","role":"Replants the retained clifftop."},{"path":"artwork/coastal-home/PROVENANCE.md","role":"The photographs used as reference."}],"sources":["nps-cabrillo-plants"],"limits":["The inland hill is still mostly bare sand.","Mats stop where the ground falls 1.2 m below their centre; plants are not fitted to arbitrary slopes."],"next":[{"move":"Sparse sagebrush and oats on the inland hill, kept clear of the stepping stones.","readiness":"ready"}]}]},{"id":"light-lab","name":"The light-paths lab","summary":"A separate site experiment at /projects/light-paths/ that renders the onsen's sunlit pool twice in the browser, to show when Metropolis light transport beats independent path sampling on light the house actually has.","techniques":[{"id":"lab-renderer","name":"One path sampler, two estimators","status":"experimental","since":"2026-10-14","what":"A dependency-free JavaScript renderer draws a simplified onsen with path tracing and with primary-sample-space Metropolis light transport from the same path sampler.","how":"`assets/js/light-lab/core.mjs` traces a box grotto, a round pool with seven frozen analytic ripples and a radial drip ripple, a disc light well and a 0.8° sun. Every random decision reads a fixed coordinate of a sampler. Path tracing feeds it fresh numbers; Kelemen and colleagues' Metropolis feeds it mutated ones, evaluated lazily with PBRT's bookkeeping. Both use next-event estimation to the sun and sky with power-heuristic multiple importance sampling, after Veach. Water hits use safeguarded Newton iteration and a certified march that cannot skip a crossing, and hot loops write into typed arrays so tracing allocates nothing.","files":[{"path":"assets/js/light-lab/core.mjs","role":"Scene, path sampler and both estimators."},{"path":"assets/js/light-lab/worker.mjs","role":"Runs one estimator per worker."},{"path":"_projects/light-paths.md","role":"The public lab page."}],"tests":["test/light-lab.test.mjs"],"sources":["veach1997mlt","kelemen2002pssmlt","veach1997thesis","pbrt3-mlt"],"limits":["A simplified scene. Frozen ripples, no steam, a round opening instead of the tilted shaft, and a sun three times the real size by default.","Metropolis's total brightness rests on independent samples that usually miss the sun, and chains start in skylight and need about 20,000 mutations each to settle."],"next":[{"move":"Manifold exploration or specular manifold sampling, to win the race on the caustic paths outright.","readiness":"needs-research"}]},{"id":"lab-race","name":"A race with the same minute each","status":"experimental","since":"2026-10-14","what":"The lab page runs the two renderers in module workers for the same wall time and charts their error against a converged reference.","how":"`page.mjs` starts one worker per renderer only on request, pauses after a minute, off screen and in hidden tabs, and shows still frames before a run and where a browser cannot run one. Error is the luminance RMSE on an 80 × 60 grid divided by the reference mean. After a minute in the recorded browser run, path tracing was off by 984 % and Metropolis by 180 %; across three seeds Metropolis ended 4 to 15 times closer.","files":[{"path":"assets/js/light-lab/page.mjs","role":"Workers, controls, chart and still frames."},{"path":"_sass/_light-lab.scss","role":"Lab layout."},{"path":"bin/capture_light_lab_frames.mjs","role":"Renders the still frames and the teaser."}],"sources":[],"limits":["Wall time per worker depends on the visitor's machine; the dashed lines are one laptop's run."],"next":[]},{"id":"lab-reference","name":"A reference split by path type","status":"experimental","since":"2026-10-14","what":"The 24-minute reference on 18 threads splits path space at the camera ray's first hit and gives each part the estimator that suits it.","how":"`bin/make_light_lab_reference.mjs` uses light tracing with camera connections where the camera first sees a dry surface (748 million particles), which converges the reflected caustics, and Metropolis restricted to the water with a path-traced brightness correction where it sees the pool (1.63 billion steps). It writes the 80 × 60 luminance grid and the 240 × 180 reference image.","files":[{"path":"bin/make_light_lab_reference.mjs","role":"The split reference renderer."},{"path":"assets/js/light-lab/onsen-reference.json","role":"The luminance grid behind the error measure."}],"sources":[],"limits":["Brightness in the pool is uncertain by a few percent; the walls are much tighter."],"next":[]}]}],"history":[{"date":"2026-06-17","title":"A paper desk learns a 3D view","system":"shared-desk","frame":"The paper collage introduced the papers, portrait and records quickly, but visitors could not wander through that little world.","dimensions":["view (2D or 3D)","state shared between views","camera framing"],"move":"Added the visible 2D | 3D switch and a first Three.js room that copied the desk into a small stage.","backtalk":"At ordinary screen sizes the furniture disappeared, dropped cards floated, rear turns exposed blank space, and the outside looked like a separate dollhouse.","reframe":"Make inside and outside two camera views of the same room, and share the record player and discoveries with the 2D desk.","commit":"c8ed8e5da"},{"date":"2026-07-13","title":"One room, two cameras","system":"camera-navigation","frame":"The exterior was a hand-matched miniature that drifted from the room it was meant to show.","dimensions":["one scene graph or two","camera position","raycast targets"],"move":"Replaced the miniature with the actual room graph under reciprocal cameras. The July 14 checkpoint (`1b07cea4c`) accepted the organic shell, the high cliff aperture and live-geometry raycasts across the viewport matrix.","backtalk":"The same stopped record and empty discovery pile now carried into the room seen from inside or outside. Material realism remained open.","reframe":"Keep the room shape, then test stone, lighting, materials and controls on more phones and high-density laptops.","commit":"a765c5291"},{"date":"2026-09-12","title":"An inhabited home from Blender sources","system":"authoring","frame":"The September brief asked for an inhabited coastal home with connected rooms and a daily rhythm, built as real geometry.","dimensions":["authoring tool","rooms","avatars and clips","load order"],"move":"Split the 3D runtime out of `home.js` into schedule, controller, material and environment modules, and authored six rooms, coastal headlands and five skinned Sirui variants in Blender through `bin/build_coastal_home.py`.","backtalk":"I rejected the first face. It read as a doll with paired hair locks, not my swept-back long hair, exposed forehead, glasses and clean-shaven face.","reframe":"Judge likeness in front, profile and full-body renders before lighting, and connect the house to the cliff.","commit":"72ce1637d"},{"date":"2026-09-13","title":"Two floors carved into one cliff","system":"authoring","frame":"A single-level pedestal and arch read as a freestanding stage, not a home carved into a cliff.","dimensions":["floors and stair","continuity of the cliff, beach and shoreline","what the cutaway lifts"],"move":"`bin/coastal_section.py` carved two storeys at 0 and 2.6 m into one mainland mesh with a 15-riser stair, and `navigation.mjs` and `locomotion.mjs` began following the exported corridors and treads.","backtalk":"Hiding the roof could erase the mountain behind the rooms, so only the local ceiling lifts. Walking contacts moved from an approximate stair equation to the exported tread surfaces.","reframe":"Judge the section before the lighting. Stairs must be walkable, the mainland continuous and the beach at its foot.","commit":"5cd5432fc"},{"date":"2026-09-13","title":"Neighbours on the coast","system":"wildlife","frame":"The home sat in an empty landscape.","dimensions":["beach depth","animal species and counts","atmosphere"],"move":"Widened the beach from 6.6 to 14 m, with Blender terrain, surf and animal contacts sharing one set of shoreline parameters, and added rabbits, a raccoon, gulls and sandpipers in browser geometry with spray, dust and vapor particles.","backtalk":"The coast read as inhabited, but the animals were simple inline shapes without articulated acting or planted contacts.","reframe":"Give the animals articulated bodies and contacts on the same terrain the house stands on.","commit":"a40e8b5e9"},{"date":"2026-09-14","title":"Closed solids and fitted activities","system":"characters","frame":"The roof split left open boundaries, the camera could look behind unfinished geometry, and activities did not touch their equipment.","dimensions":["solid topology","camera envelope","equipment contacts"],"move":"Rebuilt the cave from exact Boolean solids, exported camera envelopes from the finished terrain, furnished the kitchen and gym, and drove coffee and strength sequences from exported equipment contacts.","backtalk":"Review renders caught a missing trouser weighting branch, sideways sleeve cuffs, wrongly oriented typing hands, an obsolete stair equation and groundcover crossing the terrain. Each was fixed in the authoring source and the runtime together.","reframe":"Keep the full exterior orbit instead of hiding rear geometry with a front-only camera.","commit":"8ea3bb504"},{"date":"2026-09-14","title":"Walks that keep time when rendering slows","system":"shared-desk","frame":"On slow software renderers, walks and gestures stretched with the frame interval.","dimensions":["active time against render cadence","catch-up after a stall"],"move":"Route and clip timing now follow active elapsed time, independent of rendering cadence, with spring integration bounded separately.","backtalk":"A Linux probe with 4 to 5 seconds between frames kept the authored walking duration and the stair and seated contacts.","reframe":"Every new motion system samples the same active clock and never queues catch-up work.","commit":"1ac0e4c7d"},{"date":"2026-10-01","title":"A bounded ocean and a driven record","system":"water-atmosphere","frame":"The sea had analytic vertical waves and the record a prescribed rotation; neither held up close.","dimensions":["wave components and steepness","footprint filtering","rotor and tonearm dynamics"],"move":"Added choppy Gerstner displacement with analytic tangents, footprint filtering that moves unresolved slope into roughness, exact water Fresnel on indirect specular, and a driven-rotor record with critically damped tonearm coordinates shared by the 2D and 3D players.","backtalk":"The ocean grew from 28,800 to 51,200 triangles with no new draw call or pass, and frame intervals stayed at a 16.7 ms median on the desktop test machine.","reframe":"Contact light was next, because the old SSAO pass dimmed direct sun as well as ambient light.","commit":"7c241e110","sources":["finch2004gerstner","bruneton2010ocean"]},{"date":"2026-10-01","title":"Contact light on the right term","system":"rendering-light","frame":"The full-image SSAO pass multiplied the finished image, dimming sunlight, highlights and emission, and its blur left grey halos.","dimensions":["which lighting term is occluded","reconstruction weights","determinism"],"move":"A horizon-based visibility buffer, computed before the beauty pass, now scales only indirect diffuse light inside each material.","backtalk":"In a controlled sun-only capture the old pass changed 4.96 % of pixels and the new one 0.00 %; with ambient light only, the new pass changed 13.51 %. Repeated stills became identical.","reframe":"Five rendering stages replaced seven. The footer coast keeps its older pass until a matched comparison.","commit":"88582b3dd","sources":["jimenez2016gtao"]},{"date":"2026-10-01","title":"Fingers that hold the bar","system":"characters","frame":"Ghibli's open hands floated above the pull-up bars, and the contact targets put wrist skin inside the handles.","dimensions":["finger joints","wrist target against skin offset"],"move":"`coastal_hands.py` added twenty finger joints and fitted the two grip clips to anatomical wrist positions beside the bars.","backtalk":"A skin-offset prototype produced flared wrists and was rejected. In 132 sampled grip frames every digit came within 2 mm of its bar, the deepest intersection was 2.09 mm, and wrist error stayed under 0.33 mm.","reframe":"Move the true wrist target, not the skin. The other avatars kept byte-identical files.","commit":"0fc3b00ef"},{"date":"2026-10-02","title":"Eyes that notice, then return","system":"characters","frame":"The avatars never acknowledged a visitor, and their eyes could not close.","dimensions":["eye and head bandwidth","habituation and cooldown","which clips may move the head"],"move":"Added native eyelids and blink morphs to all five models and an additive layer for blinks, breathing and a short eyes-first glance toward the pointer.","backtalk":"The first lids, at the sclera's depth, left the protruding irises visible and were rejected. Integration review corrected the exported gaze axes, so horizontal attention turns about the vertical axis instead of rolling the head.","reframe":"Contact-sensitive clips cancel head motion immediately, and eating and drinking allow the eyes only.","commit":"19b1648b4","sources":["pan2020gaze"]},{"date":"2026-10-02","title":"Animals that plant their feet","system":"wildlife","frame":"The animals were simple inline shapes, with no articulated acting and no planted contacts.","dimensions":["contact holds","trajectory continuity","support surfaces"],"move":"Rebuilt the raccoon, gull and sandpiper as articulated Blender masters and sampled their acting from active time. Gull soles were fitted to real perch triangles the same day (`7bd235902`).","backtalk":"Rabbit loops had physically crossed the planting and moved to clear scrub edges. Gulls had sampled grass blades as footing until plants were excluded from perch support.","reframe":"Animals, feet and foam all read the same exported terrain.","commit":"a58764f38"},{"date":"2026-10-02","title":"One wind for leaves and particles","system":"wind-plants","frame":"Leaves moved by a small material sway, and dust, spray and vapor each drifted by their own rule.","dimensions":["spatial coherence of the wind","where a leaf is attached","what shadows see","determinism across frame rates"],"move":"A bounded curl-noise field shared by the room plants and every particle population, with each leaf bending around its actual stem attachment and the same bend in its shadow passes.","backtalk":"Only 110 of 132 olive-leaf components had a supporting stem nearby, and one agave component happened to come close to wood, so the automatic resolver is limited to olive leaves. The first shadow version computed the bend inside the depth shader's displacement guard and lost it (`c7e779a1d`).","reframe":"Run the identical hook on every pass that draws a leaf, or report the mismatch.","commit":"f095c818c","sources":["bridson2007curl"]},{"date":"2026-10-02","title":"Skin, water and wind as transport","system":"rendering-light","frame":"Skin, the bath and the leaves were art-directed effects. The terminator was hard, the pool bobbed on a sine, and the leaves swayed unattached.","dimensions":["diffusion distance per channel","conservation and boundaries","divergence-free wind"],"move":"Integrated three bounded, tested adaptations of published methods. Preintegrated skin diffusion after Christensen and Burley, a conservative height-field pool after Chentanez and Müller, and curl-noise wind for rooted leaves and particles after Bridson and colleagues.","backtalk":"The pool held its volume to about 10⁻¹⁴ over a ten-second pulse, and 20 sampled GPU wind velocities matched the CPU within the 8-bit quantization step. A first leaf-shadow compile attempt failed and is kept out of the accepted evidence.","reframe":"The pool should move only when something moves it, so drive it from the bather's hand.","commit":"0b7b9cef7","sources":["christensen2015profiles","chentanez2010water","bridson2007curl"]},{"date":"2026-10-02","title":"Waves from the hand, not a timer","system":"water-atmosphere","frame":"Periodic height pulses made the bath ripple whether or not anyone moved.","dimensions":["forcing source","coupling strength","rest without energy"],"move":"`pool-stroke.mjs` drives a slow anatomical hand skim, and the palm's measured submerged velocity relaxes nearby face velocities inside each CFL step.","backtalk":"Resting hands now add no energy. First samples, stalls and avatar changes cannot turn a placement jump into a fluid kick.","reframe":"Keep the coupling honest in the notes. The water never pushes the hand.","commit":"edb99aee7"},{"date":"2026-10-02","title":"Steam and light that know the house","system":"rendering-light","frame":"Eighteen vapor points did not read as steam, and diffuse light ignored the walls between rooms.","dimensions":["scalar transport","volume compositing around glass","static visibility and bounce"],"move":"Replaced the points with an 18³ thermal density and temperature field rendered by single scattering, and added a CPU-traced static light field with one diffuse bounce and four warm fixtures.","backtalk":"The bath shirt read as skin, so it became opaque teal fabric with diffusion off, and privacy no longer depends on steam or angle. Trials with dense steam and misplaced fixtures were excluded from the accepted evidence.","reframe":"Keep native refraction and reflections separate from the custom diffuse cache.","commit":"780f1292f","sources":["stam1999stable","fedkiw2001smoke","wronski2014fog","ramamoorthi2001irradiance","majercik2019ddgi"]},{"date":"2026-10-12","title":"Twenty-six seconds of shaders, moved off the page","system":"loading","frame":"Opening the 3D home froze the page for about 13 seconds, then again when the rooms streamed in and on the first Look around.","dimensions":["programs first used in a frame","blocking time per program","time to the first view","changed pixels (must stay zero)"],"move":"`program-warmup.mjs` creates every program a frame needs and waits for the driver without blocking, and streamed rooms join the house once their programs are ready.","backtalk":"Three's `compileAsync` cut blocking from 26 s to 5 s, but each first frame still linked ten programs; a define diff showed the shadow pass is keyed without fog. The result was 0.6 s of blocking reads, readiness from 11.3 to 7.5 s, and 0 changed pixels.","reframe":"Next was the outside view's cost, about 2,000 draws a frame.","commit":"011fe99b2","sources":["khr-parallel-shader-compile"]},{"date":"2026-10-13","title":"A mirror that rendered the house twice","system":"water-atmosphere","frame":"Stepping outside cost about 1,900 draws a frame, and the sea's reflection was a large share.","dimensions":["draws and triangles per pass","mirror resolution","changed pixels"],"move":"The 384 px mirror skips transmissive meshes, and the house and footer canvases stopped multisampling because their composers already multisample off screen.","backtalk":"A transmissive mesh made Three redraw the whole opaque scene at full canvas size even inside the mirror. The outside frame fell from 1,934 to 1,574 draws and from 4.45 to 3.57 million triangles, with 0 changed pixels.","reframe":"A profile then showed draw submission takes about 11 of the 16 ms outside, and merging static meshes became the next suspect.","commit":"38ee5438e"},{"date":"2026-10-13","title":"A look lab instead of a guess","system":"rendering-light","frame":"A new tone curve or bloom changes every frame of the home, so the choice needed matched evidence.","dimensions":["tone curve","exposure matched to mean brightness","bloom threshold","fabric sheen"],"move":"URL flags became session-long previews (`95f1f27e7`). I asked for the call to be made from the matched sheet, and the shipped look is ACES Filmic, bloom above 1.6 and fabric sheen.","backtalk":"At equal exposure AgX and Neutral rendered 4 % and 12 % darker than ACES, so exposure factors of 1.08 and 1.24 came first. Neutral was truest at noon outside but greyed the evening shadows. Raising the bloom threshold from 0.92 to 1.6 removed the midday haze while the evening sconce still glows.","reframe":"Hair highlights, smoother edges and the ocean are now judged against this look.","commit":"84cc25a7b","sources":["agx","khronos-pbr-neutral"]},{"date":"2026-10-13","title":"A loading bar that only reports finished work","system":"loading","frame":"Opening the 3D home showed an empty frame and a status line for 2 to 6 seconds, with nothing to say whether it was working.","dimensions":["work known up front","progress source per piece","whether the bar can run ahead","longest main-thread task"],"move":"A five-segment progress bar fills from real events, and the warmup creates programs in batches of six materials with a task boundary between them.","backtalk":"The first bar sat at 80 % for 2.2 s because one task created every program. A profile blamed the shadow variants, but timing each section showed `renderer.compile(scene)` took 0.1 to 1.6 s in a single task. Batches cut the longest frame from 658 to 196 ms, with the same 48 programs and 0 changed pixels.","reframe":"A sampling profile is a lead, not a verdict. Time the sections before changing one.","commit":"cd395cbae","sources":["harrison2007progress"]},{"date":"2026-10-13","title":"Starved, not slow","system":"loading","frame":"The page still froze in steps of up to 0.7 s while the bar filled, and the plan was to spread out buffer and texture uploads.","dimensions":["wall time against CPU time per task","concurrent compiles","programs created after the warmup","readiness"],"move":"The warmup submits a batch only while no more than half the logical cores' worth of programs are compiling, and gives custom depth materials the side and alpha state the shadow pass copies onto them.","backtalk":"The uploads were not the problem. The worst tasks used 3 to 7 ms of CPU across 1.0 to 1.5 s of wall time, because the GPU process ran up to 21 compiles at once on 22 cores and the page waited for a turn. Interleaved runs moved the longest task from 658 to 209 ms and readiness from 4.66 to 5.17 s; an earlier batch-against-batch gain was drift.","reframe":"Compare variants interleaved, never batch against batch.","commit":"b0834bbac","sources":["gregg2020systems"]},{"date":"2026-10-13","title":"A highlight that says hair","system":"rendering-light","frame":"Long black hair rendered as a dark helmet; roughness alone gave no sign of strands.","dimensions":["highlight shape","lobe shift and width","strand direction without tangents","sparkle at the hairline"],"move":"Two shifted strand highlights on every avatar's hair, with strand directions combed from the crown of the bind pose.","backtalk":"All five avatars gained a glossy band across the crown, and a dotted strip at the hairline where fine modeled detail turned the strand direction faster than a pixel. Widening the lobe by the normal's variance removed most of it.","reframe":"Measure sparkle on other bumpy surfaces before applying the same widening everywhere.","commit":"4bf61f640","sources":["kajiya1989fur","marschner2003hair","kaplanyan2016normals","tokuyoshi2019gsaa"]},{"date":"2026-10-13","title":"Where the sea meets the sand","system":"water-atmosphere","frame":"From the coast the sea met the beach in a hard teal-to-sand cut, with no surf.","dimensions":["swash width","travel and fade of a broken front","depth that hides the sand","foam lighting"],"move":"A swash band that breathes with each run-up, a broken front that travels in from about 3 m, thin water that shows wet sand, and foam lit like a white surface.","backtalk":"A debug tint showed the authored beach profile sits about 1.5 m off the visible waterline on the left. The first foam was a frothy patch, and the old unlit foam glowed white at night.","reframe":"A spectral ocean is still the real upgrade.","commit":"9d1a97b10","sources":["tessendorf2004ocean"]},{"date":"2026-10-13","title":"Measure before merging","system":"water-atmosphere","frame":"The outside view ran near 30 fps on an integrated GPU, and the plan was to merge static furniture into fewer draws.","dimensions":["draws per pass","which meshes never move","main-thread time per function"],"move":"Measured first. The onsen solver now runs only where the pool can be seen, saving about 2 ms a frame outside.","backtalk":"Only 12 of 390 drawn meshes were static, opaque and shared a material, so merging would save 3 draws. A quarter-pixel camera shift changed the image by 1.2/255 on average, so 4× multisampling already holds, and temporal antialiasing would ghost on everything that moves.","reframe":"The remaining frame-rate moves change pixels slightly and need an A/B.","commit":"483a80d41","sources":["amdahl1967"]},{"date":"2026-10-14","title":"Room to wander","system":"camera-navigation","frame":"Moving around the home felt limited. Zoom stopped short, and nothing moved the viewpoint itself.","dimensions":["meaning of each gesture per view","zoom range","pan radius","how a visitor discovers the gestures","ordinary page scrolling"],"move":"Semantic zoom from room to house to coast, panning within bounded radii, room names on hover, and ⌘/Ctrl+scroll zoom that leaves ordinary scrolling alone.","backtalk":"Keeping the visitor's heading when zooming out framed the house badly, so views settle on authored framings. Room pans of 1.8 m pushed the camera into walls. One Ctrl-held mouse notch zoomed from 4.4 to 2.4 m until whole notches got the gentler rate.","reframe":"The loading wait was the next empty space.","commit":"e4fddaba5","sources":["perlin1993pad","bederson1994padpp"]},{"date":"2026-10-14","title":"A sketch while the house loads","system":"loading","frame":"The middle of the scene stayed empty while the house loaded, and I asked for something that morphs.","dimensions":["what the wait shows","how it changes with each finished piece","motion under a busy main thread","phone legibility","what reduced motion sees"],"move":"Pen strokes in a worker-owned canvas draw the sea, the cliff, the cut-away house and the occupied room, then settle onto the first frame's edges as it fades in.","backtalk":"Edges of the rendered colors drew bedding planes and leaves as heavily as walls. Object outlines weighted by frame coverage, creases above about 57° and the structure tensor's coherence made legible line drawings, and eight drawings fit in 45 KB.","reframe":"The drawings are fixed captures, so rerun the tracer after a room or camera changes.","commit":"28d77c4b0","sources":["saito1990comprehensible"]},{"date":"2026-10-14","title":"A bed someone can sleep in","system":"characters","frame":"In my screenshot the sleeping avatar sank about 9 cm into a rigid box pillow, and the cover's hem cut through the chest by up to 24 cm.","dimensions":["contact between body, pillow and cover","five very different bodies","how a filled duvet folds","the empty bed","download size"],"move":"Six bed variants, a made bed and a pillow and duvet fitted to each avatar by an original position-based solver.","backtalk":"Blender's cloth solver exploded into spikes. The first custom drape fell to the floor, then hovered like a stiff plate under a Jacobi solver, until colored Gauss–Seidel batches with a soft bend draped like a filled duvet. One cover for all five would have had to stop below South Park's chin.","reframe":"The rest of the house came next, with wall decoration, the onsen's blocky stones and the light well.","commit":"e95545ab1","sources":["muller2007pbd","fratarcangeli2016vivace"]},{"date":"2026-10-14","title":"Stones you could sit on, and things on the walls","system":"authoring","frame":"The onsen's rim was 22 faceted slabs, and most walls were bare plaster.","dimensions":["what each room is for","what the camera actually sees","polygon and draw cost","fit with the person who lives there"],"move":"24 rounded river stones and decoration for four rooms, each piece placed by casting rays through that room's camera onto the walls it sees, joined into 14 meshes.","backtalk":"The cutaway keeps the whole back wall, so every upper-room piece hangs there. Re-exporting the onsen moved the solver's measured depth by 0.02 mm, which a five-decimal test caught.","reframe":"The bamboo spout is waiting for water.","commit":"0e39ce208","sources":["jenkins2004narrative"]},{"date":"2026-10-14","title":"A well for the sun","system":"rendering-light","frame":"I asked for a hole drilled from the clifftop to bring natural light into the house like a well, and for more interesting light-path simulation.","dimensions":["which room and how the shaft leans","hours of sun through the year","the cutaway's open-roof lighting","cost per frame","the estimator for each light path"],"move":"A shaft into the onsen's vault leaning 28° toward the noon sun, followed along four light paths. The shadow map carries the sun patch, dust scatters the beam, quasi-Monte Carlo tracing gives the shaft's radiance, and the patch bounces once into the room.","backtalk":"Keeping the roof in the shadow map everywhere made every room gloomy at noon, so only the onsen is roofed, and the open study let noon sun in sideways until a curtain joined the shadow copy. At plausible dust densities the beam was invisible. Only 5.5 % of the sky passes the shaft, so outside midday the onsen is darker than before.","reframe":"The grotto or the open onsen is my call. Metropolis sampling belongs where paths are hard to find, such as caustics from the water onto the vault.","commit":"2efd976f7","sources":["wronski2014fog","veach1997mlt","li2015h2mc"]},{"date":"2026-10-14","title":"A front for the cave, and a terrace below it","system":"authoring","frame":"I asked for floor-to-ceiling windows onto the ocean, a patio, and rooms that are more open or more intimate, designed the way an architect would.","dimensions":["where the rock jamb exists","what each camera sees through the glass","where the doors can travel","how deep a usable outdoor room must be","glass reflectance against legibility","the sequence from public to private"],"move":"A bronze-framed glass front inside the cave lip with two sliding doors where the kitchen and lounge cameras look in, a corbelled terrace 3.1 m deep, and cedar alcove screens.","backtalk":"The parked kitchen door's pull touched the dining table, so the glass line moved 6 cm out. Ordinary 4 % glass turned the cut-away view into sky by day, so the front became anti-reflective glass. The olive's leaves floated free of its branches until it grew twigs.","reframe":"The terrace is waiting for a sunset hour in the routine.","commit":"00e138803","sources":["alexander1977pattern"]},{"date":"2026-10-14","title":"Lights that keep the house's hours","system":"rendering-light","frame":"Keep the light well, and add light installations inside and out that come on at different times of day.","dimensions":["what each light is for","when it is on","which surfaces it may reach","cost when it is off"],"move":"A terrace festoon, a fire bowl, a soffit wash, stair step lights and a pool light, each on its own schedule. The eight lamps now follow the routine, and the bedside lamp works as a dawn lamp.","backtalk":"Line lights use the exact irradiance of a glowing segment, checked against brute-force integration on 40 random layouts. Bounding boxes keep the festoon off the cliff under the ledge, and by day one uniform skips the whole term.","reframe":"The pool light is where the Metropolis experiment meets the house, because its caustics on the vault are the hard paths.","commit":"00e138803","sources":["kelly1952lighting"]},{"date":"2026-10-14","title":"A coast you can walk up to","system":"authoring","frame":"With visitors walking around outside, the cliff, plants and waves had to hold up at 5 to 10 m.","dimensions":["what 5 to 10 m reveals","which effect causes each artefact","texel and pixel footprint","first-load bytes"],"move":"A camera-following sun shadow on the coast snapped to whole texels, twelve normal-only sea ripples, close-range sandstone detail, and a replanted clifftop of sage scrub, iceplant, grass and Torrey pines that loads after the first frame.","backtalk":"Diagonal stripes on the near water survived turning off the shadows and the mirror; they were the swells themselves, one plane wave per octave. The first planting was 90 % bare and its pines read as dead snags. Streaming the planting shrank the first model from 1.65 to 1.00 MB.","reframe":"Up close the tide-pool rocks looked dry (wetted the same day in `570bd80c8`), and the pines need bark plates.","commit":"00e138803","sources":["bruneton2010ocean","worley1996cellular"]},{"date":"2026-10-14","title":"Two renderers, one minute","system":"light-lab","frame":"I agreed to the Metropolis experiment. Show, on light the house actually has, when Markov chain Monte Carlo beats independent sampling and when it does not.","dimensions":["paths next-event estimation cannot connect","the sun's size against a minute's budget","what equal means","how error is measured","where the reference comes from","how much of a visitor's processor the page may use"],"move":"A dependency-free renderer of a simplified onsen whose single path sampler is fed independent numbers (path tracing) or mutated ones (Kelemen's Metropolis), raced in module workers on its own page.","backtalk":"After a minute path tracing was off by 984 % and Metropolis by 180 %. Metropolis's total brightness is itself a rare-event estimate, because the bootstrap usually holds no sun path. Lazily replayed small steps left a few-percent bias until unread coordinates were redrawn, and a slab shortcut created ghost water hits that tests now catch.","reframe":"The engine is ready for the pool light's caustics, and for manifold-based mutations if the race should be won outright.","commit":"a1b4d3bd3","sources":["veach1997mlt","kelemen2002pssmlt","jakob2012manifold","zeltner2020sms"]},{"date":"2026-10-15","title":"More kinds of plant, from photographs of the real bluffs","system":"authoring","frame":"I kept the first replanting but found one kind of shrub boring, and asked what the landscape actually looks like.","dimensions":["which species grow on the bluffs","their October colors","where each grows relative to the cliff edge","silhouette contrast","bytes for a lazily loaded planting"],"move":"A second planting on its own random stream. Sagebrush and wild oats form the matrix, lemonade berry fills the slope, prickly pear, yucca and rust-red iceplant grow on the rim, daisies line the walks, and one trimmed Canary Island date palm stands by the doorway.","backtalk":"The rust iceplant read as a dark tarp because every mat had been wound clockwise, so its normal pointed into the ground; rewound, the old mats brightened to real iceplant green. A rim mat had stretched over a 7 m drop, the first oats looked like confetti, and the first palm like a spider. `flora.glb` grew from 1.18 to 2.2 MB.","reframe":"If the inland hill needs cover, sparse sagebrush and oats are the cheapest step.","commit":"f403dfead","sources":["nps-cabrillo-plants"]}],"open_problems":[{"id":"spectral-ocean","title":"A spectral ocean","system":"water-atmosphere","why_hard":"The Pacific is ten authored Gerstner components. A spectral FFT ocean would give realistic wave statistics, but WebGL2 has no compute shaders, so the transform needs several ping-pong passes, cascades to hide tiling, and a CPU sampler that agrees with it, because floating objects and arrival rays read the same wave heights.","candidate_moves":["Prototype a 128² FFT height field on a separate page and compare it with the authored spectrum from the same camera.","Keep the authored components for CPU sampling and use the FFT only for normal detail, so contacts stay exact."],"sources":["tessendorf2004ocean","arcblanc2025","bruneton2010ocean"],"readiness":"needs-research"},{"id":"pool-caustics","title":"Caustics on the onsen vault and floor","system":"light-lab","why_hard":"Light reaching the vault or the pool floor through rippled water is a chain of specular bounces that must end in a small sun or a small pool light. Next-event estimation cannot aim through the water, which is the case the light lab measured, and the house has no runtime ray tracer.","candidate_moves":["Add the pool light to the lab scene and render its caustics on the vault.","Precompute a caustic pattern from the lab and project it in the onsen, animated by the solver's slopes.","Try manifold exploration or specular manifold sampling in the lab before anything reaches the house."],"sources":["veach1997mlt","jakob2012manifold","zeltner2020sms"],"readiness":"needs-research"},{"id":"outside-frame-rate","title":"The outside view on integrated GPUs","system":"rendering-light","why_hard":"Outside, about 1,600 draws a frame go through four passes: the view, the shadow map, the opaque copy refraction needs, and the sea mirror. Most objects are interactive, animated, wind-swayed or skinned, so batching could save 3 draws. Every remaining saving changes pixels.","candidate_moves":["A distant, non-refracting onsen material outside, which drops the opaque copy (about 400 draws).","Skip objects smaller than a pixel (about 180 draws).","Judge each with a matched A/B before shipping."],"sources":["amdahl1967"],"readiness":"needs-decision"},{"id":"temporal-antialiasing","title":"Temporal antialiasing","system":"rendering-light","why_hard":"Without per-object motion vectors, temporal antialiasing would trade a little shimmer for ghosting on the characters, animals, plants and waves, which move on almost every frame. 8× multisampling cost about 3 ms of GPU time for no visible gain.","candidate_moves":["Motion vectors for skinned and wind-deformed meshes.","A still photo mode where a temporal filter can accumulate."],"sources":[],"readiness":"blocked"},{"id":"one-wind","title":"One wind for the whole coast","system":"wind-plants","why_hard":"The room plants, particles and steam share the curl field, but the clifftop planting sways on its own sine rule and its shadows see the rest shape. Unifying them means patching the planting's beauty and depth materials with the same field and response, without breaking the divergence guarantee or the shader cache keys the warmup relies on.","candidate_moves":["Bind the clifftop materials to `coastalWind` with the damped response from `plant-motion.mjs`.","Give the planting a custom depth material that runs the same bend."],"sources":["bridson2007curl"],"readiness":"ready"},{"id":"runtime-cloth","title":"Cloth that moves","system":"characters","why_hard":"The duvets are offline drapes and nothing in the scene is runtime cloth. A curtain in the breeze or a duvet that breathes needs a solver that stays stable within the frame budget, attached to animated bodies.","candidate_moves":["Start from the colored Gauss–Seidel solver in `bin/coastal_bedding.py` and port a small extended position-based version to the browser for one curtain.","Drive it with the shared wind field."],"sources":["muller2007pbd","fratarcangeli2016vivace","macklin2016xpbd"],"readiness":"needs-research"},{"id":"dynamic-light-field","title":"Light that sees people and plants","system":"rendering-light","why_hard":"The traced light field is static and has one bounce, so the avatar, P and the swaying plants never shade it. Retracing on the CPU every frame is too slow, and probe updates in the style of DDGI rely on GPU ray tracing that WebGL does not offer.","candidate_moves":["Subtract soft analytic occluders (capsules) for the avatar and P from probe visibility."],"sources":["majercik2019ddgi"],"readiness":"needs-research"},{"id":"onsen-choice","title":"Grotto or open onsen","system":"rendering-light","why_hard":"The light well makes the bath a grotto that gets sun only through the shaft, so outside midday it is darker than the open cutaway was. Both are defensible; it is a taste decision.","candidate_moves":["Keep the grotto.","Open the onsen and show the well only from the clifftop; `?onsen-light=open` previews it."],"sources":[],"readiness":"needs-decision"},{"id":"terrace-hour","title":"A sunset hour on the terrace","system":"characters","why_hard":"The terrace has loungers, a bench and a fire bowl but no activity in the routine. A new activity needs fitted contacts, a camera that looks through a door opening, and a place in the schedule.","candidate_moves":["Add an evening terrace activity with contacts on a lounger or the bench, timed with the festoon and fire bowl."],"sources":[],"readiness":"needs-decision"},{"id":"alternate-treatments","title":"Architectural and Illustrated treatments","system":"rendering-light","why_hard":"In September the three treatments read as too similar, and Realistic worked best. A shader swap alone does not make a different way of seeing the same home.","candidate_moves":["A same-camera comparison where each treatment has its own form, light, mark-making and motion, kept behind `?scene-lab=1`."],"sources":[],"readiness":"blocked"},{"id":"close-up-detail","title":"Close-up character and environment detail","system":"characters","why_hard":"The scene is a stylized browser world. Film-studio character surfacing and close-up environmental detail remain a higher target, bounded by download size and frame budget.","candidate_moves":["Pick one close-up (a face at the desk, the onsen rim) and compare a detail pass against the current frame at the same camera."],"sources":[],"readiness":"needs-research"}],"sources":[{"id":"jimenez2016gtao","label":"Jimenez et al. 2016","citation":"Jorge Jimenez, Xian-Chun Wu, Angelo Pesce and Adrian Jarabo. *Practical Realtime Strategies for Accurate Indirect Occlusion*. Activision technical report ATVI-TR-16-01, 2016.","url":"https://www.activision.com/cdn/research/PracticalRealtimeStrategiesTRfinal.pdf"},{"id":"ext-disjoint-timer-query","label":"Khronos timer queries","citation":"Khronos Group. *EXT_disjoint_timer_query_webgl2*, WebGL extension specification.","url":"https://registry.khronos.org/webgl/extensions/EXT_disjoint_timer_query_webgl2/"},{"id":"ramamoorthi2001irradiance","label":"Ramamoorthi and Hanrahan 2001","citation":"Ravi Ramamoorthi and Pat Hanrahan. *An Efficient Representation for Irradiance Environment Maps*. SIGGRAPH 2001.","url":"https://graphics.stanford.edu/papers/envmap/envmap.pdf"},{"id":"majercik2019ddgi","label":"Majercik et al. 2019","citation":"Zander Majercik, Jean-Philippe Guertin, Derek Nowrouzezahrai and Morgan McGuire. *Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields*. Journal of Computer Graphics Techniques 8(2), 2019.","url":"https://jcgt.org/published/0008/02/01/"},{"id":"pbrt4-bvh","label":"PBRT 4th edition, BVHs","citation":"Matt Pharr, Wenzel Jakob and Greg Humphreys. *Physically Based Rendering*, 4th edition, the chapter on bounding volume hierarchies.","url":"https://www.pbr-book.org/4ed/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies"},{"id":"christensen2015profiles","label":"Christensen and Burley 2015","citation":"Per H. Christensen and Brent Burley. *Approximate Reflectance Profiles for Efficient Subsurface Scattering*. Pixar Technical Memo 15-04, 2015.","url":"https://research.pixar.com/docs/2015.TechnicalReport.CB.pdf"},{"id":"jimenez2015separable","label":"Jimenez et al. 2015","citation":"Jorge Jimenez, Károly Zsolnai, Adrian Jarabo, Christian Freude, Thomas Auzinger, Xian-Chun Wu, Javier von der Pahlen, Michael Wimmer and Diego Gutierrez. *Separable Subsurface Scattering*. Computer Graphics Forum 34(6), 2015.","url":"https://www.iryoku.com/separable-sss/"},{"id":"chen2014pdss","label":"Chen, Lambert and Penner 2014","citation":"Xi M. Chen, Timothy Lambert and Eric Penner. *Pre-Integrated Deferred Subsurface Scattering*. SIGGRAPH 2014 poster.","url":"https://history.siggraph.org/wp-content/uploads/2022/12/2014-Poster-77-Chen_PDSS_-Pre-Integrated-Deferred-Subsurface-Scattering.pdf"},{"id":"noaa-solar","label":"NOAA solar equations","citation":"NOAA Global Monitoring Laboratory. *General Solar Position Calculations*.","url":"https://gml.noaa.gov/grad/solcalc/solareqns.PDF"},{"id":"kelly1952lighting","label":"Kelly 1952","citation":"Richard Kelly. *Lighting as an Integral Part of Architecture*. College Art Journal 12(1), 24–30, 1952.","url":"https://doi.org/10.1080/15436322.1952.11466295"},{"id":"estevez2017sheen","label":"Estevez and Kulla 2017","citation":"Alejandro Conty Estevez and Christopher Kulla. *Production Friendly Microfacet Sheen BRDF*. SIGGRAPH 2017 course on physically based shading.","url":"https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_sheen.pdf"},{"id":"agx","label":"Sobotka, AgX","citation":"Troy Sobotka. *AgX*, a picture formation for scene-referred rendering.","url":"https://github.com/sobotka/AgX"},{"id":"khronos-pbr-neutral","label":"Khronos PBR Neutral","citation":"Khronos Group. *PBR Neutral* tone mapper, Khronos ToneMapping repository, 2024.","url":"https://github.com/KhronosGroup/ToneMapping"},{"id":"threejs-r164","label":"three.js r164","citation":"three.js contributors. *three.js* release r164 (MIT), vendored with its loaders and post-processing passes.","url":"https://github.com/mrdoob/three.js/releases/tag/r164"},{"id":"draco","label":"Google Draco","citation":"Google. *Draco* 3D geometry compression (Apache 2.0).","url":"https://github.com/google/draco"},{"id":"blender","label":"Blender","citation":"Blender Foundation. *Blender* 4.5 LTS, run through its background Python API.","url":"https://www.blender.org/"},{"id":"kajiya1989fur","label":"Kajiya and Kay 1989","citation":"James T. Kajiya and Timothy L. Kay. *Rendering Fur with Three Dimensional Textures*. SIGGRAPH 1989.","url":"https://doi.org/10.1145/74334.74361"},{"id":"marschner2003hair","label":"Marschner et al. 2003","citation":"Stephen R. Marschner, Henrik Wann Jensen, Mike Cammarano, Steve Worley and Pat Hanrahan. *Light Scattering from Human Hair Fibers*. ACM Transactions on Graphics 22(3), SIGGRAPH 2003.","url":"https://doi.org/10.1145/882262.882345"},{"id":"scheuermann2004hair","label":"Scheuermann 2004","citation":"Thorsten Scheuermann. *Hair Rendering and Shading*. Game Developers Conference 2004, ATI Research (slides, course mirror).","url":"https://web.engr.oregonstate.edu/~mjb/cs519/Projects/Papers/HairRendering.pdf"},{"id":"kaplanyan2016normals","label":"Kaplanyan et al. 2016","citation":"Anton S. Kaplanyan, Stephen Hill, Anjul Patney and Aaron Lefohn. *Filtering Distributions of Normals for Shading Antialiasing*. High Performance Graphics 2016.","url":"https://research.nvidia.com/publication/2016-06_filtering-distributions-normals-shading-antialiasing"},{"id":"tokuyoshi2019gsaa","label":"Tokuyoshi and Kaplanyan 2019","citation":"Yusuke Tokuyoshi and Anton S. Kaplanyan. *Improved Geometric Specular Antialiasing*. ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games, 2019.","url":"https://doi.org/10.1145/3306131.3317026"},{"id":"worley1996cellular","label":"Worley 1996","citation":"Steven Worley. *A Cellular Texture Basis Function*. SIGGRAPH 1996.","url":"https://doi.org/10.1145/237170.237267"},{"id":"finch2004gerstner","label":"Finch 2004","citation":"Mark Finch. *Effective Water Simulation from Physical Models*. GPU Gems, chapter 1, 2004.","url":"https://developer.nvidia.com/gpugems/gpugems/part-i-natural-effects/chapter-1-effective-water-simulation-physical-models"},{"id":"bruneton2010ocean","label":"Bruneton, Neyret and Holzschuch 2010","citation":"Eric Bruneton, Fabrice Neyret and Nicolas Holzschuch. *Real-time Realistic Ocean Lighting using Seamless Transitions from Geometry to BRDF*. Computer Graphics Forum 29(2), 2010.","url":"https://evasion.inrialpes.fr/Publications/2010/BNH10/"},{"id":"arcblanc2025","label":"Algis et al. 2025","citation":"David Algis, Bérenger Bramas, Emmanuelle Darles and Lilian Aveneau. *Arc Blanc: A Real Time Ocean Simulation Framework*. arXiv:2503.03326, 2025.","url":"https://arxiv.org/abs/2503.03326"},{"id":"tessendorf2004ocean","label":"Tessendorf, Simulating Ocean Water","citation":"Jerry Tessendorf. *Simulating Ocean Water*. SIGGRAPH course notes, revised 1999–2004.","url":"https://jtessen.people.clemson.edu/reports/papers_files/coursenotes2004.pdf"},{"id":"chentanez2010water","label":"Chentanez and Müller 2010","citation":"Nuttapong Chentanez and Matthias Müller. *Real-time Simulation of Large Bodies of Water with Small Scale Details*. Symposium on Computer Animation 2010.","url":"https://matthias-research.github.io/pages/publications/hfFluid.pdf"},{"id":"stam1999stable","label":"Stam 1999","citation":"Jos Stam. *Stable Fluids*. SIGGRAPH 1999.","url":"https://www.dgp.toronto.edu/public_user/stam/reality/Research/pdf/ns.pdf"},{"id":"fedkiw2001smoke","label":"Fedkiw, Stam and Jensen 2001","citation":"Ronald Fedkiw, Jos Stam and Henrik Wann Jensen. *Visual Simulation of Smoke*. SIGGRAPH 2001.","url":"https://graphics.stanford.edu/papers/smoke/smoke.pdf"},{"id":"wronski2014fog","label":"Wronski 2014","citation":"Bartlomiej Wronski. *Volumetric Fog*. SIGGRAPH 2014, Advances in Real-Time Rendering course.","url":"https://bartwronski.com/wp-content/uploads/2014/08/bwronski_volumetric_fog_siggraph2014.pdf"},{"id":"bridson2007curl","label":"Bridson, Hourihan and Nordenstam 2007","citation":"Robert Bridson, Jim Hourihan and Marcus Nordenstam. *Curl-Noise for Procedural Fluid Flow*. SIGGRAPH 2007.","url":"https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph2007-curlnoise.pdf"},{"id":"pan2020gaze","label":"Pan et al. 2020","citation":"Matthew K. X. J. Pan, Sungjoon Choi, James Kennedy, Kyna McIntosh, Daniel Campos Zamora, Günter Niemeyer, Joohyung Kim, Alexis Wieland and David Christensen. *Realistic and Interactive Robot Gaze*. IEEE/RSJ IROS 2020, Disney Research.","url":"https://la.disneyresearch.com/wp-content/uploads/root.pdf"},{"id":"muller2007pbd","label":"Müller et al. 2007","citation":"Matthias Müller, Bruno Heidelberger, Marcus Hennix and John Ratcliff. *Position Based Dynamics*. VRIPHYS 2006; Journal of Visual Communication and Image Representation 18(2), 2007.","url":"https://matthias-research.github.io/pages/publications/posBasedDyn.pdf"},{"id":"fratarcangeli2016vivace","label":"Fratarcangeli, Tibaldo and Pellacini 2016","citation":"Marco Fratarcangeli, Valentina Tibaldo and Fabio Pellacini. *Vivace: A Practical Gauss-Seidel Method for Stable Soft Body Dynamics*. ACM Transactions on Graphics 35(6), SIGGRAPH Asia 2016.","url":"https://doi.org/10.1145/2980179.2982437"},{"id":"macklin2016xpbd","label":"Macklin, Müller and Chentanez 2016","citation":"Miles Macklin, Matthias Müller and Nuttapong Chentanez. *XPBD: Position-Based Simulation of Compliant Constrained Dynamics*. Motion in Games 2016.","url":"https://matthias-research.github.io/pages/publications/XPBD.pdf"},{"id":"sandiego-childrens-pool","label":"City of San Diego, Children's Pool","citation":"City of San Diego Lifeguard Services. *Children's Pool*.","url":"https://www.sandiego.gov/lifeguards/beaches/pool"},{"id":"sio15-beachwalk","label":"UC San Diego SIO 15 beach walk","citation":"UC San Diego, SIO 15. *Beach walk summary*, on the Scripps Sandstone, the Ardath Shale and cliff erosion along the local coast.","url":"https://geowiki.ucsd.edu/sio15/pdfs/beachwalk-summ.pdf"},{"id":"scripps-cliffs","label":"Scripps, cliff failures","citation":"Scripps Institution of Oceanography. *Exposing Failures*, on coastal cliff erosion research.","url":"https://scripps.ucsd.edu/news/exposing-failures"},{"id":"alexander1977pattern","label":"Alexander et al. 1977","citation":"Christopher Alexander, Sara Ishikawa, Murray Silverstein, Max Jacobson, Ingrid Fiksdahl-King and Shlomo Angel. *A Pattern Language*. Oxford University Press, 1977.","url":"https://en.wikipedia.org/wiki/A_Pattern_Language"},{"id":"jenkins2004narrative","label":"Jenkins 2004","citation":"Henry Jenkins. *Game Design as Narrative Architecture*. In *First Person: New Media as Story, Performance, and Game*, MIT Press, 2004.","url":"https://web.mit.edu/~21fms/People/henry3/games\u0026narrative.html"},{"id":"nps-cabrillo-plants","label":"Cabrillo National Monument","citation":"National Park Service, Cabrillo National Monument. *Plant Communities*.","url":"https://home.nps.gov/cabr/learn/nature/plant-communities.htm"},{"id":"khr-parallel-shader-compile","label":"Khronos parallel shader compile","citation":"Khronos Group. *KHR_parallel_shader_compile*, WebGL extension specification, 2019.","url":"https://registry.khronos.org/webgl/extensions/KHR_parallel_shader_compile/"},{"id":"gregg2020systems","label":"Gregg 2020","citation":"Brendan Gregg. *Systems Performance*, 2nd edition. Addison-Wesley, 2020.","url":"https://www.brendangregg.com/systems-performance-2nd-edition-book.html"},{"id":"harrison2007progress","label":"Harrison et al. 2007","citation":"Chris Harrison, Brian Amento, Stacey Kuznetsov and Robert Bell. *Rethinking the Progress Bar*. UIST 2007.","url":"https://doi.org/10.1145/1294211.1294231"},{"id":"saito1990comprehensible","label":"Saito and Takahashi 1990","citation":"Takafumi Saito and Tokiichiro Takahashi. *Comprehensible Rendering of 3-D Shapes*. SIGGRAPH 1990.","url":"https://doi.org/10.1145/97880.97901"},{"id":"perlin1993pad","label":"Perlin and Fox 1993","citation":"Ken Perlin and David Fox. *Pad: An Alternative Approach to the Computer Interface*. SIGGRAPH 1993.","url":"https://doi.org/10.1145/166117.166125"},{"id":"bederson1994padpp","label":"Bederson and Hollan 1994","citation":"Benjamin B. Bederson and James D. Hollan. *Pad++: A Zooming Graphical Interface for Exploring Alternate Interface Physics*. UIST 1994.","url":"https://doi.org/10.1145/192426.192435"},{"id":"amdahl1967","label":"Amdahl 1967","citation":"Gene M. Amdahl. *Validity of the Single Processor Approach to Achieving Large Scale Computing Capabilities*. AFIPS Spring Joint Computer Conference, 1967.","url":"https://doi.org/10.1145/1465482.1465560"},{"id":"veach1997mlt","label":"Veach and Guibas 1997","citation":"Eric Veach and Leonidas J. Guibas. *Metropolis Light Transport*. SIGGRAPH 1997.","url":"https://graphics.stanford.edu/papers/metro/"},{"id":"kelemen2002pssmlt","label":"Kelemen et al. 2002","citation":"Csaba Kelemen, László Szirmay-Kalos, György Antal and Ferenc Csonka. *A Simple and Robust Mutation Strategy for the Metropolis Light Transport Algorithm*. Computer Graphics Forum 21(3), 2002.","url":"https://doi.org/10.1111/1467-8659.t01-1-00703"},{"id":"veach1997thesis","label":"Veach 1997, thesis","citation":"Eric Veach. *Robust Monte Carlo Methods for Light Transport Simulation*. PhD thesis, Stanford University, 1997.","url":"https://graphics.stanford.edu/papers/veach_thesis/"},{"id":"pbrt3-mlt","label":"PBRT 3rd edition, Metropolis","citation":"Matt Pharr, Wenzel Jakob and Greg Humphreys. *Physically Based Rendering*, 3rd edition, section 16.4, Metropolis Light Transport.","url":"https://pbr-book.org/3ed-2018/Light_Transport_III_Bidirectional_Methods/Metropolis_Light_Transport"},{"id":"li2015h2mc","label":"Li et al. 2015","citation":"Tzu-Mao Li, Jaakko Lehtinen, Ravi Ramamoorthi, Wenzel Jakob and Frédo Durand. *Anisotropic Gaussian Mutations for Metropolis Light Transport through Hessian-Hamiltonian Dynamics*. ACM Transactions on Graphics 34(6), SIGGRAPH Asia 2015.","url":"https://people.csail.mit.edu/tzumao/h2mc/"},{"id":"jakob2012manifold","label":"Jakob and Marschner 2012","citation":"Wenzel Jakob and Steve Marschner. *Manifold Exploration: A Markov Chain Monte Carlo Technique for Rendering Scenes with Difficult Specular Transport*. SIGGRAPH 2012.","url":"https://www.cs.cornell.edu/projects/manifolds-sg12/"},{"id":"zeltner2020sms","label":"Zeltner, Georgiev and Jakob 2020","citation":"Tizian Zeltner, Iliyan Georgiev and Wenzel Jakob. *Specular Manifold Sampling for Rendering High-Frequency Caustics and Glints*. SIGGRAPH 2020.","url":"https://rgl.epfl.ch/publications/Zeltner2020Specular"}]}