A Classic rank-up that draws taller grows in place; the changelog and help record the change #148

Owner

Tracker: #3792

Step 6 of plan #3561: Classic grows a rank-up that draws taller in place by decision D9, and cross-fades it under reduced motion. changelog.md and the in-game help record the whole plan's change.

What it delivers

  • Classic's growth (src/client/render/cities.ts, remodel). A plot whose D8 model at its new rank draws taller than its old building was last drawn is drawn as its own mesh in its city's building material, while the city's merged mesh leaves its plot out. Each frame poses the mesh's y by riseScale(start, from, clock), with from the old drawn height over the new one and start the clock of its first frame, so it climbs over the same 0.68 s as a Boomtown growth. The mesh carries its plot in userData.plots, so a tap on it picks the plot. The monument's tower step counts it at its new height, and the tower swaps at once, as on main (D11). When the growth ends, its mesh leaves, the budget gets it back, and the city's mesh is rebuilt with the building at its new height.
  • Under reduced motion the same rank-up draws the old model fading out and the new fading in, each its own mesh at full height, for FADE_SECONDS. Then both leave and the city's mesh is rebuilt with the new one. The two screen doors are complementary. The direction is the new fadeIn uniform of the one city-swatch-fade program, which compiles as the kit arrives (if ( ( bayer < fadeCut ) != ( fadeIn > 0.5 ) ) discard;), so no program is added. A fallen building's reduced-motion fade uses the same program with fadeIn 0.
  • At once, as on main: a rank-up that draws no taller, a growth past the shared MeshBudget (and a cross-fade that cannot take both its meshes), and a building on an empty or ruined plot. A building that falls while it grows or fades leaves its own meshes first, then falls as on main, as the building it was growing into at its full height.
  • A rank-up during a growth grows on from the height the growth had reached, as last drawn. A re-model during a cross-fade ends the fade and goes on from the faded-in building.
  • Reading taken for D9's reduced-motion bullet: the cross-fade takes the place of the growth, so it covers the rank-ups that would grow. A rank-up that draws no taller swaps at once under reduced motion too, as it does with motion. Boomtown cross-fades every reduced-motion re-model (D7). This is a one-line change if every Classic rank-up should fade.
  • changelog.md: a "Current" bullet for the whole plan. It covers the eight families, growth in place from the old height, a new building on an empty or ruined plot rising from its base, the monument's tower growing and fading to its lower step, the reduced-motion fade, and Classic's kind rule and growth. Two older "Current" bullets that the plan made untrue are corrected: "159 building looks" becomes "eight building families", and the list of the 34 tall-rank looks and the farms, which step 2 removed, is dropped.
  • The help (src/client/menu.ts): "its buildings are replaced with denser, taller ones" becomes "each of its buildings keeps its style and grows taller where it stands".
  • src/client/cities.phone.ts: the rankup check's name now says what it covers in every finish, including Classic's growing and fading meshes. Its checks are unchanged: no WebGL or shader error, and no program added from before the rank-up to the scene's end, in every finish with and without reduced motion.

Tests

src/client/render/city-layers.test.ts, with the stand-in kit of step 5. Nine rank-2 plots rank up to rank 3. Six draw taller and three draw shorter, and the tower steps 4 → 8. With the growing six counted at their old heights it would step to 4.

  • Growth: each taller rank-up is its own mesh, outside the merged mesh, and the budget holds 6. On its first frame its drawn height equals the old building's drawn height to 1e-4. It never drops below that height. It stays below its new height on every 0.02 s frame before 0.66 s and first reaches it at 0.68 s. A downward ray at its plot hits its own mesh, and pick returns its plot. The tower's top right after the rank-up is the new step's. From RISE_SECONDS on, its own mesh is gone and the merged mesh holds the building at its new height and model. No own mesh is left, and the budget is back to 0.
  • At once: with the budget at 23 of 24, one taller rank-up grows and the others swap in at once in the merged mesh at their new heights. So do the three that draw shorter and a rank-1 building on the empty plot after them. Once the growth ends, the budget is back to 23, so its other users keep their mesh.
  • A fall while growing: 0.3 s into the growth, the growing building falls. Its growth mesh is gone, and one collapsing mesh of the model it grew into stands on its plot. The fallen block's height is that model's full drawn height. The budget holds the 5 other growths plus the collapse.
  • A rank-up during a growth: 0.3 s into the growth, the six growing plots stand 0.710 tall, on their way from 0.539 to their rank-3 height of 0.811, and the city ranks up to 4. The four whose rank-4 model draws 0.742 grow on as their own mesh, and on its first frame each stands at the height its growth had reached, to 1e-4. The two whose rank-4 model draws 0.495 swap in at once, in the merged mesh at that height.
  • Reduced motion: each taller rank-up draws two own meshes: the old model and the new, each at its full drawn height on every frame. Both materials have the city-swatch-fade key, which is among the programs compiled as the kit arrived. Both compile to the same GLSL, with the same discard line. Their fadeIn is 0 and 1, and they share the cut, which runs t / FADE_SECONDS. At each of the 16 Bayer thresholds, exactly one of the two keeps the cell. After FADE_SECONDS both are gone, their materials are disposed once, the budget is back to 0, and the merged mesh holds the new model. With the budget at 23, no cross-fade starts, the budget stays at 23, and the merged mesh holds the new models at once.
  • A rank-up during a cross-fade: 0.1 s into the fade, the six fading plots rank up, alternately to rank 4 and to rank 6. With the stand-in kit, no plot's rank-4 model draws taller than its rank-3 one (0.742 or 0.495 against 0.811), and every rank-6 model does (0.976). The four ranked to 6 fade out from the rank-3 model they were fading in to the rank-6 one. The two ranked to 4 swap in at once, in the merged mesh at their rank-4 height.
  • The rule test's drawnModels now reads a plot's own mesh through its matrix, so a plot that grows during the rule test's walk still names its model. The reduced-motion fall test checks the new discard line and fadeIn 0.

