A Boomtown model swaps in place through a cross-fade under reduced motion and on a tower's step down #146

Owner

Tracker: #3790

Step 4 of plan #3561. Under reduced motion every re-model of a drawn Boomtown building or monument now swaps in place by a cross-fade over FADE_SECONDS (0.3 s) through the fade program. With motion, a re-model to a model no taller than the version that stands takes the same cross-fade, which is a monument tower's step down (D7, D11). The version that fades in becomes the placement's remembered version at full height, so the step up that follows a step down grows from the lower tower (D6).

What changes

  • The layer (src/client/render/boomtown/layer.ts):
    • The cross-fade. A city keeps its placements' cross-fades (swaps). When a building or monument is re-modelled in a batch its city draws in view, its old model's instances stay in that batch and fade out by a group slot drawing every key before and none after, as the reduced-motion fall fades, with no rubble. Its new model's instances fade in by a second slot, drawing none before and every key after, so the two screen doors are complementary. Both slots start on the cross-fade's first frame. The batches draw with the fade program meanwhile. After FADE_SECONDS the old instances leave, the new ones draw by the city's group slot again, and both slots are released.
    • Which re-models cross-fade. Under reduced motion, every building's or monument's re-model in a batch drawn in view (a rank-up, a tower's step either way). With motion, one to a model no taller than the height the remembered version stands at by then (modelHeight(old) × riseScale); no rank-up makes one, but a tower's step down does. Every other re-model grows as in step 3. Street pieces still swap at once.
    • What the placement remembers. The new version stands at once and is remembered at full height (start clock − RISE_SECONDS, from 0), so a re-model that follows, during the fade or after it, grows or fades from it.
    • A re-model during a cross-fade ends that fade at once: the old instances leave, the new ones return to the group slot, and both slots are released. The re-model then goes on from the new version.
    • A re-model the city does not draw in view stands at once when the city next draws, with no cage, where it would have cross-faded. Under reduced motion this is begin's stand-at-once, as before. With motion, beginRises stands a placement whose model is no taller than its remembered version's height at once instead of rising it from its base. Both are remembered at full height.
    • A tier step its city starts during a cross-fade reaches the new version once the cross-fade ends, because the new version draws by its own slot until then. The old instances are shown, hidden, removed and forgotten with their batch like every other instance of the city. A placement that falls or leaves during a cross-fade ends it first.
    • growFrom becomes reachedBy, the height the remembered version stands at, which place and beginRises compare with the new model's height.
  • The Wreck Yard (src/mock/wrecks.ts):
    • Growth record. The Yard records the held city's layout before its growth and after each growth step, on until growth stops. The city itself still stands at the last layout under the population of 260, as before, so every other scene is unchanged.
    • The tower scene. It takes the record's last growth step that steps the monument's tower up (&step=up, the default) or down (&step=down), by Boomtown's body heights and towerStep, and throws when the record holds none. It stages the held city at the layout before that step, before the host's first push, and clears the trees of the layout after it. At the death's time its cue installs the layout after that step and syncs the game's blocks, as the rankup cue does. It focuses on the monument and publishes data-tower, { before, after }.
    • The steps on the yard's seed (11, 96²), on the families:
      • Up: growth step 204, 18 → 20, within the Yard's growth.
      • Down: growth step 277, 20 → 18, as a 19th building joins the city, past the population of 260. The record runs past the population cap because the held city never steps down below it on the families: its tower steps only up through growth step 249, where the Yard stops (main's looks stepped 17 → 15 at step 80). Without the longer record the step=down scene would throw.

Tests

  • layer.test.ts, "Boomtown cross-fades":
    • Reduced-motion rank-up at Full: at 0, 0.05, 0.15 and 0.25 s the old and the new version both draw, each by its own slot (neither the group's), with the batch's mesh on its fade material. By the test's keeps helper, exactly one of the two keeps each of the 16 Bayer cells, and the old keeps 16 − round(16 t / 0.3). Both stand at scale 1, and no cage stands over the plot. At FADE_SECONDS the old instance is gone, the new one is the placement's only instance, draws by its group's slot through the plain program at its full height, and both fade slots are among the released ones.
    • Reduced-motion tier step during the swap: a Full→Mid tier step started 0.1 s into the swap leaves the new Full version on its own slot at 0.2 s. At the swap's end it draws by the group's slot, fading out with the tier step, while Mid draws the new version by the same slot, fading in.
    • Reduced-motion tower step up at Mid cross-fades the same way, both towers at scale 1 with no cage over the monument, and the old tower leaves after FADE_SECONDS.
    • Reduced-motion rank-up in a city that draws no tier: made while the city is out of view, it draws at once at scale 1 when the city is next in view, as the placement's only instance, by its group's slot, with no cage.
    • Tower's step down with motion at Mid. The city grows from grown(16) until stepOf falls (bound 20 growth steps) and then until it rises (bound 10), and the test fails if either step does not come. On the families the city steps 12 → 10 at growth step 26 and 10 → 12 at step 29, the same steps as with main's looks at their target heights.
      • The step down: the taller tower fades out and the lower one in over FADE_SECONDS, complementary, both at scale 1 with no cage over the monument (the growth step's new rank-1 building raises its own cage on its plot, so the check reads the monument's tile). Then the taller tower's instance is gone, the lower draws by its group's slot at towerHeight(10), and both slots are released.
      • The step up that follows, presented once the fade has ended: on the new tower's first frame at Mid it draws at towerHeight(10) to 1e-4 tiles, with a cage. It never drops below that height, and climbs to towerHeight(12) at 0.68 s.
      • A step up presented 0.1 s into the step down's fade: the fade ends at once, with the taller tower's instance gone, the lower back on its group's slot and both slots released. The new tower grows from towerHeight(10) to 1e-4 tiles, with a cage, as the placement's only instance.
  • cities.phone.ts plays the tower scene with &step=up and &step=down in every finish, with and without reduced motion (16 loads, 36 s), beside step 3's rankup check. data-tower moves as the scene names, and no load shows a WebGL or shader error or adds a program from before the cue to the scene's end.
  • Mutations, each in a whole-tree copy of the head, on layer.test.ts and life.test.ts (control 58/58):
    • The fade-in slot claimed the same way as the fade-out (every key before, none after) fails 3 tests at their 0 s complementary check: the reduced-motion rank-up, the reduced-motion tower step and the step down.
    • A step down rising from its base, step 3's rule (no cross-fade with motion, and beginRises rising a no-taller re-model from its base with a cage), fails the step-down test (no fade program at 0 s) and the step-up-during-fade test.
    • A cross-fade that leaves the placement's remembered version unchanged fails the step up that follows (it cross-fades instead of growing, with no cage) and the step-up-during-fade test.

Renders for the Reviewer

Wreck Yard pages from a vite build of the head, 520 × 360 per frame. Each listed page drew with no page error and added no program. Each is attached to #3790 as a strip of its frames (files 713–736), with crops around the watched tower.

  • ?mode=frames&scene=rankup&frames=-0.1,0.05,0.1,0.15,0.2,0.3,0.6&zoom=3, reduced motion (files 713, 714, 716, 717, crop 715):
    • In Die-cast, Toybox and Micro-voxel the watched Round building (rank 9 → 10) dithers from the old version to the new over 0.1–0.2 s, and stands at the new by 0.3 s, with no shrink, rise or cage.
    • Adding the rank-1 building lowers the city's median, so the monument's tower steps down too and dithers the same way.
    • The rank-1 building on the emptied plot stands at once, as a new placement does under reduced motion.
    • Classic swaps at once at 0.05 s.
  • ?mode=frames&scene=tower&step=up&frames=-0.1,0.02,0.2,0.45,0.7,1.2&zoom=2, with motion, data-tower 18 → 20 (files 731–735): in the three Boomtown finishes the new tower stands at the old height under its cage on the 0.02 frame and climbs to its own by 0.7 s, the growth of step 3. Classic swaps at once.
  • ?mode=frames&scene=tower&step=down&frames=-0.1,0.05,0.1,0.15,0.2,0.3,0.6&zoom=2, data-tower 20 → 18, with motion (files 718–721, 727–730, 736) and under reduced motion (files 722–726): in the three Boomtown finishes the taller crown dithers out over 0.1–0.2 s and the lower tower stands from 0.3 s, at full height throughout, with no cage. Classic swaps at once at 0.05 s.

Draw checks

The swap draws only during a re-model, so the game draws as before at rest; the bench was not run for that reason. One load per arm, view and finish of the Map Yard (?bench&view=…&finish=…[&grown=full], WebKit, iPhone 15 landscape), base 776e4b6 against head 1bf3464, both built from git archive trees:

  • Under reduced motion, 48 loads: all 24 (finish, growth, view) pairs drew identical calls and triangles, for example Toybox 67 / 65 / 41 calls with 334,072 / 415,516 / 518,984 triangles at rest and 67 / 66 / 42 calls with 441,775 / 527,043 / 668,691 full. Classic: 71 / 92 / 131 calls at rest and 71 / 95 / 131 full.
  • With motion, as the bench loads, 48 loads: 23 of 24 pairs identical, for example Toybox 68 / 65 / 41 calls with 336,400 / 415,516 / 518,984 triangles at rest and 68 / 66 / 42 with 463,939 / 526,887 / 668,691 full, matching step 3's figures. Micro-voxel 1× full drew 514,703 on base and 514,383 on head, with the same 68 calls. Four more loads of that view gave base 514,143 ×4 and head 514,303, 514,143 ×2 and 514,383: life's smoke puffs (80 triangles each) vary between loads, and both arms draw 514,143.

Play

The esbuild bundles of src/ai/sim.ts and src/bench/net.ts from the base 776e4b6 and the head are byte-identical (sim a3c510e5…, net 4ffd0007…), so the 14 AI matches and npm run bench were not run.

Review

  • a0f3263 drops the placement's grows flag. A cross-fade now begins for any re-model that has not risen, tested by placement.rise !== null alone, because only a building or a monument is ever re-modelled unrisen. Every other placement is put built, and a rider is made risen. The flag never decided the branch, so the renders, draw checks and bundles above, made at 1bf3464, still hold.

Gates

  • sak-gate fast (tracker gate run 407) on 1bf3464: passed, npm test 1005/1005.
  • sak-gate full (tracker gate run 408) on 1bf3464: passed, npm test 1005/1005 and test:phone 182/182, the tower phone check included.
  • sak-gate fast (tracker gate run 416) on a0f3263: passed, npm test 1005/1005.
  • sak-gate full (tracker gate run 417) on a0f3263: passed, npm test 1005/1005 and test:phone 182/182.

changelog.md and the in-game help are step 6's, for the whole plan.

Tracker: [#3790](https://sak.swipelab.com/tasks/3790) Step 4 of plan [#3561](https://sak.swipelab.com/tasks/3561). Under reduced motion every re-model of a drawn Boomtown building or monument now swaps in place by a cross-fade over FADE_SECONDS (0.3 s) through the fade program. With motion, a re-model to a model no taller than the version that stands takes the same cross-fade, which is a monument tower's step down (D7, D11). The version that fades in becomes the placement's remembered version at full height, so the step up that follows a step down grows from the lower tower (D6). ## What changes - **The layer** (src/client/render/boomtown/layer.ts): - **The cross-fade.** A city keeps its placements' cross-fades (`swaps`). When a building or monument is re-modelled in a batch its city draws in view, its old model's instances stay in that batch and fade out by a group slot drawing every key before and none after, as the reduced-motion fall fades, with no rubble. Its new model's instances fade in by a second slot, drawing none before and every key after, so the two screen doors are complementary. Both slots start on the cross-fade's first frame. The batches draw with the fade program meanwhile. After FADE_SECONDS the old instances leave, the new ones draw by the city's group slot again, and both slots are released. - **Which re-models cross-fade.** Under reduced motion, every building's or monument's re-model in a batch drawn in view (a rank-up, a tower's step either way). With motion, one to a model no taller than the height the remembered version stands at by then (`modelHeight(old) × riseScale`); no rank-up makes one, but a tower's step down does. Every other re-model grows as in step 3. Street pieces still swap at once. - **What the placement remembers.** The new version stands at once and is remembered at full height (start `clock − RISE_SECONDS`, `from` 0), so a re-model that follows, during the fade or after it, grows or fades from it. - **A re-model during a cross-fade** ends that fade at once: the old instances leave, the new ones return to the group slot, and both slots are released. The re-model then goes on from the new version. - **A re-model the city does not draw** in view stands at once when the city next draws, with no cage, where it would have cross-faded. Under reduced motion this is `begin`'s stand-at-once, as before. With motion, `beginRises` stands a placement whose model is no taller than its remembered version's height at once instead of rising it from its base. Both are remembered at full height. - A tier step its city starts during a cross-fade reaches the new version once the cross-fade ends, because the new version draws by its own slot until then. The old instances are shown, hidden, removed and forgotten with their batch like every other instance of the city. A placement that falls or leaves during a cross-fade ends it first. - `growFrom` becomes `reachedBy`, the height the remembered version stands at, which `place` and `beginRises` compare with the new model's height. - **The Wreck Yard** (src/mock/wrecks.ts): - **Growth record.** The Yard records the held city's layout before its growth and after each growth step, on until growth stops. The city itself still stands at the last layout under the population of 260, as before, so every other scene is unchanged. - **The `tower` scene.** It takes the record's last growth step that steps the monument's tower up (`&step=up`, the default) or down (`&step=down`), by Boomtown's body heights and `towerStep`, and throws when the record holds none. It stages the held city at the layout before that step, before the host's first push, and clears the trees of the layout after it. At the death's time its cue installs the layout after that step and syncs the game's blocks, as the `rankup` cue does. It focuses on the monument and publishes `data-tower`, `{ before, after }`. - **The steps on the yard's seed (11, 96²), on the families:** - Up: growth step 204, 18 → 20, within the Yard's growth. - Down: growth step 277, 20 → 18, as a 19th building joins the city, past the population of 260. The record runs past the population cap because the held city never steps down below it on the families: its tower steps only up through growth step 249, where the Yard stops (main's looks stepped 17 → 15 at step 80). Without the longer record the `step=down` scene would throw. ## Tests - **layer.test.ts, "Boomtown cross-fades":** - **Reduced-motion rank-up at Full:** at 0, 0.05, 0.15 and 0.25 s the old and the new version both draw, each by its own slot (neither the group's), with the batch's mesh on its fade material. By the test's `keeps` helper, exactly one of the two keeps each of the 16 Bayer cells, and the old keeps 16 − round(16 t / 0.3). Both stand at scale 1, and no cage stands over the plot. At FADE_SECONDS the old instance is gone, the new one is the placement's only instance, draws by its group's slot through the plain program at its full height, and both fade slots are among the released ones. - **Reduced-motion tier step during the swap:** a Full→Mid tier step started 0.1 s into the swap leaves the new Full version on its own slot at 0.2 s. At the swap's end it draws by the group's slot, fading out with the tier step, while Mid draws the new version by the same slot, fading in. - **Reduced-motion tower step up at Mid** cross-fades the same way, both towers at scale 1 with no cage over the monument, and the old tower leaves after FADE_SECONDS. - **Reduced-motion rank-up in a city that draws no tier:** made while the city is out of view, it draws at once at scale 1 when the city is next in view, as the placement's only instance, by its group's slot, with no cage. - **Tower's step down with motion at Mid.** The city grows from `grown(16)` until `stepOf` falls (bound 20 growth steps) and then until it rises (bound 10), and the test fails if either step does not come. On the families the city steps 12 → 10 at growth step 26 and 10 → 12 at step 29, the same steps as with main's looks at their target heights. - **The step down:** the taller tower fades out and the lower one in over FADE_SECONDS, complementary, both at scale 1 with no cage over the monument (the growth step's new rank-1 building raises its own cage on its plot, so the check reads the monument's tile). Then the taller tower's instance is gone, the lower draws by its group's slot at `towerHeight(10)`, and both slots are released. - **The step up that follows,** presented once the fade has ended: on the new tower's first frame at Mid it draws at `towerHeight(10)` to 1e-4 tiles, with a cage. It never drops below that height, and climbs to `towerHeight(12)` at 0.68 s. - **A step up presented 0.1 s into the step down's fade:** the fade ends at once, with the taller tower's instance gone, the lower back on its group's slot and both slots released. The new tower grows from `towerHeight(10)` to 1e-4 tiles, with a cage, as the placement's only instance. - **cities.phone.ts** plays the `tower` scene with `&step=up` and `&step=down` in every finish, with and without reduced motion (16 loads, 36 s), beside step 3's `rankup` check. `data-tower` moves as the scene names, and no load shows a WebGL or shader error or adds a program from before the cue to the scene's end. - **Mutations,** each in a whole-tree copy of the head, on layer.test.ts and life.test.ts (control 58/58): - The fade-in slot claimed the same way as the fade-out (every key before, none after) fails 3 tests at their 0 s complementary check: the reduced-motion rank-up, the reduced-motion tower step and the step down. - A step down rising from its base, step 3's rule (no cross-fade with motion, and `beginRises` rising a no-taller re-model from its base with a cage), fails the step-down test (no fade program at 0 s) and the step-up-during-fade test. - A cross-fade that leaves the placement's remembered version unchanged fails the step up that follows (it cross-fades instead of growing, with no cage) and the step-up-during-fade test. ## Renders for the Reviewer Wreck Yard pages from a vite build of the head, 520 × 360 per frame. Each listed page drew with no page error and added no program. Each is attached to #3790 as a strip of its frames (files 713–736), with crops around the watched tower. - **`?mode=frames&scene=rankup&frames=-0.1,0.05,0.1,0.15,0.2,0.3,0.6&zoom=3`, reduced motion** (files 713, 714, 716, 717, crop 715): - In Die-cast, Toybox and Micro-voxel the watched Round building (rank 9 → 10) dithers from the old version to the new over 0.1–0.2 s, and stands at the new by 0.3 s, with no shrink, rise or cage. - Adding the rank-1 building lowers the city's median, so the monument's tower steps down too and dithers the same way. - The rank-1 building on the emptied plot stands at once, as a new placement does under reduced motion. - Classic swaps at once at 0.05 s. - **`?mode=frames&scene=tower&step=up&frames=-0.1,0.02,0.2,0.45,0.7,1.2&zoom=2`, with motion,** `data-tower` 18 → 20 (files 731–735): in the three Boomtown finishes the new tower stands at the old height under its cage on the 0.02 frame and climbs to its own by 0.7 s, the growth of step 3. Classic swaps at once. - **`?mode=frames&scene=tower&step=down&frames=-0.1,0.05,0.1,0.15,0.2,0.3,0.6&zoom=2`,** `data-tower` 20 → 18, with motion (files 718–721, 727–730, 736) and under reduced motion (files 722–726): in the three Boomtown finishes the taller crown dithers out over 0.1–0.2 s and the lower tower stands from 0.3 s, at full height throughout, with no cage. Classic swaps at once at 0.05 s. ## Draw checks The swap draws only during a re-model, so the game draws as before at rest; the bench was not run for that reason. One load per arm, view and finish of the Map Yard (`?bench&view=…&finish=…[&grown=full]`, WebKit, iPhone 15 landscape), base 776e4b6 against head 1bf3464, both built from `git archive` trees: - **Under reduced motion,** 48 loads: all 24 (finish, growth, view) pairs drew identical calls and triangles, for example Toybox 67 / 65 / 41 calls with 334,072 / 415,516 / 518,984 triangles at rest and 67 / 66 / 42 calls with 441,775 / 527,043 / 668,691 full. Classic: 71 / 92 / 131 calls at rest and 71 / 95 / 131 full. - **With motion, as the bench loads,** 48 loads: 23 of 24 pairs identical, for example Toybox 68 / 65 / 41 calls with 336,400 / 415,516 / 518,984 triangles at rest and 68 / 66 / 42 with 463,939 / 526,887 / 668,691 full, matching step 3's figures. Micro-voxel 1× full drew 514,703 on base and 514,383 on head, with the same 68 calls. Four more loads of that view gave base 514,143 ×4 and head 514,303, 514,143 ×2 and 514,383: life's smoke puffs (80 triangles each) vary between loads, and both arms draw 514,143. ## Play The esbuild bundles of src/ai/sim.ts and src/bench/net.ts from the base 776e4b6 and the head are byte-identical (sim a3c510e5…, net 4ffd0007…), so the 14 AI matches and `npm run bench` were not run. ## Review - a0f3263 drops the placement's `grows` flag. A cross-fade now begins for any re-model that has not risen, tested by `placement.rise !== null` alone, because only a building or a monument is ever re-modelled unrisen. Every other placement is put built, and a rider is made risen. The flag never decided the branch, so the renders, draw checks and bundles above, made at 1bf3464, still hold. ## Gates - `sak-gate fast` (tracker gate run 407) on 1bf3464: passed, `npm test` 1005/1005. - `sak-gate full` (tracker gate run 408) on 1bf3464: passed, `npm test` 1005/1005 and `test:phone` 182/182, the `tower` phone check included. - `sak-gate fast` (tracker gate run 416) on a0f3263: passed, `npm test` 1005/1005. - `sak-gate full` (tracker gate run 417) on a0f3263: passed, `npm test` 1005/1005 and `test:phone` 182/182. changelog.md and the in-game help are step 6's, for the whole plan.
agra merged commit 9d318ec539 into task/3561-a-growing-building-keeps-its-style-and-g 2026-10-25 07:04:00 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
agra/mini-alert!146
No description provided.