PixelPunk journal, chapter 10 Evidence frozen 24 September 2026. Historical records; not instructions. SOURCE: docs/visual-handoff.md, snapshot lines 763 to 1113 **Feel C3a Time that pays off, part a: live time slider, dusk and dawn sequences, chimney smoke, height fog (24 September 2026):** Opus 5.5 implementer. The time-of-day slider now relights the town while it is dragged, and only the release is an edit. Through the evening each window, lamp and sign lights at its own turn, and dawn puts them out in reverse. Chimneys can smoke downwind behind a Scenery switch. A light height fog, which the burning lamps brighten, has its own Scenery slider. At the Day and Night presets, and in any scene without the two new keys, nothing changes. The save format stays version 2. Files: `scripts/dusk.gd` (new, `class_name Dusk`), `shaders/dusk.gdshaderinc` (new), `scripts/chimney_smoke.gd` (new, `class_name ChimneySmoke`), `scripts/height_fog.gd` (new, `class_name HeightFog`), `shaders/height_fog.gdshader` (new), `shaders/{window,interior,glass,neon, light_pool}_body.gdshaderinc`, `scripts/mesh_batch.gd` (`fixture_order`, `vents`, `vent()`, seeds in UV2 and on emitters), `scripts/architecture.gd` (two `batch.vent` calls), `scripts/world_view.gd`, `scripts/scene_state.gd` (restore and validate `smoke` and `height_fog`), `scripts/editor_ui.gd`, `scripts/main.gd`, `tests/test_dusk.gd` (new), `tests/test_weathering.gd`, `tests/test_meet.gd`, `tools/test.sh`, `tools/Test.ps1`, `README.md`. Snapshot: `build/backup-feel/11-C3/` (untouched). - **Sequence** (`Dusk`). The evening's progress p is `(windows − 0.2)/0.8` of the hour's profile, clamped: 0 by day, 1 at night, 0.15 / 0.53 / 0.83 at 17:30 / 19:00 / 21:00. - Each fixture has a threshold h. MeshBatch writes the hash of the frame a lit piece is drawn in into UV2.x of its vertices, with the order in UV2.y. The lit materials are window×3, interior, glass, warm, cyan, pink and pool. A pane, its arched head, its halo and its light source share that frame. - A placed lamp (`Dusk.LAMP_KINDS`) has order 1: `h = 0.8·clamp(|xz|/20) + 0.2·seed` on its instance origin, so the lamps ignite from the middle of the town outward. Windows and signs use the seed alone. - A fixture is lit a 0.06 band after p passes h. Before that it keeps `min(value, floor)` (floor 0.2 for windows and neon, 0.12 for lamps and pools). Cyan and pink neon sputter through the band. - At p = 0 or 1 every shader and `Dusk.level` return the old uniform exactly. - The hash is 32-bit integer maths, the same on the GPU as in GDScript. The OmniLight pool powers each light by `WorldView.lamp_power` on its source's threshold. - **Live slider.** While the time slider is dragged, `environment_previewed` goes to `main.preview_environment`. That writes the hour, runs `sync_profile` and `apply_presentation`, and keeps the hour from before the drag. - A release restores that hour and then commits one edit. A drag that ends where it began (`environment_preview_cancelled`) restores it. A running clock waits while the knob is held. - **Smoke** (`environment.smoke`, optional bool, absent = off). One `CPUParticles3D` emits from every chimney mouth: the stone stacks from `_roof_trim` and placed `chimney` stacks. Wind is the rain's slant, `(−0.10, 0, 0.05)` normalised, with drift 0.16 + 0.5·rain m/s². The colour is the hour's ambient and haze, dimmed at night. Harbour has 7 mouths, the artwork 1. - **Fog** (`environment.height_fog`, optional 0..1, absent = 0 and the node hidden). It is a full-screen quad drawn last in the 3D pass, not the canvas presentation shader, which cannot read 3D depth under GL Compatibility. - It reads the depth texture and ends water rays on the y 0.15 plane. The layer is exponential from the water (falloff 1.3 m, density 0.4, at most 0.45·amount), coloured by the hour's haze. - The ≤16 burning pool lights add a soft-capped glow. CLI: `--chimney-smoke=on|off`, `--height-fog=<0..1>`. `performance.json` gains `"evening"`. Tests: - Test first: `artifacts/feel/11-C3/a/a-testfirst-test_dusk.log(.out)`, 27 checks, 19 failures (missing scripts gated). After that run, `canal_house` was fixed (it used `add_object`), the flicker getter was made null-safe, the artwork chimney count check became "mouths = roof sites + stacks", and the fog constant lookup was changed. - `test_dusk`: 136 checks, 0 failures (`a/final/test_dusk.log.out`). - Full set (`a/full/summary.txt`, via `a/full.sh`): 28 suites, 10,883 checks, 0 failures. `a/full/verify.log` and `a/final/verify.log` both have `PIXELPUNK_INTEGRATION []`. `test_dusk`, `test_ui` and `test_time` were re-run in `a/final/` after the last `main.gd` edit. Changed expectations: - `test_weathering` stream check: seeded materials now carry one UV2 per vertex. The old rule was that interior, warm, glass and pool never write UV2. - `test_meet` `full_fingerprint`: seeded groups' UV2 and the emitters' `seed` and `order` are left out, and the stock connections then match `stairs-pre.json` byte for byte. Captures: `artifacts/feel-journey/11-C3/pkg/` (`a/cap.sh`, artwork scene and camera, rain 0.45, before = the `11-C3` tree). I looked at every PNG listed. - `before-` and `after-{1730,1900,2100}`: the windows and lamps change, 4.3 / 9.2 / 9.2 % of pixels (`diff.png`, `a/probe/diff-main.txt`). At 17:30 a few windows are lit; by 21:00 most are. - `dusk-sheet.png`: the town's middle at 17:30, 18:00, 18:30, 19:00, 19:30, 20:00, 20:30 and 21:00. The tower's windows light one by one. `before-dawn-sheet.png` has the before tree at the three hours and the after tree at 06:00, 07:00 and 08:00. - `fog-{1730,1900,2100}`: fog 0.5 and smoke on. A mist lies over the canals (milky at 0.5 by golden hour), with a warm glow round the plaza lamps at 21:00. The artwork's one stack smokes just above the frame. - `before-`/`after-{day,night}` (rain 0) differ by 0.26 / 0.92 %, and `before-`/`after-harbour-day` by 0.35 %. All of it is water shimmer, twinkle and sway; no building or lamp changes. - `smoke-harbour-day` and `smoke-harbour-1900` show seven plumes leaning downwind. Measurements (vsync off, 1920x1080, medians of 3, alternating): | Measure | Before (ms) | After (ms) | Source | |---|---|---|---| | Dense fixture | 13.39 | 13.50 | `a/bench-dense-novsync-v2.txt` | | Dense fixture, after run first | 13.37 | 13.48 | `a/bench-dense-novsync-v3-reversed.txt` | | Artwork 21:00, fog 0 against 0.5 | 8.727 | 8.669 | `a/bench-fog-2100.txt` | - The fog's cost is below run-to-run noise (spread about 0.17 ms), within the 0.5 ms budget. - The dense +0.11 ms (0.8 %) is consistent. Taking out one piece at a time: the per-vertex hash is 0, the smoke and fog nodes are 0, and the UV2 seeds about 0.04 ms (`a/bench-diag-*.txt`). The rest is not isolated. - A first set also timed the GPU with `viewport_set_measure_render_time`. The driver reported 0 and the set read +0.20 ms (`a/bench-dense-novsync.txt`), so the timer was taken out. - The drag harness was not run (track C). Review: . Not done: - Reflection and paving streaks, the backdrop's far city, leaves and the interior and glass albedo outside the seeded shaders still follow the uniform hour value. A streak can show under a lamp that is not lit yet. - Neon flicker is a function of the hour, not wall time, so a clock stopped mid-band holds a dim sign. - The fog glow comes only from the 16 pool lights near the view centre. - Smoke points come from full-detail roofs only, so a building being dragged loses its plume until release. - No filmstrip through the `--drag` harness; the dusk sheet stands in for motion. - Fog at full pixel size (pixels 1) was not measured. - No one has judged the fog strength for taste. Platforms: Mac run; Windows not run (`Test.ps1` edited, not run). **Feel C2b Sound that follows the hand, part b: ambience by content and camera, a procedural layered score, and Scenery volume controls (24 September 2026):** Opus 5.5 implementer. The scene now has its own sound under the builder's. Water laps in proportion to the water in view, wind rises as the view zooms out, birds sing by day over meadow and trees, rain follows the Rain setting, and neon signs hum near the middle of the view at night. Under that plays a synthesised score in four layers (pad, bass, pluck, chime). The time of day picks a mood with its own key, scale, tempo and three or four of those layers. The score thins while build sounds play, returns two to three quiet seconds later, and gains layers and notes as more is built. Ambience volume and Music volume sit in Scenery. They are optional environment keys that save with the scene and are undoable. Nothing else is saved, drawn or put in undo, and the save format stays version 2. Files: `scripts/scenery_audio.gd` (new, `class_name SceneryAudio`), `scripts/build_audio.gd` (`last_sound`, `quiet_time`), `scripts/main.gd` (`scenery_audio`, `LISTEN_GAP` update in `_process`, configure in `_sync` and `change_environment`, `"audio"` in the performance report, help text), `scripts/editor_ui.gd` (two sliders), `scripts/scene_state.gd` (restore and validate `music` and `ambience`), `tests/test_scenery_audio.gd` (new), `tools/test.sh`, `tools/Test.ps1`, `README.md`. Snapshot: `build/backup-feel/10-C2/` (untouched). This part's "before" is the post-part-a tree, saved as `build/backup-feel/10-C2-b-pre/` before the first edit. Working copies are in `build/backup-feel/10-C2-b-work/`. - **Listening** (`listen`, `ground_samples`, `sources`). Four times a second (`LISTEN_GAP` 0.25 s) main casts a 9 x 9 grid of view rays onto the terrain, walked once onto the ground height or the water surface. The levels are: - water: the share of samples on water or stepping stones, 0 with the water surroundings off; - wind: `WIND_BASE` 0.06, rising to 1 between camera sizes 14 and 70; - birds: day (06:00 to 19:00, with fades) × (0.25 + 0.75 × greenery) × (1 − 0.7 × rain). Greenery is meadow or flower samples, or trees near the target. There are fewer birds zoomed right out; - rain: `environment.rain`; - hum: signs (`neon`, `neon_bar`, `neon_strip`) within 5 m plus 0.1 × size of the target, × night (the profile's `lamps`) × a fade between sizes 24 and 60. The tree and sign positions are kept between edits (`content_dirty` from `_sync`, a changed part count, or every 8th listen). Levels glide with `AMB_TAU` 0.5 s. - **Score.** Four moods: morning 05:00–09:00 (pad, pluck, chime), day 09:00–17:30 (pad, bass, pluck), evening 17:30–21:00 (all four), night (pad, bass, chime). Each has a root, a pentatonic scale, 60 to 84 bpm and a four-chord cycle. - A seeded eighth-note sequencer plays on the node's clock. The pad is a looped 2 s tone with a per-voice envelope on two sets of three voices, and it changes chord every 2 bars. The mood changes only there. - Layers join at 0, 3, 10 and 24 parts (`RICH_STEPS`). Melody density rises with log(parts) up to 120. - **Thinning.** `BuildAudio.last_sound` moves whenever any builder sound starts, or while a stroke loop is above silence. `thin_target(quiet)` is 0 for the first `QUIET_RETURN` 2.0 s, then a smoothstep to 1 over `RETURN_FADE` 1.0 s. The drop takes `THIN_TAU` 0.08 s. While thin, the pad sits at `PAD_THIN` 0.35 and no bass, pluck or chime note starts. - **Synthesis.** Eleven sounds at 16 kHz on a `WorkerThreadPool` task (130 to 136 ms, off the main thread). The score starts on the first context, so it opens in the view's mood. - **Voices.** 16 players (6 pad, 1 bass, 2 pluck, 2 chime, 1 bird, 4 beds), 31 with the builder's 15. The builder's Sound switch does not mute the scenery; its own two volumes do (0 stops every voice). Tests: - Test first: `artifacts/feel/10-C2/b/b-testfirst-test_scenery_audio.log(.out)`, 1 check, 1 failure (the script is missing, so every later gate is unreached). Checks were refined after that run: - the pad became a loop; - notes are judged in the mood that played them; - the stroke-return time allows for the loop's 0.7 s fade; - kept-sources equality and a real-value thinning print were added. - `test_scenery_audio`: 221 checks, 0 failures (`b/full2/test_scenery_audio.log.out`). It covers: - loops, one-shots and determinism, and the thread render matching the synchronous one; - the voice count, moods, and richness (monotonic; an empty canvas hears only the pad); - thinning on a hand clock: 0.0235 at +0.3 s, 0.0000 at 1.9 quiet s, 0.500 at 2.5 and 1.0000 at 3.0. It stays thin under repeated sounds and a held wall stroke, with no bass, pluck or chime note while thin, and notes after the return; - notes in scale, more notes for a bigger town, and mood changes only at a phrase; - listen for water, wind, rain, birds and hum, and the ambience glide and mute; - saving: byte-identical without the keys, a JSON round trip as floats, refusal of bad values; - the editor: context, ground samples, zoom raising the wind, thinning, both sliders, save and read, undo and redo, silence at 0. - Full set, foreground, one Godot at a time (`b/full2/summary.txt`, via `b/full.sh`): 27 suites, 10,747 checks, 0 failures. `b/full2/verify.log` has `PIXELPUNK_INTEGRATION []`. Changed expectations: none. `performance.json` gains an `"audio"` block (tick, listen and render cost, voices sounding). Captures (`artifacts/feel-journey/10-C2/pkg/`, `b/cap.sh`, before = `10-C2-b-pre` tree). I looked at each PNG listed. - `b-{before,after}-harbour-{day,night}` and `b-{before,after}-dense-day`: `diff.png` (`b/probe/diff.gd`, `b/probe/diff-final.txt`) marks 0.45, 0.27 and 0.96 % of pixels. All of them are water shimmer, sky and canopy sway; no building or ground pixel changes. - Before against before (`b-before2-*`, `b/probe/noise.txt`) is 0.43 % on harbour-day and 0.53 % on dense. - Every `performance.json` count matches except timings and night pool energy (14.79 against 14.78; corrected by the orchestrator 24 September after the handoff check, the entry first said 14.80: `b-before-harbour-night` 14.7855, `b-after-harbour-night` 14.7771), and that energy moves between runs of one tree too (`b/probe/counts.txt`). - `b-after-sound/` (`b/session.gd`, windowed): `session.wav` is 30 s recorded off the Master bus by `AudioEffectRecord`, so it is what the mixer produced. `trace.png` and `trace.json` are also here. `b-before-sound/session.wav` has the same edits with the builder only. - Harbour at night: building starts at 8 s and the score is below 0.1 by 8.21 s. The last builder sound is at about 12.19 s. The score starts back at 14.25 s and is whole at 15.16 s. - The meadow by day from 18 s: wind and birds rise, and the day score takes over at the 24 s phrase. Its first pluck note is at 24.71 s (`trace.json` summary.notes); the pluck layer's level starts rising at 24.03 s (`trace.json` rows), which the handoff check read as the pluck. - RMS (`b/probe/session-rms.txt`), after against before: −31.2 against silence for 0–8 s, −25.7 against −26.4 dBFS while building, −30.9 against silence for 14.5–18 s. Measurements (same sitting, alternating, 1920x1080, vsync off, `b/dense3.sh`, `b/bench/`, `b/bench-*.txt`): | Scene | Before (ms) | After (ms) | Process CPU, before → after | Draw calls | |---|---|---|---|---| | Dense | 13.33 | 13.40 | 9.91 → 10.23 s | 2,124 = 2,124 | | Harbour | 7.66 | 7.64 | 6.76 → 7.01 s | 1,007 = 1,007 | - Frame times are medians of 3; CPU is user+sys for the whole process over a run of about 13.5 s (dense) or 8.6 s (Harbour). - The dense +0.07 ms matches the scenery's main-thread cost. `tick` is 0.033 to 0.038 ms a frame, 0.39 ms at most. `listen` is 0.36 ms four times a second, about 0.02 ms a frame. - The first dense set, before the tree and sign lists were kept (`bench-dense-novsync.txt`, listen 0.72 ms), read 13.31 against 13.39. - Audio CPU on dense: +0.32 s of process CPU per run, about 2.4 % of one core. That covers the mixer thread, the 0.13 s synthesis task and the main-thread ticks. - Headless (`b/full2/test_scenery_audio.log.out`): `listen` on dense takes 0.224 ms with a fresh read and 0.063 ms with the lists kept. `tick` takes a median of 0.015 ms. - The drag harness was not run, since this is a track-C part. Review: . Not done: - No person has listened for taste; the WAVs are there for the owner. - Birdsong is not tied to C1's creatures: C1 has not run, so birds follow the hour and the greenery. - There was no rain sound before this part. "Rain as today" is read as a rain bed that follows the existing Rain setting. - The ambience is mono, with no panning by position. - Music and ambience play in automated windowed runs, and the default volumes are 0.5 and 0.6. - Samples are 16 kHz and a pluck plays up to about 2x pitch, so there may be some dulling; no one has listened for it. Platforms: Mac run; Windows not run (`Test.ps1` was edited but not run). **Feel C2a Sound that follows the hand, part a: per-piece clicks, stroke sounds, handle ticks and a quieter undo (24 September 2026):** Opus 5.5 implementer. With Build animations on, every new brick, tile, plank and stone course now clicks when its piece starts to grow on A2's schedule. The pitch follows the piece's size, and a rate limit and a voice budget keep a whole house a patter. A freehand wall stroke plays a looped stone-stacking layer that follows the hand's speed, the wall's height and the stroke's curvature, with glass and wood layers when it applies. A terrain stroke plays an earthy loop while it changes ground, a handle drag ticks per step, and undo and redo are quieter and plainer. Nothing is drawn, saved or undone, and the save format is unchanged. Files: `scripts/build_audio.gd`, `scripts/world_view.gd` (`piece_clicks`, `take_piece_clicks`, `_note_clicks`), `scripts/build_anim.gd` (optional `report` argument to `diff`, which returns `reported`), `scripts/main.gd`, `tests/test_build_audio.gd` (new), `tools/test.sh`, `tools/Test.ps1`. Snapshot: `build/backup-feel/10-C2/` (untouched). Working copies: `build/backup-feel/10-C2-a-work/`. - **Clicks.** When a chunk first draws a cluster, `BuildAudio.piece_events` bins that cluster's new instanced bricks and tiles. Pieces that start within `CLICK_BUCKET` (30 ms) of each other make one event, so a course is one click. `BuildAnim.diff` reports the animating wood pieces, plus the "plain" pieces of a timber style, as planks (`reveal_events`). A cluster without instanced bricks, such as a freehand wall, is heard a stone course (`Masonry.ROW`) at a time as the reveal line climbs. Main queues these events on the audio clock. `tick()` plays each one unless it is more than `CLICK_LATE` (150 ms) late, no closer than `CLICK_GAP` (30 ms) to the last click, and only on a free voice (`CLICK_VOICES` 3). A replayed rebuild plays one sample at one pitch, `REPLAY_GAP` 110 ms apart and 8 dB lower. The pitch is `sqrt(SIZE_REF/size)`, clamped to 0.7 to 1.5. - **Strokes.** `stroke_mix(speed, height, curvature)` sets the stacking level and pitch. The speed and curvature come from the last samples of the stroke clock (`stroke_motion`), so a replay sounds the same. A hand held still for 0.1 s fades out (release time constant 0.12 s). - Glass joins when the continued wall carries windows or the stroke meets a modern building. Wood joins beside a japanese or medieval building (`wall_layers`, `point_layers`, `layer_bodies`, read once per stroke). - The terrain loop follows the cells changed per dab and decays over 0.3 s. Only the first changed dab now plays its material sound; before, every dab did, rate limited to 90 ms. - **Ticks.** `drag_resize` now wraps `_drag_resize_step`. When a frame moves the part's 0.25 m / 15 degree signature (`handle_signature`), one tick plays, higher for up and lower for down, at most once per `TICK_GAP` (30 ms). - **Undo.** `play_undo` plays at -16 dB against -11 before, at pitch 1. - **Budget.** `VOICE_BUDGET` is 15 players: 6 edit voices (still limited to 90 ms), 3 click voices, 2 tick voices and 4 loops. The loops are synthesised one per tick over the first four frames. Muting stops every voice and clears the queue. Tests: - Test first: `artifacts/feel/10-C2/a/a-testfirst-test_build_audio.log(.out)`, 40 checks, 40 failures (every gate missing). Three checks were added after that run: the wall-course check, the editor handle-drag check, and the loop-wrap check, which moved from a quarter of the peak to the largest step inside the loop. - `test_build_audio`: 191 checks, 0 failures (`a/a-run5-test_build_audio.log.out`). It covers samples and loops, the budget, pitch, bucketing, the click gap, voices, lateness, the queue cap, replays, the edit-sound limit and the undo level. It covers the stroke mix, motion and layers, the terrain envelope, handle steps and tick limits, and muting. It checks WorldView scheduling: switch off, settled, a replayed raise, planks and wall courses. It checks the editor: wall stroke, clicks queued, undo, redo replay, road stroke, handle drag and the Sound switch. - Full set, foreground, one Godot at a time (`a/full2/summary.txt`, via `a/full.sh`): 26 suites, 10,526 checks, 0 failures. `a/full2/verify.log` has `PIXELPUNK_INTEGRATION []`. Changed expectations: - The `--verify` mute check now covers `all_players()` rather than the 6 edit voices. - A terrain stroke no longer clicks on every dab, as described under Strokes. Captures: - `artifacts/feel-journey/10-C2/pkg/` (`a/cap.sh`). `a-{before,after}-harbour-{day,night}` and `a-{before,after}-dense-day` use the pre-stage tree and the final code. Every `performance.json` count is equal (`a/probe/counts-final.txt`). - `diff.png` (`a/probe/diff.gd`) marks 1.83, 2.31 and 0.86 % of pixels, all on water, sky and shore reflections. No building or ground pixel changes. - Two captures of the before tree differ by 0.27 % (`a-before2-harbour-day/diff.png`). Suggested cause: the water and sky shaders run on wall-clock `TIME`, and the four loops synthesised over the first frames move the frame-75 capture later. - `a-after-sound/` (`a/trace.gd`) holds `trace.png` (key in the script), `trace.json`, and `after-*.wav` and `before-*.wav` for six edits. The WAVs are offline mixes of what `BuildAudio` played. The "before" files replay the old rules and are not recordings of the old build. The edits: - house: 703 new pieces make 31 events and 25 clicks, with 5 dropped for want of a voice. - redo: 16 events make 4 clicks. - wall: the stacking layer dips in the bend, then 7 course clicks follow the release. - wall-timber: the same with the wood layer. - terrain: one edit sound and the loop, against 15 dab clicks before. - handle: 6 ticks for 3.0 to 4.6 m. Measurements (same sitting, alternating, 1920x1080, `a/dense3.sh`, `a/bench/`): | Scene | Vsync | Before (ms) | After (ms) | Draw calls | |---|---|---|---|---| | Dense | Off | 13.32 | 13.36 | 2,124 = 2,124 | | Harbour | Off | 7.63 | 7.62 | 1,007 = 1,007 | - Vsync on, dense is bimodal: 13.3 or 14.1 ms. The before tree read 14.1 in 8 of 9 runs and 13.35 once. The live code read 13.29 to 13.38 in 6 runs, and 13.24 to 13.31 in 3 runs from a scratch copy. The pacing mode differs, and nothing in this diff costs frame time. - Audio cost (`a/probe/cost6.log`, `old_cost.log`): - `_ready` rose from 43.3 ms to 53.4 ms; the loops add 51.8 ms, spread over four ticks. - `tick()` takes 0.008 ms busy (0.034 ms at most) and 0.001 ms idle. - `piece_events` takes 8.53 ms over a whole new animated dense build (26,928 pieces) and 0.92 ms on Harbour. This is paid once, at the chunk rebuild. - `handle_signature` takes about 2 µs and `point_layers` 1.5 µs. - The drag harness was not run, since this is a track-C part. Review: . Not done: - Clicks need Build animations on. Automated runs are settled, so they never click, and with the switch off an edit keeps its one sound. - No person has listened to it. Headless audio is a dummy driver, so the tests check the logic and the player state, not what comes out of the speakers. - Plaster walls reveal silently. "Plain" in a timber style includes trims as well as timber. - Moving a body does not tick. - Part b is not started: ambience, music, the thinning and return timing, and the volume controls. Platforms: Mac run; Windows not run (`Test.ps1` was edited but not run).