Fail before: the head's city-layers.test.ts run against the base's code (4d55866) in a whole-tree copy fails 7 of 21. Those are the 6 new tests and the fall-fade test, whose discard line changed.

Mutation checks, each from a whole-tree copy outside the checkout with one edit (diff -r shows only that edit). The control passes 21/21, and each mutant fails the test aimed at it:

mutant fails
the growth starts from the base (from: 0) the growth test, at its first-frame height (0.523 off)
the growth starts at the present's clock, not its first frame the growth test, at its first-frame height (0.015 off)
the fade-in material fades out (fadeIn: false) the reduced-motion test
fill leaves a changing plot out of the tower's heights the growth test
a change is not ended when the layout drops its plot the fall test
a cross-fade that gets only one of its two meshes keeps it the reduced-motion test
frame does not rebuild the city's mesh when a change ends the growth and reduced-motion tests
a rank-up of a growing plot starts from the full height of the model it was growing into (const height = kit.drawn[from]) the rank-up during a growth: the four that grow on swap in at once
a cross-fading plot stands at height 0 (heightOf returns 0 for a fade) the rank-up during a cross-fade: the two ranked to 4 fade too

Draw checks

The draw checks, the Node timing, the renders and the play check ran on 35c8013. The head, 91f0ebf, changes only city-layers.test.ts after it.

The growth and the cross-fade draw only while a rank-up changes, and the Map Yard's cities hold still while it benches, so the bench is not run. One load per arm and view in Classic (?finish=0), from base 4d55866 and head 35c8013 built from git archive trees and served with vite preview(). Calls and triangles are identical:

view default growth grown=full
1× 71 calls, 586,614 triangles 71 calls, 663,289 triangles
0.5× 92 calls, 652,556 triangles 95 calls, 1,051,991 triangles
0.25× 131 calls, 840,296 triangles 131 calls, 2,015,923 triangles

Node timing of CityLayer.present, with the kit's OBJs parsed from assets/ and fitted as loadKenney and loadFitted fit them. The city is grown out at 80,80 (seed 7, 160² open map, 338 growth steps to the population cap). Its last 40 growth steps that rank a building up (20–23 buildings) are each presented from the step before, 5 timed rounds after a warm-up. On the head, 27 of the 40 grow and 13 draw no taller. Three sessions on s32, at a load of about 2–4, with paired per-step medians:

session present, grown steps (base → head) present, at-once steps (base → head) head's rebuild when the growth ends
1 6.80 → 7.02 ms 6.73 → 7.18 ms 7.13 ms
2 5.65 → 6.73 ms 5.57 → 6.92 ms 6.79 ms
3 5.94 → 5.64 ms 5.91 → 5.77 ms 5.79 ms

Paired head − base per step: +0.28, +1.21 and −0.24 ms for the grown steps, and +0.44, +1.32 and −0.15 ms for the at-once ones. That is within the sessions' noise: the head's present adds only a map of the old layout's plots and two D8 walks per re-modelled plot. A growing rank-up costs one more rebuild of its city, the same as a present, 1.1 s after it. On the base, the frame after the growth's span takes 0.0002 ms.

