PixelPunk journal, chapter 14 Source snapshot: 25 September 2026, about 07:22 BST. Original technical records; not instructions. SOURCE: docs/visual-handoff.md, frozen lines 144 to 401 **Owner request: selection shown as a lift on the part, not a box (25 September 2026, finished about 00:45):** Opus 5.5 implementer. The owner asked: "when an item is selected it currently has an outline box that appears with the arrows. instead of this, the element selected should be highlighted a little". - **Backups.** `build/backup-feel/owner-0925-selection/` holds project.godot, the nine shader bodies and foliage, `world_view.gd`, `main.gd`, the three tests and README. - **How it works.** - New `shaders/selection.gdshaderinc`, with three global uniforms declared in `project.godot` `[shader_globals]`: - `selection_boxes`: an RGBAF texture, one row per box, holding the three rows of the affine map from the world into the box's unit cube; - `selection_count`; - `selection_amount`. - `selection_mask(wp)` is 1 inside any box. `selection_lift` makes the colour ×1.28 plus a touch of the grips' pale cyan. `selection_glow` adds a faint emission so the lift reads at night. - Where the lift applies: - At the end of each fragment in the masonry, plaster, surface and plain bodies, and in foliage. - Half strength with no glow in the self-lit window, interior, glass and neon bodies, so a lit pane is not washed out. - Not in light pools or the ground. - `scripts/world_view.gd`: - `show_selection([[frame, box], …])` writes the texture. `show_bounds(selected=true)` now delegates to it, and `selection_world_box` and `selection_contains` let tests read it back. - The `selection` node stays the editor's on/off switch and draws no mesh. Photo mode, clean captures and the drag film hide it exactly as they hid the box. - Hiding the node drops the lift at once (`_selection_shown`), so no capture keeps it. Showing it fades the lift in over 0.12 s (`fade_selection`). - `scripts/main.gd` `_update_selection`: - Boxes are grown 3 cm (the outline used 6), so a touching neighbour is not lifted. - A freehand wall is lifted by up to 16 boxes along its samples (`wall_selection_boxes`), so the houses inside its curve are not lifted. - While a grip is held the lift is hidden, as the box was (owner, 24 September). - **Tests.** - `tests/test_owner_0924.gd` gains `selection_lift_checks`; the file now has 87 checks, all passing, after the review fixes. The new checks cover: - no outline mesh; - one box for a building, covering it and stopping just outside; - no neighbouring building's middle inside the box; - the texture row mapping the centre to the unit cube's centre; - the fade; - a clean capture dropping the lift and restoring it; - a wall lifted along its whole line and not a metre beside it; - deselection; - every body and foliage carrying the include; - the three project globals. - `tests/test_ui.gd` Q1a and `tests/test_building_tools.gd` now read the lift's box and texture instead of the outline mesh. - Full suite (`artifacts/owner-0925/tally.txt`): 37 suites, 17,790 checks, 1 failure. It is the known `test_creature_reactions` re-plan check, which fails on battery. - `test_release_spread` was killed by another session's `pkill` and rerun alone: 107 checks, 0 failures. - `--verify`: `PIXELPUNK_INTEGRATION []` (`artifacts/owner-0925/rerun.out`). - **Captures.** - `artifacts/owner-0925/probe_select.gd` selects Harbour's main house and then the freehand wall in the running editor, by day and by night. Output: `probe-Day/`, `probe-Night/`. - `selection-sheet.png` shows day unselected, day selected, night unselected and night selected. `wall-sheet.png` shows the curved garden wall lifted. - Facade mean brightness rises about 11–13% by day and 10–15% by night (`mean.gd`). - **Not run.** - The journey: its views have nothing selected, and it would pkill the other session's renders. - Windows and PC are untested. - **Other session.** Another session is rendering `artifacts/amsterdam` in this folder at the same time. Its `pkill` killed several of these runs, which were retried. - **Review** (Fable 5.1, `facade-reviewer`, read-only): acceptance MET. Its own region measurements agree with the captures: the house by day and by night, and the wall alone when it is selected. - **Must-fix, done:** the evidence had gone stale. The Amsterdam session edited `scripts/main.gd` and `tests/test_ui.gd` at 00:12 (its starter scene, the menu order and a verify block), after the first suite run. - Those edits are **not part of this change**, and the backup diffs now include them. - Every suite that reads the changed files was rerun on the current tree afterwards (`artifacts/owner-0925/rerun2.out`): `test_ui` 325, `test_building_tools` 669, `test_masonry` 501, `test_look` 248, `test_warmup` 31 and `test_foliage_merge` 78, all passing; `test_owner_0924` 87, passing after the fix below. - **Notes taken:** - The shader has an early out. Texels (3, 0) and (3, 1) hold the world bounds of all the boxes, so a fragment outside them costs two fetches however many boxes a wall has. - The wall box half-width is now thickness/2 + 0.1 m (it was + 0.2), so an abutting house gets a thinner strip. - The dead outline slot for the selection is removed. - A comment now says the globals belong to the renderer (the last WorldView made owns the lift). - `selection_world_box` is documented as the last selection's box while hidden. - **Found in the rerun:** the texture-row test was vacuous. A headless `ImageTexture.get_image()` reads back zeros, so the row-0 check passed on an empty texture. The test now reads `selection_image`, and its row-0 and bounds checks are real. - **Frame cost** (`artifacts/owner-0925/cost/`, `probe_cost.gd`, 240 frames each, `--disable-vsync`, battery). The second round's mean frame times were 8.74 ms with nothing selected, 8.63 ms with the house and 8.79 ms with the 16-box wall; the first round was 8.77 ms (house) and 8.13 ms (wall), with a warm-up spike on "none". There is no measurable cost. - **Seen in the rerun, not from this change:** - `test_foliage` fails "tree crown is a wide mass" (0.80 to 0.83). That check measures crown geometry from `Architecture.decoration`, which the trees session is editing (`build/backup-trees/`); this change touched only the foliage shader's colour. After the trees session finished that step, `test_foliage` passes on the rerun (97 checks, 0 failures; `artifacts/owner-0925/test_foliage-2.log`). - One `--verify` failed "Holding the raise brush did not build a hill: 2.05". It passed on the next run of the same tree (`artifacts/owner-0925/verify-a.log`: `PIXELPUNK_INTEGRATION []`). - **Plan.** Tracked with T1–T3 in `docs/operation-treehouse.md`. **Owner request: the cat, arrow grips, and a resize drawn in full (24 September 2026, finished about 23:45):** Opus 5.5 implementer, Fable 5.1 reviewer. The owner asked: "Fix the cat. Also add arrows to the resize instead of the spheres. I also don't want to see the geometric shapes during resize the buildings should just animate and grow." - **Pre-state and snapshots.** - Pre-state: `build/backup-feel/16-P1-d/`. Nothing changed after P1d except `export_presets.cfg`. - Per-file backups: `build/backup-feel/*before-owner-0924*`. - Snapshots: `build/backup-feel/17-owner/` (mid-work, 21:43) and `17-owner-final/` (after the review fixes). - **Cat.** - `shaders/cat.gdshader` gains `albedo_scale = 0.62` and `fur_tint = vec3(1.0, 0.84, 0.78)`, measured by k-means against the orange tabby row of `animalartreference.png`. Trials are in `artifacts/owner-0924/cat/trial-*`, using `cat/trial.sh` and `cat/kmeans.gd`. - The trial logs do not record which uniforms each trial used. The shader comment's "trial-b" figures are the chosen values. - `shaders/cat_outline.gdshader`: the back-face outline widens as the camera closes in, to `clamp(outline_pixels*38/view_height, outline_pixels, 3)` pixels. The earlier pull toward the camera is removed; it painted dark patches on the face (`cat/pull0`). - Captures: - before: `artifacts/owner-0924/cat-before/`; - after: `cat/final/` (day and night 8x loaf, day 8x sit, day and night 1x); - sheet: `artifacts/owner-0924/cat-sheet-8x.png`, showing the reference, before, after by day, and after by night. - **Arrow grips.** In `scripts/world_view.gd`: - `show_handles` specs carry a shape and a direction. - Width, depth, height, lift, ridge and neon grips are arrows pointing the way they pull. - The rotation grip is a curved arrow (`"turn"`). - Hip grips point in along the ridge. - The free knobs stay round: wall nodes, the scissors and pipe ends. A wall's height grip is an arrow. - `arrow_mesh` builds a six-sided tube and cone with outward flat normals. It is wound clockwise from outside (Godot's front face). `handle_basis` turns +y onto the direction. - The grip material has a dark rim next pass (`HANDLE_EDGE`, unshaded, no depth test, `CULL_FRONT`, grown), so a pale grip reads on a pale wall. - `handle_pick_box`: an arrow picks from its centre cube on to its tip, and the turn grip over its arc. - `pick_handle`: among the boxes a click enters, it picks the grip whose centre passes nearest the ray. - `scripts/main.gd`: `start_resize` and `_update_selection` hide the selection box while any grip is held. Only the grips ride along. - Capture: `artifacts/owner-0924/drag/grips4/film-hold-sub01.png`. - **Resize drawn in full.** - `WorldView.sync_live` (`live_full`, on by default) rebuilds the changed clusters at full detail, not the plain "preview" massing. - It builds at most once every `LIVE_FULL_GAP_USEC` (80 ms), or twice the last build's cost if that is longer. - A step inside the gap is built when the gap ends (`live_waiting`, from `_process`). - A neighbour whose members did not change is skipped during the drag (`cluster_own`) and caught up on release. - Each build and the release carry their pieces: `BuildAnim.diff` `carry` and `record`, `schedule_new` carry, `CARRY_REACH` 1.2 m, and a one-to-one nearest match within a material. - A piece that stood before stays settled. - A piece still revealing carries on. - Only what is really new grows, with A2's reveal at `LIVE_RATE` 1.6. The release after a drag builds at the same rate. - Queued builds of a spread release keep their carry flag (`todo` item index 5, `_build_cluster(..., carrying)`). - `build_audio.gd` does not click carried pieces. - A there-and-back drag re-syncs the view, ending the carry. `live_full = false` is the old preview path. - **Harness.** - `--film-overlays` keeps the selection, ghost and grips in drag frames. - A `handle` step's `film_substeps: [n…]` saves mid-drag frames. - Both are in the README. - **Tests.** - New `tests/test_owner_0924.gd`, 61 checks, registered in `tools/test.sh` and `tools/Test.ps1`. It covers: - arrow mesh shape, normals and winding; - grip shapes and directions; - picking an arrow head and the turn arc; - the box hidden on press and while held; - the full-detail drag, its pacing and carry (a 20 cm raise grows only the new courses; growing courses carry on); - the release: its rate and carry, including queued builds; nothing rebuilt; neighbours grow nothing; - a there-and-back drag; - the animate-off path; - the cat uniforms and outline. - Wording-only edits in `tests/test_ui.gd` and `tests/test_build_anim.gd`. - Final full suite (`artifacts/owner-0924/tests/tally-final.txt`, run by `tests/full2.sh`): 37 suites, 17,765 checks, 1 failure. - The failure is the known `test_creature_reactions` "The re-plan is applied after the edits (0)", as in P1c2 and P1d. Its timing check fails on battery with Low Power Mode. - `tools/test.sh` stops at that suite, so `full2.sh` runs every suite in the same order without stopping. - After the last pick-box change, `test_ui` (325), `test_building_tools` (669) and `test_owner_0924` (61) were rerun and pass. - `--verify`: `PIXELPUNK_INTEGRATION []`. - The first post-review run failed its A1b wall body drag. The arrow's pick box then also ran back along the tail into the wall; it now reaches toward the tip only. - The pre-review run is in `tests/tally.txt` (17,753 checks, the same single failure). - **Drag cost.** Measured with `artifacts/owner-0924/drag/resize-film.json` (raise then widen Harbour's main house, 80 steps, `--animate`). The Mac was on battery with Low Power Mode, so compare only within this sitting. | step_total_ms | Median | p95 | Max | |---|---|---|---| | Before (`drag/before`, preview boxes) | 38.3 | 55.2 | 61.6 | | After, final (`drag/final3`) | 25.7 | 117.1 | 391.1 | - A full build of that house is about 43 ms (facade 24 ms), against 2 ms for the boxes. - Steps between paced builds are cheaper than before; the paced ones are dearer. - The max is the drag's first full build. The release is 25.7 ms. - **Trade-off for the owner:** smoother on average, with a hitch every ~120–170 ms while dragging a big house on this machine. - Not measured on the dense fixture or the canal quarter. A cluster there costs 100–200 ms, which would make the gap about 400 ms. - **Frames.** `artifacts/owner-0924/drag-sheet-raise.png` shows three frames: before, mid-drag; after, mid-drag; and after, 400 ms past release. - Before shows the pale massing box and round knobs. - After shows the house in full, with arrow grips, no box, and its new storey growing. - Films: `drag/before/` and `drag/final3/`, with `film-*-sub05/20/40.png` and `film-*-0/150/400/900ms.png`. - **Journey** `artifacts/feel-journey/17-owner/`: counts against 16-P1 (`counts-vs-16-P1.txt`). - `draw_calls` and `rendered_objects` are identical. - Only run-time fields differ: `audio.*`, `light_select_calls` and `pool_energy_*`. - Frames are the same apart from rain. The smoke views have no grips and no cat. - `pkg/owner-*.png` holds the cat sheet, the drag sheet and the grips. - `notes.json` has entry `17-owner` (backup `notes.json.bak-before-17-owner`). The index and digest are rebuilt. - **Review** (Fable 5.1, `facade-reviewer`, read-only): acceptance MET on the captures. - Two must-fix items, both done: - the arrow heads lay outside the old ±0.36 m pick box; - no handoff entry existed (this is it). - Four should-fix items, all done: - `carry_next` leaked past a there-and-back drag; - the arrow winding was counter-clockwise; - carried bricks slowed at release (the release now builds at `LIVE_RATE`); - the scissors drag kept the box. - Found while fixing, and fixed: - A spread release's queued builds lost the carry: the house would have re-revealed whole if the release queued it. - The height arrow over a pitched roof took the ridge knob's click (`test_ui` A1c). - The arrow's tail took a wall body press (`--verify` A1b). - Before-fix copies are in the session scratchpad; the tree before fixes is `17-owner/`. - **Open.** - A widen re-spaces windows. Pieces that jump more than 1.2 m reveal again as new, so some lit panels flash in during a widen, about 30 new pieces a step against about 90 carried (`drag/probe_carry.log.out`). - Per-material matching can briefly show a pane without its frame mid-widen (`drag/after3/film-widen-sub20.png`), until release. Matching per opening would remove both. - The build clock is held during each paced build, so other animations run at roughly 50–60% speed while dragging. - No unit test of `BuildAnim.diff` carry in isolation, and none of a merge or split mid-drag. - Windows and PC are untested. **Feel P1d Fewer foliage draw calls and a smaller first-reveal hitch (24 September 2026):** Opus 5.5 implementer. Each foliage, clutter and planting kind now draws its fullness and hue variants from one merged mesh per shape, with the variant carried as per-instance data. A start-up screen covers the drawing of every build shader variant, and the rest of the variants warm while the editor is idle. - **Files.** - New: - `shaders/grown.gdshaderinc`: `uniform bool grown`, `grown_color`, `grown_hidden`. - `scripts/shader_warmup.gd`: `ShaderWarmup`. - `tests/test_foliage_merge.gd` and `tests/test_warmup.gd`. - Changed: - `shaders/foliage.gdshader` and `shaders/plain_body.gdshaderinc`: the include plus two guarded lines each. - `scripts/architecture.gd`: `GROWN_MATERIALS`, `grown_materials`, `grown_variant_groups`, `grown_arrays` and `grown_mesh`. - `scripts/world_view.gd`: - `grown_materials` and `grown_cache`. - `set_material_parameter` also reaches the grown copies. - `_sync_decorations` uses `_grown_key` and sets the instance custom value (hue, full). - `deco_slots[id].data` records that value. - `scripts/main.gd`: the warm-up, `PIXELPUNK_WARMUP` and `PIXELPUNK_STARTUP`, and the drag harness waits for `all_done`. - `tests/test_nature.gd`, `tools/test.sh`, `tools/Test.ps1` and `README.md`: the flags `--warmup`, `--warmup=startup`, `--warmup=off` and `--startup-report`. - Merged surfaces carry CUSTOM1 as RGBA_FLOAT: dry colour as byte/255, and fullness. An RGBA8 CUSTOM1 read back only its red channel in GL Compatibility. - Save version is still 2. No params were added. - **Snapshot.** `build/backup-feel/16-P1-d/`, with work backups in `build/backup-feel/16-P1-d-work/`. - **Tests.** - Full suite (`artifacts/feel/16-P1/full-d/tally.txt`): 36 suites, 17,704 checks, 1 failure. `test_foliage_merge` passes 78 checks and `test_warmup` passes 31. - The failure is `test_creature_reactions` "The re-plan is applied after the edits (0)". It also fails on the pre-part tree in this sitting (`d/creature/pre-1.log.out`, with a second failure there from a missing reference PNG). The Mac was on battery in Low Power Mode. - `--verify` gave `PIXELPUNK_INTEGRATION []`. - Test-first logs are in `artifacts/feel/16-P1/`: - `d-testfirst-test_foliage_merge.log`: parse errors, because the API was absent. - `d-testfirst-pretree-test_foliage_merge.log`: 12 failures. - `d-testfirst-test_warmup.log`: pre-part tree, 1 failure. This test was written after the first draft of the warm-up. - **Changed expectations.** `test_nature` lines 411 and 778 expect the `_s` key in place of `_g`. - **Captures** (`artifacts/feel-journey/16-P1/pkg/`: `d-pre-*`, `d-live-*`, `d-journey-pre*`, `d-journey-live*`, `d-diff-*.png`, `d-counts-*.txt`). - Harbour day and night, quarter and dense match the pre-part tree within noise, in colour and greyscale. The diffs show only rain, water phase and plant sway. - Colour pixel share that differs (`d/imgdiff-*.txt`): | View | Same tree | Across trees | |---|---|---| | Harbour day | 0.89 to 0.99% | 0.81 to 0.96% | | Harbour night | 1.8 to 4.0% | 2.6 to 4.6% | | Quarter day | 1.8 to 2.3% | 2.1 to 2.7% | | Quarter night | 3.9 to 4.7% | 2.9 to 4.5% | | Dense | 1.5 to 1.7% | 1.5 to 2.3% | - Journey counts are in the next bullet. Every other journey count is equal. The one exception is styles-day `audio.voices` (11 or 12), which also flips within each tree (`d-styles-voices.txt`). - **Measurements.** - Journey draw_calls and rendered_objects, pre-part tree to this part: | View | draw_calls | rendered_objects | |---|---|---| | harbour-day and harbour-night | 993 to 925 | 1069 to 1001 | | harbour-lane | 813 to 755 | 889 to 831 | | quarter-day and quarter-night | 957 to 905 | 1033 to 981 | | styles-day | 520 to 496 | 596 to 572 | - Foliage MultiMeshes: Harbour 52 to 35, quarter 34 to 24, dense 6 to 6. - Frame cost (`artifacts/feel/16-P1/d/bench/table.txt`): 3 alternating vsync-off 1920x1080 smoke churn runs. - Harbour: 1008 to 936 draw calls. Median frame 14.99 to 15.51 ms. - Dense: 2125 draw calls, unchanged. Median frame 19.86 to 20.09 ms. - The run ranges overlap, so there is no measurable gain. - Start-up (`d/startup/results.txt`), time to ready: - Pre-part tree: 7260, 7147 and 7235 ms. This part: 8659, 8700 and 8677 ms. - The median is 1.44 s longer, against a cap of 1.5 s. - The start-up phase draws for about 5.86 s. The idle phase finishes about 14.7 s after launch, over 124 rounds, and its worst frame is 142 to 147 ms. - First reveal (`d/reveal/results.txt`), 3 rounds each: | Build | First house release | Worst frame of the first raise | |---|---|---| | Pre-part tree | about 1000 ms | about 473 ms | | This part, no warm-up | about 1000 ms | about 471 ms | | This part, `--warmup=startup` | about 250 ms | about 177 ms | | This part, full warm-up | 86 to 93 ms | about 177 ms | - Trees, lanterns, benches and walls showed no hitch in any build. The first terrain raise still costs a worst frame of about 177 ms in every warmed build (473 ms before); start-up-only and full warm-up give the same figure, so it is not a shader compile (added by the orchestrator from the review). - **Review** (orchestrator, 24 September 2026): Fable 5.1 review in run `wf_b6d3e304-5c7`, acceptance met, no must-fix, seven notes. Corrected above: the title and the first-raise figure. Recorded here: - **Owner trade-off.** Only the build variants warm behind the start-up screen; the other 123 entries warm in 124 idle rounds of up to 147 ms each over about nine seconds after the screen lifts, so the one 1 s reveal hitch becomes a run of 0.14 s stutters in the first 15 s of a session. The 1.5 s start-up cap forces the split (the whole set takes about 6 s). Warming the Glade look last, or only when it is switched to, would roughly halve the run. - **Start-up margin is nominal.** +1,442 ms against the 1,500 cap is within the pre-part tree's own drift in the sitting (6,678 to 6,717 ms in `d/startup/pre-S2..S4.out`, 7,147 to 7,260 in `pre-F1..F3.out`; live S runs are +1.5 to +1.7 s against the pre S runs). The pre-part figures come from `tree-pre-d-probe` in the scratchpad, whose `main.gd` adds only the `startup_report` lines. - **Captures.** The table quotes the C-series pairs. The A-series quarter-day pre capture was a transient (draw_calls 1131, light_admits 21, 75.9% of pixels from the same tree's B run) and the A-series Harbour pairs differ by 10 to 11% within and across trees (water phase); the conclusion holds (`artifacts/feel/16-P1/review/imgdiff.txt`). - **Latent fallback gap.** If `Architecture.grown_arrays` refuses a merge, `world_view.gd` about line 1895 logs an error and stores a null mesh, so every plant in that group vanishes instead of falling back to per-variant batches. Not reachable with today's generators (`test_foliage_merge` checks every kind, shape and set); a later generator change could hit it. - **Frame cost on battery.** The bench ran on battery in Low Power Mode at about twice the 12-C1 orchestrator bench frame times, so the B4 cost this part set out to recover (6.72 to 7.53 ms) is neither recovered nor measurable against its baseline here. - **Unlisted change.** `_grown_key` puts a plant's stand (afloat or plinth) in the mesh key; before, a `kind_variant_gN` group drew the stand of its first object, so a scene mixing plinthed and bare plants of one variant now draws each correctly. No journey view is affected (`d-counts-journey.txt`, 0 other fields differ). The Sonnet handoff check flagged two numbers in older entries: the P1c Harbour animations-on "before" median (91.4 ms, corrected to 92.0, the median of 92.02, 91.41 and 107.92) and the P1c2 old-state percentages (correct: they are the fraction column of `imgdiff-old-states.txt`, 0.0164 to 0.0232 and 0.1355 to 0.1420; the checker read the mean-difference column). - **Not done.** - Ivy is not merged: merging it would change the journey field `foliage.multimeshes`. - Flower-patch palette variants still get their own MultiMeshes. - Dense gets no draw-call change, and there is no measurable frame-time gain. - The idle warm-up rounds can cost about 0.15 s frames. - A house placed before the idle phase ends still costs about 250 ms. - Start-up is close to its cap. - The 104 MB of bench PNGs are left for the owner-approved clean-up. - **Platforms:** Mac run; Windows not run.