Boomtown draws each plot's family at its rank, and main's 159 looks leave #144

Owner

Tracker: #3788

Step 2 of plan #3561 (a growing building keeps its style and grows taller in place). Boomtown now draws every city building as its plot's family (D4), so a plot keeps its family and palette through every rank 1–20. Main's 159-look catalogue (RANKS) and its keys are gone, with no alias. Growth in place comes in step 3; a rank-up still re-models and rises from its base, as on main.

What it delivers

  • looks.ts: lookOf(rank, x, y) returns { rank, family, palette }, with family = hash(x, y, 11) % FAMILIES.length and palette = hash(x, y, 29) % 3. looksOf(rank) lists each family's version at that rank in family order, so a look's index at a rank is its family. RANKS leaves, and so do the helpers and tones only main's looks used (ring4, facade, pyramid, gableZ, the animals, buses, umbrellas, barrels, columns, pediments, HAY, RED and the private tones). CityLook keeps the sail and flag pivots; rotor and dish leave, since no family carries them (D2).
  • models.ts: the families' 480 models take keys 0–479, ahead of the plot slab. lookKey(rank, family, palette), lookHeight(rank, family), lookExtent(rank, family) and lookFootprint(rank, family) name and measure a family version. familyKey, familyHeight, familyExtent and familyFootprint leave.
  • bodyColor takes the model's largest body part as step 1's materials test measures it: the non-roof part of any shape at least BODY_WIDTH wide on x and z with the largest rest-box volume, cached per model. So a Round version's rubble falls back to its drum's tone, where the old largest-box rule took some other box.
  • Callers: the layer's lay and fall, src/mock/wrecks.ts and src/bench/towns.ts read look.family.
  • The Cities Yard (src/client/cities-yard.ts, mock/cities.html): its Looks section is now the families' rows, 20 columns, one row per family through ranks 1–20 (160 cells); the separate Families section is folded into it.

Tests

  • looks.test.ts:
    • The catalogue holds 160 looks, 8 at every rank, each the version of one family at that rank in family order. The 159-look count, main's rank-medians test and the per-name picks leave.
    • A plot's family and palette are a function of the plot alone: every plot of a 64 × 64 grid gives the same family and palette at every rank 1–20.
    • Two plots pinned at rank 1 and rank 20: (10, 12) draws Round 1 / Round 20 in palette 2, and (40, 2) Glass curtain 1 / Glass curtain 20 in palette 1.
    • A version's body colour is its family's wall tone at every rank and palette. With the old largest-box bodyColor in a whole-tree copy, it fails on all 60 Round versions and palettes; the control copy passes.
    • Step 1's height, ground-plan, materials and Micro-voxel roof tests stand over the catalogue, now through lookHeight, lookExtent, lookFootprint and lookKey.
  • detail.test.ts: the budget test walks every key up to the trees, which now covers every family version in every palette; its separate families loop leaves. The detail rule's water-tower legs leave with the water tower; the lamp still pins a thin tall part kept at Far.
  • collapses.test.ts: its look list is the 160 family versions.
  • life.test.ts: sails (Wind tower) and the flag (Round) pass on the families. Only Brick gable smokes, so the 512-puff cap test's two blocks are 24 × 24 with only their smoking buildings kept; two 12 × 12 blocks of families reached 163 puffs. The 256-light cap test's rank-20 crowd is 24 × 24, since a 12 × 12 block of families gave 97 lights.
  • layer.test.ts: the reference cities stay within BUDGETS with 4 / 4 / 2 calls.
  • cities.phone.ts counts { looks: 160, streets: 36, life: 8, monuments: 18, wide: 1 } in every finish and tier, with no WebGL or shader error.
  • No look name of main's 159 appears anywhere in src or mock.

Draw checks: the Map Yard bench