Renders for the Reviewer

Attached to #3792. Each shows the Wreck Yard's rankup scene in Classic at &zoom=3, the watched plot ranking 9 → 10, with frames at device scale 2 cropped around the plot. Every page threw no error and added no program (data-added empty):

  • 3792-classic-rankup-motion.png: frames −0.1, 0.02, 0.1, 0.2, 0.3, 0.45, 0.6, 0.7 and 1.2 on the head. The new building's top pixel row reads 82, 79, 77, 75, 73, 71, 71 and 71 from 0.02 on: it climbs to its height by 0.6 s and holds there.
  • 3792-classic-rankup-still.png: under reduced motion, frames −0.1, 0.02, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3 and 0.6. The old building dithers out as the new one dithers in, and the new one stands alone from 0.3.
  • 3792-classic-rankup-base.png: the base's frames, for reference. The new building stands at its full height (top row 71) from 0.02.
  • 3792-classic-rankup-context.png: the head's frames −0.1, 0.02, 0.3, 0.6 and 1.2 over the whole focus, with the rank-1 container appearing at once on the empty plot and the monument.

Play

The esbuild bundles of src/ai/sim.ts and src/bench/net.ts built from the base and head trees are byte-identical, after the tree path in esbuild's module comments is normalized (sha256 beb944e8… and 78c5cbf6…). So the AI matches and npm run bench were not run. MAP_VERSION stays.