src/bench/map.ts, 8 arms in one map.ts 7 … call per session, 5 sessions, 840 pages, 18:14–19:21 on s32. Base e483667 (the plan branch, drawing main's looks) and head 16f393a, each a git archive tree built with vite and served by preview(). The final head d0b7087 adds only a test to looks.test.ts, so it builds the same yard. Every arm draws the default finish, Toybox, with motion.

D10, head against base (paired medians of the same session, run and view; phone estimate 7.5 ×):

growth view calls base / head triangles base / head (ratio) pooled ms base / head paired head − base
default 1× 67 / 68 343,124 / 336,400 (0.980) 1.503 / 1.507 +0.000 (phone +0.00)
default 0.5× 65 / 65 420,840 / 415,516 (0.987) 2.213 / 2.223 +0.007 (+0.05)
default 0.25× 41 / 41 524,624 / 518,984 (0.989) 2.677 / 2.683 +0.010 (+0.07)
full 1× 68 / 68 479,559–479,959 / 463,939–464,499 (0.967) 1.953 / 1.947 −0.010 (−0.08)
full 0.5× 66 / 66 521,787 / 526,887 (1.010) 2.807 / 2.823 +0.017 (+0.12)
full 0.25× 42 / 42 658,607 / 668,691 (1.015) 3.440 / 3.477 +0.020 (+0.15)
  • Per session, the paired head − base lay between −0.010 and +0.030 ms at default growth and between −0.017 and +0.033 ms at full growth.
  • D10 holds: the head draws at most the base's calls + 1, and its paired frame time is at most +0.020 ms (phone +0.15 ms) against D10's +0.05 ms.
  • The + 1 call at 1× default is life's shared Puffs mesh, drawing Brick gable's chimney smoke, as step 1 found. At 1× full main's own smoke is in view, so both arms draw 68.
  • A 1× full load's triangles change between loads of the same build by multiples of 80 (one puff), on both arms.

Step 1's figures for the families against these counts: step 1's copy, loaded with motion in Toybox, drew 343,124 / 336,400 (0.980), 420,840 / 415,516 (0.987) and 524,624 / 518,984 (0.989) at default growth, and 479,879 / 463,939 (0.967), 521,787 / 526,887 (1.010) and 658,607 / 668,691 (1.015) at full growth, with the same calls. The bench's own counts are the same numbers; its 1× full values include step 1's.

The doom peaks on the head, against their spare arms (paired medians):

arm view calls doom / spare pooled ms doom / spare paired peak
battle 1× 70 / 72 1.597 / 1.523 +0.073 (phone +0.55)
battle 0.5× 66 / 69 2.320 / 2.230 +0.080 (+0.60)
battle 0.25× 54 / 43 2.783 / 2.673 +0.110 (+0.83)
nuke 1× 88 / 81 1.720 / 1.683 +0.043 (+0.33)
nuke 0.5× 85 / 78 2.407 / 2.337 +0.083 (+0.62)
nuke 0.25× 71 / 54 2.897 / 2.783 +0.127 (+0.95)

Other runs started on s32 during the bench: 611 (18:25), 615 (18:37) and 616 (18:39), whose full gates ran 18:42–18:43 and 18:57–18:58. Each session's paired deltas stayed within the ranges above.

Renders for the Reviewer

The Map Yard overview and 1× in the four finishes, base against head, at default growth and with grown=full (32 loads, Playwright WebKit, iPhone 15 landscape, reduced motion), with no page or console error. Classic draws as on the base. Die-cast, Toybox and Micro-voxel draw the families: pagodas under team-coloured hips, round drums under team-coloured cones, glass curtains, stepped terraces, art-deco spires, brick gables and wind towers, where the base drew main's mixed looks. Viewed in this run: the Toybox overview at default growth, Toybox 1× at default and full growth, and native-resolution crops of the 1× view's city at full growth in all four finishes. The other pairs were saved but not viewed.

Towns bench

npm run towns -- --part monument --json on the base (whose rows equal main d3604f1's, as step 1 showed) and on the head, 5 rows, 5.4–5.7 s each. p5 / median / p95 over 120 harness towns:

people median building, base → head tower, base → head outside 3–4×, base → head
founding 0.21 / 0.22 / 0.24 → 0.25 / 0.25 / 0.25 1.00 / 1.00 / 1.00 → 1.00 / 1.00 / 1.00 120 → 0
30 0.22 / 1.00 / 1.04 → 0.25 / 0.98 / 0.98 1.50 / 3.50 / 3.75 → 1.25 / 3.50 / 3.50 36 → 36
90 1.25 / 1.50 / 1.52 → 1.23 / 1.51 / 1.51 4.50 / 5.25 / 5.50 → 4.50 / 5.50 / 5.50 0 → 0
180 1.43 / 1.60 / 1.71 → 1.37 / 1.51 / 1.65 5.25 / 5.75 / 6.00 → 5.00 / 5.50 / 6.00 0 → 0
330 1.38 / 1.52 / 1.69 → 1.37 / 1.51 / 1.65 5.00 / 5.50 / 6.00 → 5.00 / 5.50 / 6.00 0 → 0

The families stand at the target height for their rank, so the median building moves by at most 0.09 and the median tower by one step (0.25) either way. At founding every town's tower is now exactly 4.00× its median building, where main's rank-1 looks stood 4.17–4.76×.

Play

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

Gates

  • sak-gate fast #1 (tracker gate run 337) on 16f393a: passed, npm test 985/985 in 29.7 s.
  • sak-gate full #2 (tracker gate run 345) on d0b7087, the final head: passed, npm test 986/986 in 58.8 s and npm run test:phone 180/180 in 200.7 s.
Tracker: [#3788](https://sak.swipelab.com/tasks/3788) Step 2 of plan #3561 (a growing building keeps its style and grows taller in place). Boomtown now draws every city building as its plot's family (D4), so a plot keeps its family and palette through every rank 1–20. Main's 159-look catalogue (`RANKS`) and its keys are gone, with no alias. Growth in place comes in step 3; a rank-up still re-models and rises from its base, as on main. ## What it delivers - **looks.ts:** `lookOf(rank, x, y)` returns `{ rank, family, palette }`, with `family = hash(x, y, 11) % FAMILIES.length` and `palette = hash(x, y, 29) % 3`. `looksOf(rank)` lists each family's version at that rank in family order, so a look's index at a rank is its family. `RANKS` leaves, and so do the helpers and tones only main's looks used (`ring4`, `facade`, `pyramid`, `gableZ`, the animals, buses, umbrellas, barrels, columns, pediments, HAY, RED and the private tones). `CityLook` keeps the `sail` and `flag` pivots; `rotor` and `dish` leave, since no family carries them (D2). - **models.ts:** the families' 480 models take keys 0–479, ahead of the plot slab. `lookKey(rank, family, palette)`, `lookHeight(rank, family)`, `lookExtent(rank, family)` and `lookFootprint(rank, family)` name and measure a family version. `familyKey`, `familyHeight`, `familyExtent` and `familyFootprint` leave. - **`bodyColor`** takes the model's largest body part as step 1's materials test measures it: the non-roof part of any shape at least BODY_WIDTH wide on x and z with the largest rest-box volume, cached per model. So a Round version's rubble falls back to its drum's tone, where the old largest-box rule took some other box. - **Callers:** the layer's `lay` and `fall`, src/mock/wrecks.ts and src/bench/towns.ts read `look.family`. - **The Cities Yard** (src/client/cities-yard.ts, mock/cities.html): its Looks section is now the families' rows, 20 columns, one row per family through ranks 1–20 (160 cells); the separate Families section is folded into it. ## Tests - **looks.test.ts:** - The catalogue holds 160 looks, 8 at every rank, each the version of one family at that rank in family order. The 159-look count, main's rank-medians test and the per-name picks leave. - A plot's family and palette are a function of the plot alone: every plot of a 64 × 64 grid gives the same family and palette at every rank 1–20. - Two plots pinned at rank 1 and rank 20: (10, 12) draws Round 1 / Round 20 in palette 2, and (40, 2) Glass curtain 1 / Glass curtain 20 in palette 1. - A version's body colour is its family's wall tone at every rank and palette. With the old largest-box `bodyColor` in a whole-tree copy, it fails on all 60 Round versions and palettes; the control copy passes. - Step 1's height, ground-plan, materials and Micro-voxel roof tests stand over the catalogue, now through `lookHeight`, `lookExtent`, `lookFootprint` and `lookKey`. - **detail.test.ts:** the budget test walks every key up to the trees, which now covers every family version in every palette; its separate families loop leaves. The detail rule's water-tower legs leave with the water tower; the lamp still pins a thin tall part kept at Far. - **collapses.test.ts:** its look list is the 160 family versions. - **life.test.ts:** sails (Wind tower) and the flag (Round) pass on the families. Only Brick gable smokes, so the 512-puff cap test's two blocks are 24 × 24 with only their smoking buildings kept; two 12 × 12 blocks of families reached 163 puffs. The 256-light cap test's rank-20 crowd is 24 × 24, since a 12 × 12 block of families gave 97 lights. - **layer.test.ts:** the reference cities stay within BUDGETS with 4 / 4 / 2 calls. - **cities.phone.ts** counts `{ looks: 160, streets: 36, life: 8, monuments: 18, wide: 1 }` in every finish and tier, with no WebGL or shader error. - No look name of main's 159 appears anywhere in src or mock. ## Draw checks: the Map Yard bench src/bench/map.ts, 8 arms in one `map.ts 7 …` call per session, 5 sessions, 840 pages, 18:14–19:21 on s32. Base e483667 (the plan branch, drawing main's looks) and head 16f393a, each a `git archive` tree built with vite and served by `preview()`. The final head d0b7087 adds only a test to looks.test.ts, so it builds the same yard. Every arm draws the default finish, Toybox, with motion. **D10, head against base** (paired medians of the same session, run and view; phone estimate 7.5 ×): | growth | view | calls base / head | triangles base / head (ratio) | pooled ms base / head | paired head − base | |---|---|---|---|---|---| | default | 1× | 67 / 68 | 343,124 / 336,400 (0.980) | 1.503 / 1.507 | +0.000 (phone +0.00) | | default | 0.5× | 65 / 65 | 420,840 / 415,516 (0.987) | 2.213 / 2.223 | +0.007 (+0.05) | | default | 0.25× | 41 / 41 | 524,624 / 518,984 (0.989) | 2.677 / 2.683 | +0.010 (+0.07) | | full | 1× | 68 / 68 | 479,559–479,959 / 463,939–464,499 (0.967) | 1.953 / 1.947 | −0.010 (−0.08) | | full | 0.5× | 66 / 66 | 521,787 / 526,887 (1.010) | 2.807 / 2.823 | +0.017 (+0.12) | | full | 0.25× | 42 / 42 | 658,607 / 668,691 (1.015) | 3.440 / 3.477 | +0.020 (+0.15) | - Per session, the paired head − base lay between −0.010 and +0.030 ms at default growth and between −0.017 and +0.033 ms at full growth. - **D10 holds:** the head draws at most the base's calls + 1, and its paired frame time is at most +0.020 ms (phone +0.15 ms) against D10's +0.05 ms. - The + 1 call at 1× default is life's shared Puffs mesh, drawing Brick gable's chimney smoke, as step 1 found. At 1× full main's own smoke is in view, so both arms draw 68. - A 1× full load's triangles change between loads of the same build by multiples of 80 (one puff), on both arms. **Step 1's figures for the families against these counts:** step 1's copy, loaded with motion in Toybox, drew 343,124 / 336,400 (0.980), 420,840 / 415,516 (0.987) and 524,624 / 518,984 (0.989) at default growth, and 479,879 / 463,939 (0.967), 521,787 / 526,887 (1.010) and 658,607 / 668,691 (1.015) at full growth, with the same calls. The bench's own counts are the same numbers; its 1× full values include step 1's. **The doom peaks on the head, against their spare arms** (paired medians): | arm | view | calls doom / spare | pooled ms doom / spare | paired peak | |---|---|---|---|---| | battle | 1× | 70 / 72 | 1.597 / 1.523 | +0.073 (phone +0.55) | | battle | 0.5× | 66 / 69 | 2.320 / 2.230 | +0.080 (+0.60) | | battle | 0.25× | 54 / 43 | 2.783 / 2.673 | +0.110 (+0.83) | | nuke | 1× | 88 / 81 | 1.720 / 1.683 | +0.043 (+0.33) | | nuke | 0.5× | 85 / 78 | 2.407 / 2.337 | +0.083 (+0.62) | | nuke | 0.25× | 71 / 54 | 2.897 / 2.783 | +0.127 (+0.95) | Other runs started on s32 during the bench: 611 (18:25), 615 (18:37) and 616 (18:39), whose `full` gates ran 18:42–18:43 and 18:57–18:58. Each session's paired deltas stayed within the ranges above. ## Renders for the Reviewer The Map Yard overview and 1× in the four finishes, base against head, at default growth and with `grown=full` (32 loads, Playwright WebKit, iPhone 15 landscape, reduced motion), with no page or console error. Classic draws as on the base. Die-cast, Toybox and Micro-voxel draw the families: pagodas under team-coloured hips, round drums under team-coloured cones, glass curtains, stepped terraces, art-deco spires, brick gables and wind towers, where the base drew main's mixed looks. Viewed in this run: the Toybox overview at default growth, Toybox 1× at default and full growth, and native-resolution crops of the 1× view's city at full growth in all four finishes. The other pairs were saved but not viewed. ## Towns bench `npm run towns -- --part monument --json` on the base (whose rows equal main d3604f1's, as step 1 showed) and on the head, 5 rows, 5.4–5.7 s each. p5 / median / p95 over 120 harness towns: | people | median building, base → head | tower, base → head | outside 3–4×, base → head | |---|---|---|---| | founding | 0.21 / 0.22 / 0.24 → 0.25 / 0.25 / 0.25 | 1.00 / 1.00 / 1.00 → 1.00 / 1.00 / 1.00 | 120 → 0 | | 30 | 0.22 / 1.00 / 1.04 → 0.25 / 0.98 / 0.98 | 1.50 / 3.50 / 3.75 → 1.25 / 3.50 / 3.50 | 36 → 36 | | 90 | 1.25 / 1.50 / 1.52 → 1.23 / 1.51 / 1.51 | 4.50 / 5.25 / 5.50 → 4.50 / 5.50 / 5.50 | 0 → 0 | | 180 | 1.43 / 1.60 / 1.71 → 1.37 / 1.51 / 1.65 | 5.25 / 5.75 / 6.00 → 5.00 / 5.50 / 6.00 | 0 → 0 | | 330 | 1.38 / 1.52 / 1.69 → 1.37 / 1.51 / 1.65 | 5.00 / 5.50 / 6.00 → 5.00 / 5.50 / 6.00 | 0 → 0 | The families stand at the target height for their rank, so the median building moves by at most 0.09 and the median tower by one step (0.25) either way. At founding every town's tower is now exactly 4.00× its median building, where main's rank-1 looks stood 4.17–4.76×. ## Play The esbuild bundles of src/ai/sim.ts (a3c510e5…) and src/bench/net.ts (4ffd0007…) built from the base and from the head are byte-identical, so the 14 AI matches and `npm run bench` are not run. ## Gates - `sak-gate fast` #1 (tracker gate run 337) on 16f393a: passed, `npm test` 985/985 in 29.7 s. - `sak-gate full` #2 (tracker gate run 345) on d0b7087, the final head: passed, `npm test` 986/986 in 58.8 s and `npm run test:phone` 180/180 in 200.7 s.
lookOf(rank, x, y) returns the plot's family, hash(x, y, 11) % 8, and palette at every rank; looksOf(rank) lists each family's version at that rank. Keys, heights, extents and footprints come from the families, and bodyColor takes the largest body part at rest of any shape. The layer, the Cities Yard, the Wreck Yard, the towns bench and the tests that walk the looks migrate.
agra merged commit 3d7dd930c1 into task/3561-a-growing-building-keeps-its-style-and-g 2026-10-24 22:23:53 +03: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!144
No description provided.