Gates

  • sak-gate fast (tracker gate run 465, run 864's #1) on 35c8013: passed, npm test 1012/1012.
  • sak-gate full (tracker gate run 466, run 864's #2) on 35c8013, clean tree: passed, npm test 1012/1012 and test:phone 182/182, the rankup check in Classic with and without reduced motion included.
  • sak-gate full (tracker gate run 479, run 892's #1) on 91f0ebf, the head with the two rank-up-during-a-change tests, clean tree: passed, npm test 1014/1014 and test:phone 182/182.
Tracker: [#3792](https://sak.swipelab.com/tasks/3792) Step 6 of plan [#3561](https://sak.swipelab.com/tasks/3561): Classic grows a rank-up that draws taller in place by decision D9, and cross-fades it under reduced motion. changelog.md and the in-game help record the whole plan's change. ## What it delivers - **Classic's growth** (src/client/render/cities.ts, `remodel`). A plot whose D8 model at its new rank draws taller than its old building was last drawn is drawn as its own mesh in its city's building material, while the city's merged mesh leaves its plot out. Each frame poses the mesh's y by `riseScale(start, from, clock)`, with `from` the old drawn height over the new one and `start` the clock of its first frame, so it climbs over the same 0.68 s as a Boomtown growth. The mesh carries its plot in `userData.plots`, so a tap on it picks the plot. The monument's tower step counts it at its new height, and the tower swaps at once, as on main (D11). When the growth ends, its mesh leaves, the budget gets it back, and the city's mesh is rebuilt with the building at its new height. - **Under reduced motion** the same rank-up draws the old model fading out and the new fading in, each its own mesh at full height, for FADE_SECONDS. Then both leave and the city's mesh is rebuilt with the new one. The two screen doors are complementary. The direction is the new `fadeIn` uniform of the one `city-swatch-fade` program, which compiles as the kit arrives (`if ( ( bayer < fadeCut ) != ( fadeIn > 0.5 ) ) discard;`), so no program is added. A fallen building's reduced-motion fade uses the same program with `fadeIn` 0. - **At once, as on main:** a rank-up that draws no taller, a growth past the shared MeshBudget (and a cross-fade that cannot take both its meshes), and a building on an empty or ruined plot. A building that falls while it grows or fades leaves its own meshes first, then falls as on main, as the building it was growing into at its full height. - **A rank-up during a growth** grows on from the height the growth had reached, as last drawn. A re-model during a cross-fade ends the fade and goes on from the faded-in building. - **Reading taken for D9's reduced-motion bullet:** the cross-fade takes the place of the growth, so it covers the rank-ups that would grow. A rank-up that draws no taller swaps at once under reduced motion too, as it does with motion. Boomtown cross-fades every reduced-motion re-model (D7). This is a one-line change if every Classic rank-up should fade. - **changelog.md:** a "Current" bullet for the whole plan. It covers the eight families, growth in place from the old height, a new building on an empty or ruined plot rising from its base, the monument's tower growing and fading to its lower step, the reduced-motion fade, and Classic's kind rule and growth. Two older "Current" bullets that the plan made untrue are corrected: "159 building looks" becomes "eight building families", and the list of the 34 tall-rank looks and the farms, which step 2 removed, is dropped. - **The help** (src/client/menu.ts): "its buildings are replaced with denser, taller ones" becomes "each of its buildings keeps its style and grows taller where it stands". - src/client/cities.phone.ts: the `rankup` check's name now says what it covers in every finish, including Classic's growing and fading meshes. Its checks are unchanged: no WebGL or shader error, and no program added from before the rank-up to the scene's end, in every finish with and without reduced motion. ## Tests src/client/render/city-layers.test.ts, with the stand-in kit of step 5. Nine rank-2 plots rank up to rank 3. Six draw taller and three draw shorter, and the tower steps 4 → 8. With the growing six counted at their old heights it would step to 4. - **Growth:** each taller rank-up is its own mesh, outside the merged mesh, and the budget holds 6. On its first frame its drawn height equals the old building's drawn height to 1e-4. It never drops below that height. It stays below its new height on every 0.02 s frame before 0.66 s and first reaches it at 0.68 s. A downward ray at its plot hits its own mesh, and `pick` returns its plot. The tower's top right after the rank-up is the new step's. From RISE_SECONDS on, its own mesh is gone and the merged mesh holds the building at its new height and model. No own mesh is left, and the budget is back to 0. - **At once:** with the budget at 23 of 24, one taller rank-up grows and the others swap in at once in the merged mesh at their new heights. So do the three that draw shorter and a rank-1 building on the empty plot after them. Once the growth ends, the budget is back to 23, so its other users keep their mesh. - **A fall while growing:** 0.3 s into the growth, the growing building falls. Its growth mesh is gone, and one collapsing mesh of the model it grew into stands on its plot. The fallen block's height is that model's full drawn height. The budget holds the 5 other growths plus the collapse. - **A rank-up during a growth:** 0.3 s into the growth, the six growing plots stand 0.710 tall, on their way from 0.539 to their rank-3 height of 0.811, and the city ranks up to 4. The four whose rank-4 model draws 0.742 grow on as their own mesh, and on its first frame each stands at the height its growth had reached, to 1e-4. The two whose rank-4 model draws 0.495 swap in at once, in the merged mesh at that height. - **Reduced motion:** each taller rank-up draws two own meshes: the old model and the new, each at its full drawn height on every frame. Both materials have the `city-swatch-fade` key, which is among the programs compiled as the kit arrived. Both compile to the same GLSL, with the same discard line. Their `fadeIn` is 0 and 1, and they share the cut, which runs t / FADE_SECONDS. At each of the 16 Bayer thresholds, exactly one of the two keeps the cell. After FADE_SECONDS both are gone, their materials are disposed once, the budget is back to 0, and the merged mesh holds the new model. With the budget at 23, no cross-fade starts, the budget stays at 23, and the merged mesh holds the new models at once. - **A rank-up during a cross-fade:** 0.1 s into the fade, the six fading plots rank up, alternately to rank 4 and to rank 6. With the stand-in kit, no plot's rank-4 model draws taller than its rank-3 one (0.742 or 0.495 against 0.811), and every rank-6 model does (0.976). The four ranked to 6 fade out from the rank-3 model they were fading in to the rank-6 one. The two ranked to 4 swap in at once, in the merged mesh at their rank-4 height. - The rule test's `drawnModels` now reads a plot's own mesh through its matrix, so a plot that grows during the rule test's walk still names its model. The reduced-motion fall test checks the new discard line and `fadeIn` 0. **Fail before:** the head's city-layers.test.ts run against the base's code (4d55866) in a whole-tree copy fails 7 of 21. Those are the 6 new tests and the fall-fade test, whose discard line changed. **Mutation checks**, each from a whole-tree copy outside the checkout with one edit (`diff -r` shows only that edit). The control passes 21/21, and each mutant fails the test aimed at it: | mutant | fails | |---|---| | the growth starts from the base (`from: 0`) | the growth test, at its first-frame height (0.523 off) | | the growth starts at the present's clock, not its first frame | the growth test, at its first-frame height (0.015 off) | | the fade-in material fades out (`fadeIn: false`) | the reduced-motion test | | `fill` leaves a changing plot out of the tower's heights | the growth test | | a change is not ended when the layout drops its plot | the fall test | | a cross-fade that gets only one of its two meshes keeps it | the reduced-motion test | | `frame` does not rebuild the city's mesh when a change ends | the growth and reduced-motion tests | | a rank-up of a growing plot starts from the full height of the model it was growing into (`const height = kit.drawn[from]`) | the rank-up during a growth: the four that grow on swap in at once | | a cross-fading plot stands at height 0 (`heightOf` returns 0 for a fade) | the rank-up during a cross-fade: the two ranked to 4 fade too | ## Draw checks The draw checks, the Node timing, the renders and the play check ran on 35c8013. The head, 91f0ebf, changes only city-layers.test.ts after it. The growth and the cross-fade draw only while a rank-up changes, and the Map Yard's cities hold still while it benches, so the bench is not run. One load per arm and view in Classic (`?finish=0`), from base 4d55866 and head 35c8013 built from `git archive` trees and served with vite `preview()`. Calls and triangles are identical: | view | default growth | `grown=full` | |---|---|---| | 1× | 71 calls, 586,614 triangles | 71 calls, 663,289 triangles | | 0.5× | 92 calls, 652,556 triangles | 95 calls, 1,051,991 triangles | | 0.25× | 131 calls, 840,296 triangles | 131 calls, 2,015,923 triangles | **Node timing** of `CityLayer.present`, with the kit's OBJs parsed from assets/ and fitted as `loadKenney` and `loadFitted` fit them. The city is grown out at 80,80 (seed 7, 160² open map, 338 growth steps to the population cap). Its last 40 growth steps that rank a building up (20–23 buildings) are each presented from the step before, 5 timed rounds after a warm-up. On the head, 27 of the 40 grow and 13 draw no taller. Three sessions on s32, at a load of about 2–4, with paired per-step medians: | session | present, grown steps (base → head) | present, at-once steps (base → head) | head's rebuild when the growth ends | |---|---|---|---| | 1 | 6.80 → 7.02 ms | 6.73 → 7.18 ms | 7.13 ms | | 2 | 5.65 → 6.73 ms | 5.57 → 6.92 ms | 6.79 ms | | 3 | 5.94 → 5.64 ms | 5.91 → 5.77 ms | 5.79 ms | Paired head − base per step: +0.28, +1.21 and −0.24 ms for the grown steps, and +0.44, +1.32 and −0.15 ms for the at-once ones. That is within the sessions' noise: the head's present adds only a map of the old layout's plots and two D8 walks per re-modelled plot. A growing rank-up costs one more rebuild of its city, the same as a present, 1.1 s after it. On the base, the frame after the growth's span takes 0.0002 ms. ## Renders for the Reviewer Attached to #3792. Each shows the Wreck Yard's `rankup` scene in Classic at `&zoom=3`, the watched plot ranking 9 → 10, with frames at device scale 2 cropped around the plot. Every page threw no error and added no program (`data-added` empty): - `3792-classic-rankup-motion.png`: frames −0.1, 0.02, 0.1, 0.2, 0.3, 0.45, 0.6, 0.7 and 1.2 on the head. The new building's top pixel row reads 82, 79, 77, 75, 73, 71, 71 and 71 from 0.02 on: it climbs to its height by 0.6 s and holds there. - `3792-classic-rankup-still.png`: under reduced motion, frames −0.1, 0.02, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3 and 0.6. The old building dithers out as the new one dithers in, and the new one stands alone from 0.3. - `3792-classic-rankup-base.png`: the base's frames, for reference. The new building stands at its full height (top row 71) from 0.02. - `3792-classic-rankup-context.png`: the head's frames −0.1, 0.02, 0.3, 0.6 and 1.2 over the whole focus, with the rank-1 container appearing at once on the empty plot and the monument. ## Play The esbuild bundles of src/ai/sim.ts and src/bench/net.ts built from the base and head trees are byte-identical, after the tree path in esbuild's module comments is normalized (sha256 beb944e8… and 78c5cbf6…). So the AI matches and `npm run bench` were not run. MAP_VERSION stays. ## Gates - `sak-gate fast` (tracker gate run 465, run 864's #1) on 35c8013: passed, `npm test` 1012/1012. - `sak-gate full` (tracker gate run 466, run 864's #2) on 35c8013, clean tree: passed, `npm test` 1012/1012 and `test:phone` 182/182, the `rankup` check in Classic with and without reduced motion included. - `sak-gate full` (tracker gate run 479, run 892's #1) on 91f0ebf, the head with the two rank-up-during-a-change tests, clean tree: passed, `npm test` 1014/1014 and `test:phone` 182/182.
agra merged commit 39ab4ab33c into task/3561-a-growing-building-keeps-its-style-and-g 2026-10-25 18:02:15 +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!148
No description provided.