Eight building families, each with a version at every rank on one ground plan, shown in the Cities Yard #143

Owner

Tracker: #3787

Step 1 of plan #3561 (a growing building keeps its style and grows taller in place). It adds the eight Boomtown building families of D2, written by D3, beside main's 159 looks. The game keeps drawing main's looks: lookOf does not change, so nothing the game draws changes in this step. The changelog and the help come in step 6, which writes them for the whole plan.

What it delivers

  • FAMILIES (src/client/render/boomtown/looks.ts): each family is a name, its wall tone in each palette, its roof shape and its version at each rank 1–20 as a CityLook. A version's parts are built once per palette on first use, from looks.ts's own helpers (gable, bands, tree, blades, look) and palettes (BRICK, PLASTER, GLASS, STONE, CONC).
    • Every version's body height is the target 0.25 + 2.05 × ((rank − 1) / 19)^1.6 itself, so each stands 1.074–1.175× its version below.
    • A family keeps one ground plan: the same base block or drum at every rank, and the same ground dressing (path, hedge, tree, bed, sidewalk). It grows by taller walls under the same roof, with more storeys, tiers, balconies and rooftop dressing as it climbs.
    • Windows are strips the height of each wall, a fixed count per face, so a taller version keeps its windows and a growth that scales y keeps them in proportion.
  • The families:
    1. Brick gable: brick walls, a slate gable that steepens over ranks 1–5, a smoking chimney (two from rank 7), stone string courses every five floors.
    2. Plaster balconies: plaster walls, a balcony slab in the team colour on every floor above the first, a flat roof with planters from rank 3 and a water tank from rank 5, a front patio.
    3. Glass curtain: glass walls with metal bands and flat roofs, one setback from rank 8 and two from rank 13, rooftop plant from rank 4, a mast with a beacon from rank 12.
    4. Round: a stone or white drum under a cone in the team colour, a flag on its tip at every rank, stone rings every five floors.
    5. Stepped terraces: concrete tiers that shrink as they rise, each terrace planted, one more tier every four ranks (2 at rank 1, 6 from rank 17), two beacons from rank 12.
    6. Pagoda: timber storeys on a stone plinth, each under a straight eave and a hipped skirt in the team colour, one more storey every three ranks (7 at ranks 19–20), a gold finial. The eave is the roof's full width and the hip stands 0.02 inside its rim. So Micro-voxel stamps every roof plot-wide at every rank, whatever height a storey ends at, and Toybox inks each roof's edge as a line.
    7. Wind tower: a pale block with a flat roof, solar panels and a rooftop turbine whose three sails turn at every rank, green wall strips from rank 6.
    8. Art deco: stone with front piers and a two-step crown under a gold roof, a neon marquee in the team colour at its street front at every rank, a gold spire with a beacon from rank 12.
  • Life (by each model's emitters at Mid and its mounts): smoke on Brick gable at every rank (two chimneys from rank 7); beacons on Glass curtain (one), Stepped terraces (two) and Art deco (one) from rank 12; neon on Art deco at every rank; sails (Mount.Sail, pivot sail) on Wind tower and a flag (Mount.Flag, pivot flag) on Round at every rank. Plaster balconies and Pagoda carry no life. No family carries a rotor or a dish.
  • Model keys and measures (src/client/render/boomtown/models.ts): familyKey(family, rank, palette) names each version and palette, after the trees, so every existing key keeps its number. They build through the same modelOf / cityGeometry path as the looks. familyHeight, familyExtent and familyFootprint give a version's body height, body extent (the x/z bounds of its parts at least BODY_WIDTH wide, on their rest geometry) and whole footprint, each measured once. bodyHeight and the new bodyExtent share restBox and bodyWide.
  • The Cities Yard (src/client/cities-yard.ts, mock/cities.html): a Families section of 20 columns, one row per family through ranks 1–20, each cell labelled with its version's name ("Brick gable 7"). The palette buttons rebuild it as they do the looks.

Tests

  • looks.test.ts, Boomtown families:
    • 8 families with unique names, each with a version at every rank 1–20.
    • Each version's body height lies within 20% of the target and is at least 1.05× its version below's.
    • Each version's body extent and whole footprint equal the rank-1 version's within 0.02 tiles, and every part of the rank-1 version whose rest box lies wholly outside its body extent stands in each version with the same box within 0.005 tiles.
    • At every rank and palette, the largest body part (the non-roof part, of any shape, at least BODY_WIDTH wide on x and z, with the largest rest-box volume) wears the family's wall tone, and every Role.Roof part has the family's roof shape.
    • In Micro-voxel at Full and at Mid, each version's team-coloured cubes (the paint attribute) reach within one cube of its version below's on every x/z bound, wherever that version draws any.
      • At 262e376, before the eaves, this case failed on Pagoda alone: Pagoda 2, 5 and 18 at Full, and Pagoda 2 and 3 at Mid.
      • It passes at the head. Pagoda's painted reach is now 0.35 at Mid and 0.38 at Full at every rank, where it went 0.35 / 0.07 / 0.28 / 0.28 / 0.35 … at Mid before.
      • Round's cone still moves by one cube between some ranks (0.21 ↔ 0.28 at Mid, 0.25 ↔ 0.28 at Full), which the case allows.
    • Mutation check in whole-tree copies outside the checkout: the control passes 10/10. Pagoda 20 at rank 19's height fails the height test. Glass curtain's base widened at ranks 8–12, and Art deco's sidewalk moved by rank, each fail the ground-plan test. Round's drum white above rank 10, and Brick gable's chimney given the roof role, each fail the materials test.
  • detail.test.ts: the budget test walks every family version in every palette at Full, Mid and Far in Die-cast, Toybox and Micro-voxel, within CITY_BUDGETS. With the eaves, Pagoda is the heaviest family in every finish and tier: Pagoda 19 draws 1,040 of 4,700 in Die-cast at Full and 4,176 of 15,000 in Toybox at Full, Pagoda 20 draws 11,916 of 20,500 in Micro-voxel at Full, and Pagoda 19 draws 468 of 1,550 at Far in Toybox. Over ranks 1–20 the eaves add 10% to Pagoda's own triangles in Die-cast at Full, 16% in Toybox at Full, 22% in Micro-voxel at Full and 40–50% at Far, which keeps every roof part.
  • collapses.test.ts: its look list holds main's 159 looks and the families' 160 versions, so every collapse property it pins holds for them. Its first test takes 0.62 s alone (15 s timeout), and the budget test 6.5 s alone (60 s timeout). Under two more vitest run suites they took up to 2.6 s and 13.1–14.1 s at 42f8173. That load was not re-run on the eaves.
  • cities.phone.ts counts { looks: 159, families: 160, streets: 36, life: 8, monuments: 18, wide: 1 } in every finish and tier.

The families' cost against main's looks (D10's bound)

A whole-tree copy of the head, outside the checkout, whose lookOf gives hash(x, y, 11) % 8 into the families (with lookKey, lookHeight and lookFootprint mapped to the family's), set beside main d3604f1.

The reference cities (layer.test.ts's grown(300) and grown(600)), triangles per finish and tier, main → families (BUDGETS), measured at the final head:

20 min grown out
Die-cast Full 36,404 → 31,432 (0.863; 116,000) 38,120 → 33,276 (0.873; 138,000)
Die-cast Mid 19,012 → 17,624 (0.927; 77,000) 20,160 → 18,764 (0.931; 93,000)
Die-cast Far 9,708 → 9,252 (0.953; 41,000) 10,340 → 9,732 (0.941; 49,000)
Toybox Full 50,116 → 47,292 (0.944; 250,000) 54,608 → 50,928 (0.933; 310,000)
Toybox Mid 19,380 → 18,168 (0.937; 77,000) 20,528 → 19,308 (0.941; 94,000)
Toybox Far 9,796 → 9,428 (0.962; 41,000) 10,428 → 9,908 (0.950; 49,000)
Micro-voxel Full 166,444 → 143,624 (0.863; 327,000) 180,816 → 158,050 (0.874; 393,000)
Micro-voxel Mid 45,020 → 37,868 (0.841; 89,000) 48,512 → 41,530 (0.856; 107,000)
Micro-voxel Far 9,708 → 9,252 (0.953; 41,000) 10,340 → 9,732 (0.941; 49,000)

The Map Yard (mock/map.html built from each tree, ?bench, Playwright WebKit, iPhone 15 landscape), one load per finish, view and growth, 36 loads, at the final head, all under reduced motion (reducedMotion: 'reduce'). Calls main / families, triangles main / families:

finish view growth calls triangles ratio
Die-cast 1× default 67 / 67 295,044 / 287,156 0.973
Die-cast 0.5× default 65 / 65 383,624 / 377,852 0.985
Die-cast 0.25× default 41 / 41 487,352 / 481,000 0.987
Die-cast 1× full 67 / 67 426,827 / 391,339 0.917
Die-cast 0.5× full 66 / 66 483,747 / 485,683 1.004
Die-cast 0.25× full 42 / 42 619,827 / 625,123 1.009
Toybox 1× default 67 / 67 341,592 / 334,072 0.978
Toybox 0.5× default 65 / 65 420,840 / 415,516 0.987
Toybox 0.25× default 41 / 41 524,624 / 518,984 0.989
Toybox 1× full 67 / 67 474,787 / 441,775 0.930
Toybox 0.5× full 66 / 66 521,943 / 527,043 1.010
Toybox 0.25× full 42 / 42 658,607 / 668,691 1.015
Micro-voxel 1× default 67 / 67 255,512 / 252,266 0.987
Micro-voxel 0.5× default 65 / 65 415,424 / 409,652 0.986
Micro-voxel 0.25× default 41 / 41 519,152 / 512,800 0.988
Micro-voxel 1× full 67 / 67 578,901 / 504,699 0.872
Micro-voxel 0.5× full 66 / 66 515,547 / 517,483 1.004
Micro-voxel 0.25× full 42 / 42 651,627 / 656,923 1.008

With motion, as src/bench/map.ts loads the yard (its page context sets no reducedMotion): the same 18 loads per arm, at the final head, with no page or console error. Calls main / families, triangles main / families:

finish view growth calls triangles ratio
Die-cast 1× default 67 / 68 296,576 / 289,484 0.976
Die-cast 0.5× default 65 / 65 383,624 / 377,852 0.985
Die-cast 0.25× default 41 / 41 487,352 / 481,000 0.987
Die-cast 1× full 68 / 68 431,999 / 413,583 0.957
Die-cast 0.5× full 66 / 66 483,591 / 485,527 1.004
Die-cast 0.25× full 42 / 42 619,827 / 625,123 1.009
Toybox 1× default 67 / 68 343,124 / 336,400 0.980
Toybox 0.5× default 65 / 65 420,840 / 415,516 0.987
Toybox 0.25× default 41 / 41 524,624 / 518,984 0.989
Toybox 1× full 68 / 68 479,879 / 463,939 0.967
Toybox 0.5× full 66 / 66 521,787 / 526,887 1.010
Toybox 0.25× full 42 / 42 658,607 / 668,691 1.015
Micro-voxel 1× default 67 / 68 254,644 / 252,194 0.990
Micro-voxel 0.5× default 65 / 65 415,424 / 409,652 0.986
Micro-voxel 0.25× default 41 / 41 519,152 / 512,800 0.988
Micro-voxel 1× full 68 / 68 571,033 / 515,343 0.902
Micro-voxel 0.5× full 66 / 66 515,391 / 517,327 1.004
Micro-voxel 0.25× full 42 / 42 651,627 / 656,923 1.008
  • The + 1 call: at 1× default the copy draws main + 1 call in Die-cast, Toybox and Micro-voxel. That call is life's shared Puffs mesh, the one InstancedMesh (an 80-triangle icosahedron per puff, MeshLambertMaterial) that draws every chimney's smoke. It draws only while there is a puff to draw. Brick gable, one family in eight, smokes at every rank (D2), and main's buildings in that view put up no puff. A listing of every object one held frame draws, in copies of both arms, shows it as the only extra draw in each of the three finishes, with 6 puffs. At 1× full main's own smoke is in view too, so both arms draw 68. Under reduced motion no puff draws, so the 36 loads above show the arms' calls equal.
  • Triangles: 0.902–0.990× main's at 1×, and at most 1.015× elsewhere (Toybox 0.25× full). The 0.5× and 0.25× loads equal the reduced-motion ones, apart from each 0.5× full load, which draws 156 fewer triangles on both arms. With motion, a 1× full load's triangles change between loads of the same build by multiples of 80, one puff.
  • The review measured the same with motion, and named the Puffs mesh the same way.

The bound holds, as D10 reads it: the families' own art batches draw main's calls in every load, with and without motion, and the copy draws at most 1.015× main's triangles (Toybox 0.25× full; it was 1.001× before the eaves, which Far keeps as roof parts). The Puffs mesh is life's, shared by every building that smokes, which D2 asks of Brick gable. So it lies outside the families' bound and counts against D10's + 2. Step 2's bench, which loads with motion, should show main + 1 call at 1× default. Plan #3561's D10 records this reading. Each reference city draws at most 0.962× main's triangles per finish and tier, within BUDGETS. No trim was needed against these loads. The first draft drew windows on every floor and cost 1.5–2.7× main's looks per rank in Die-cast and Toybox, so the families were trimmed to wall-high window strips and leaner dressing before any load ran.

Draw checks: the game draws as before

  • mock/map.html built from main d3604f1 and from the final head 4bd6436 (git archive trees outside the checkout, vite build(), preview()), loaded once per view (1×, 0.5×, 0.25×) in the four finishes at default growth and with grown=full, under reduced motion: all 24 pairs draw identical calls and triangles, with no page or console error.
  • With motion, as the bench loads it, the same 24 views per arm, run twice (96 loads, no page or console error): head and base draw the same calls in all 48 pairs. Their triangles are identical in 43 pairs. The other 5 are all 1× full in Die-cast, Toybox and Micro-voxel, and differ by multiples of 80, one smoke puff. Main's own loads of those views differ from each other the same way, and the values overlap across arms (Toybox main {479,879, 479,959}, head {479,559, 479,879}; Die-cast main {431,599, 431,919}, head {431,919}), so this is load noise. Classic is identical in every load. The review's 144 loads with motion (3 rounds) found the same.
  • Arm split in Toybox at 1× default, five loads under reduced motion (main a, head a, main b, head b, main c), run three times. The pixels split by arm were 168, 148 and 147, against controls of 144/162, 216/153 and 191/110, so they lie within the controls' range of 110–216. Two main loads differ in 39–41k pixels, which is load noise.
  • The bench is not run: this step changes nothing the game draws. lookOf and every key the game uses are unchanged (the family keys come after the trees), and the head draws the base's calls in every load, with and without motion. The families' own cost is held to D10's bound above, before step 2's bench.

Renders for the Reviewer

The Cities Yard's Families section, mock/cities.html?finish=<1|2|3>&detail=<0|1|2>, viewport 2560 × 1400 at device scale 2, reduced motion, one image per family row through ranks 1–20:

  • At 42f8173: Die-cast, Toybox and Micro-voxel at Full, in the neutral roofs and in player 1's colour (48 rows), and Wind tower at Mid and at Far in each of the three finishes (6 rows). Every row shows one ground plan through ranks 1–20, the walls climbing under the same roof, the ground dressing unchanged, and the dressing above the base growing with rank.
  • At the final head, every family but Pagoda builds the same geometry as at 262e376: the same positions, normals, colours and paint in every finish, tier and palette, and in Classic's geometry.
  • Pagoda's row before the eaves (262e376) and at the final head, in Die-cast, Toybox and Micro-voxel at Full, Mid and Far, in the neutral roofs and in player 1's colour (36 rows):
    • Micro-voxel at Mid: before, several ranks (Pagoda 2, 4 and 6 among them) drew a brown timber box under a small blue square. Now every rank draws a plot-wide blue roof, with a roof on each storey.
    • Micro-voxel at Full: now each storey's roof is plot-wide at every rank.
    • Die-cast: reads as before, with a thin straight eave under each hip.
    • Toybox at Full: each roof's edge is inked as a thin line, as before. A first try with the hip's base on the eave's rim drew a thick black band round every roof, because the ink shell welded the two rims into one edge.
  • No WebGL or shader error was logged on any of these pages.

Play

  • The esbuild bundles of src/ai/sim.ts (a3c510e5…) and src/bench/net.ts (4ffd0007…) built from main and from the final head are byte-identical. So the 14 AI matches and npm run bench are not run.
  • npm run towns -- --part monument --json prints byte-identical rows on main and on the final head (5 rows, 5.8 s each), which shows lookOf unchanged.

Gates

  • sak-gate fast #1 (tracker gate run 318) on 42f8173: passed, npm test 984/984 in 30.4 s.
  • sak-gate full #2 (tracker gate run 322) on 262e376: passed, npm test 984/984 in 33.8 s and npm run test:phone 180/180 in 199.5 s.
  • sak-gate fast #1 (tracker gate run 324) on c34772b: passed, npm test 985/985 in 30.9 s.
  • sak-gate full #2 (tracker gate run 329) on 4bd6436, the final head: passed, npm test 985/985 in 30.4 s and npm run test:phone 180/180 in 202.2 s.
  • sak-gate full #1 (tracker gate run 334) on 4bd6436 again, in this run: passed, npm test 985/985 in 30.8 s and npm run test:phone 180/180 in 199.9 s.
Tracker: [#3787](https://sak.swipelab.com/tasks/3787) Step 1 of plan #3561 (a growing building keeps its style and grows taller in place). It adds the eight Boomtown building families of D2, written by D3, beside main's 159 looks. The game keeps drawing main's looks: `lookOf` does not change, so nothing the game draws changes in this step. The changelog and the help come in step 6, which writes them for the whole plan. ## What it delivers - **`FAMILIES`** (src/client/render/boomtown/looks.ts): each family is a name, its wall tone in each palette, its roof shape and its version at each rank 1–20 as a `CityLook`. A version's parts are built once per palette on first use, from looks.ts's own helpers (`gable`, `bands`, `tree`, `blades`, `look`) and palettes (BRICK, PLASTER, GLASS, STONE, CONC). - Every version's body height is the target 0.25 + 2.05 × ((rank − 1) / 19)^1.6 itself, so each stands 1.074–1.175× its version below. - A family keeps one ground plan: the same base block or drum at every rank, and the same ground dressing (path, hedge, tree, bed, sidewalk). It grows by taller walls under the same roof, with more storeys, tiers, balconies and rooftop dressing as it climbs. - Windows are strips the height of each wall, a fixed count per face, so a taller version keeps its windows and a growth that scales y keeps them in proportion. - **The families:** 1. **Brick gable:** brick walls, a slate gable that steepens over ranks 1–5, a smoking chimney (two from rank 7), stone string courses every five floors. 2. **Plaster balconies:** plaster walls, a balcony slab in the team colour on every floor above the first, a flat roof with planters from rank 3 and a water tank from rank 5, a front patio. 3. **Glass curtain:** glass walls with metal bands and flat roofs, one setback from rank 8 and two from rank 13, rooftop plant from rank 4, a mast with a beacon from rank 12. 4. **Round:** a stone or white drum under a cone in the team colour, a flag on its tip at every rank, stone rings every five floors. 5. **Stepped terraces:** concrete tiers that shrink as they rise, each terrace planted, one more tier every four ranks (2 at rank 1, 6 from rank 17), two beacons from rank 12. 6. **Pagoda:** timber storeys on a stone plinth, each under a straight eave and a hipped skirt in the team colour, one more storey every three ranks (7 at ranks 19–20), a gold finial. The eave is the roof's full width and the hip stands 0.02 inside its rim. So Micro-voxel stamps every roof plot-wide at every rank, whatever height a storey ends at, and Toybox inks each roof's edge as a line. 7. **Wind tower:** a pale block with a flat roof, solar panels and a rooftop turbine whose three sails turn at every rank, green wall strips from rank 6. 8. **Art deco:** stone with front piers and a two-step crown under a gold roof, a neon marquee in the team colour at its street front at every rank, a gold spire with a beacon from rank 12. - **Life** (by each model's emitters at Mid and its mounts): smoke on Brick gable at every rank (two chimneys from rank 7); beacons on Glass curtain (one), Stepped terraces (two) and Art deco (one) from rank 12; neon on Art deco at every rank; sails (`Mount.Sail`, pivot `sail`) on Wind tower and a flag (`Mount.Flag`, pivot `flag`) on Round at every rank. Plaster balconies and Pagoda carry no life. No family carries a rotor or a dish. - **Model keys and measures** (src/client/render/boomtown/models.ts): `familyKey(family, rank, palette)` names each version and palette, after the trees, so every existing key keeps its number. They build through the same `modelOf` / `cityGeometry` path as the looks. `familyHeight`, `familyExtent` and `familyFootprint` give a version's body height, body extent (the x/z bounds of its parts at least BODY_WIDTH wide, on their rest geometry) and whole footprint, each measured once. `bodyHeight` and the new `bodyExtent` share `restBox` and `bodyWide`. - **The Cities Yard** (src/client/cities-yard.ts, mock/cities.html): a Families section of 20 columns, one row per family through ranks 1–20, each cell labelled with its version's name ("Brick gable 7"). The palette buttons rebuild it as they do the looks. ## Tests - **looks.test.ts, Boomtown families:** - 8 families with unique names, each with a version at every rank 1–20. - Each version's body height lies within 20% of the target and is at least 1.05× its version below's. - Each version's body extent and whole footprint equal the rank-1 version's within 0.02 tiles, and every part of the rank-1 version whose rest box lies wholly outside its body extent stands in each version with the same box within 0.005 tiles. - At every rank and palette, the largest body part (the non-roof part, of any shape, at least BODY_WIDTH wide on x and z, with the largest rest-box volume) wears the family's wall tone, and every Role.Roof part has the family's roof shape. - In Micro-voxel at Full and at Mid, each version's team-coloured cubes (the `paint` attribute) reach within one cube of its version below's on every x/z bound, wherever that version draws any. - At 262e376, before the eaves, this case failed on Pagoda alone: Pagoda 2, 5 and 18 at Full, and Pagoda 2 and 3 at Mid. - It passes at the head. Pagoda's painted reach is now 0.35 at Mid and 0.38 at Full at every rank, where it went 0.35 / 0.07 / 0.28 / 0.28 / 0.35 … at Mid before. - Round's cone still moves by one cube between some ranks (0.21 ↔ 0.28 at Mid, 0.25 ↔ 0.28 at Full), which the case allows. - Mutation check in whole-tree copies outside the checkout: the control passes 10/10. Pagoda 20 at rank 19's height fails the height test. Glass curtain's base widened at ranks 8–12, and Art deco's sidewalk moved by rank, each fail the ground-plan test. Round's drum white above rank 10, and Brick gable's chimney given the roof role, each fail the materials test. - **detail.test.ts:** the budget test walks every family version in every palette at Full, Mid and Far in Die-cast, Toybox and Micro-voxel, within CITY_BUDGETS. With the eaves, Pagoda is the heaviest family in every finish and tier: Pagoda 19 draws 1,040 of 4,700 in Die-cast at Full and 4,176 of 15,000 in Toybox at Full, Pagoda 20 draws 11,916 of 20,500 in Micro-voxel at Full, and Pagoda 19 draws 468 of 1,550 at Far in Toybox. Over ranks 1–20 the eaves add 10% to Pagoda's own triangles in Die-cast at Full, 16% in Toybox at Full, 22% in Micro-voxel at Full and 40–50% at Far, which keeps every roof part. - **collapses.test.ts:** its look list holds main's 159 looks and the families' 160 versions, so every collapse property it pins holds for them. Its first test takes 0.62 s alone (15 s timeout), and the budget test 6.5 s alone (60 s timeout). Under two more `vitest run` suites they took up to 2.6 s and 13.1–14.1 s at 42f8173. That load was not re-run on the eaves. - **cities.phone.ts** counts `{ looks: 159, families: 160, streets: 36, life: 8, monuments: 18, wide: 1 }` in every finish and tier. ## The families' cost against main's looks (D10's bound) A whole-tree copy of the head, outside the checkout, whose `lookOf` gives `hash(x, y, 11) % 8` into the families (with `lookKey`, `lookHeight` and `lookFootprint` mapped to the family's), set beside main d3604f1. **The reference cities** (layer.test.ts's `grown(300)` and `grown(600)`), triangles per finish and tier, main → families (BUDGETS), measured at the final head: | | 20 min | grown out | |---|---|---| | Die-cast Full | 36,404 → 31,432 (0.863; 116,000) | 38,120 → 33,276 (0.873; 138,000) | | Die-cast Mid | 19,012 → 17,624 (0.927; 77,000) | 20,160 → 18,764 (0.931; 93,000) | | Die-cast Far | 9,708 → 9,252 (0.953; 41,000) | 10,340 → 9,732 (0.941; 49,000) | | Toybox Full | 50,116 → 47,292 (0.944; 250,000) | 54,608 → 50,928 (0.933; 310,000) | | Toybox Mid | 19,380 → 18,168 (0.937; 77,000) | 20,528 → 19,308 (0.941; 94,000) | | Toybox Far | 9,796 → 9,428 (0.962; 41,000) | 10,428 → 9,908 (0.950; 49,000) | | Micro-voxel Full | 166,444 → 143,624 (0.863; 327,000) | 180,816 → 158,050 (0.874; 393,000) | | Micro-voxel Mid | 45,020 → 37,868 (0.841; 89,000) | 48,512 → 41,530 (0.856; 107,000) | | Micro-voxel Far | 9,708 → 9,252 (0.953; 41,000) | 10,340 → 9,732 (0.941; 49,000) | **The Map Yard** (mock/map.html built from each tree, `?bench`, Playwright WebKit, iPhone 15 landscape), one load per finish, view and growth, 36 loads, at the final head, all under reduced motion (`reducedMotion: 'reduce'`). Calls main / families, triangles main / families: | finish | view | growth | calls | triangles | ratio | |---|---|---|---|---|---| | Die-cast | 1× | default | 67 / 67 | 295,044 / 287,156 | 0.973 | | Die-cast | 0.5× | default | 65 / 65 | 383,624 / 377,852 | 0.985 | | Die-cast | 0.25× | default | 41 / 41 | 487,352 / 481,000 | 0.987 | | Die-cast | 1× | full | 67 / 67 | 426,827 / 391,339 | 0.917 | | Die-cast | 0.5× | full | 66 / 66 | 483,747 / 485,683 | 1.004 | | Die-cast | 0.25× | full | 42 / 42 | 619,827 / 625,123 | 1.009 | | Toybox | 1× | default | 67 / 67 | 341,592 / 334,072 | 0.978 | | Toybox | 0.5× | default | 65 / 65 | 420,840 / 415,516 | 0.987 | | Toybox | 0.25× | default | 41 / 41 | 524,624 / 518,984 | 0.989 | | Toybox | 1× | full | 67 / 67 | 474,787 / 441,775 | 0.930 | | Toybox | 0.5× | full | 66 / 66 | 521,943 / 527,043 | 1.010 | | Toybox | 0.25× | full | 42 / 42 | 658,607 / 668,691 | 1.015 | | Micro-voxel | 1× | default | 67 / 67 | 255,512 / 252,266 | 0.987 | | Micro-voxel | 0.5× | default | 65 / 65 | 415,424 / 409,652 | 0.986 | | Micro-voxel | 0.25× | default | 41 / 41 | 519,152 / 512,800 | 0.988 | | Micro-voxel | 1× | full | 67 / 67 | 578,901 / 504,699 | 0.872 | | Micro-voxel | 0.5× | full | 66 / 66 | 515,547 / 517,483 | 1.004 | | Micro-voxel | 0.25× | full | 42 / 42 | 651,627 / 656,923 | 1.008 | **With motion**, as src/bench/map.ts loads the yard (its page context sets no `reducedMotion`): the same 18 loads per arm, at the final head, with no page or console error. Calls main / families, triangles main / families: | finish | view | growth | calls | triangles | ratio | |---|---|---|---|---|---| | Die-cast | 1× | default | 67 / 68 | 296,576 / 289,484 | 0.976 | | Die-cast | 0.5× | default | 65 / 65 | 383,624 / 377,852 | 0.985 | | Die-cast | 0.25× | default | 41 / 41 | 487,352 / 481,000 | 0.987 | | Die-cast | 1× | full | 68 / 68 | 431,999 / 413,583 | 0.957 | | Die-cast | 0.5× | full | 66 / 66 | 483,591 / 485,527 | 1.004 | | Die-cast | 0.25× | full | 42 / 42 | 619,827 / 625,123 | 1.009 | | Toybox | 1× | default | 67 / 68 | 343,124 / 336,400 | 0.980 | | Toybox | 0.5× | default | 65 / 65 | 420,840 / 415,516 | 0.987 | | Toybox | 0.25× | default | 41 / 41 | 524,624 / 518,984 | 0.989 | | Toybox | 1× | full | 68 / 68 | 479,879 / 463,939 | 0.967 | | Toybox | 0.5× | full | 66 / 66 | 521,787 / 526,887 | 1.010 | | Toybox | 0.25× | full | 42 / 42 | 658,607 / 668,691 | 1.015 | | Micro-voxel | 1× | default | 67 / 68 | 254,644 / 252,194 | 0.990 | | Micro-voxel | 0.5× | default | 65 / 65 | 415,424 / 409,652 | 0.986 | | Micro-voxel | 0.25× | default | 41 / 41 | 519,152 / 512,800 | 0.988 | | Micro-voxel | 1× | full | 68 / 68 | 571,033 / 515,343 | 0.902 | | Micro-voxel | 0.5× | full | 66 / 66 | 515,391 / 517,327 | 1.004 | | Micro-voxel | 0.25× | full | 42 / 42 | 651,627 / 656,923 | 1.008 | - **The + 1 call:** at 1× default the copy draws main + 1 call in Die-cast, Toybox and Micro-voxel. That call is life's shared Puffs mesh, the one InstancedMesh (an 80-triangle icosahedron per puff, MeshLambertMaterial) that draws every chimney's smoke. It draws only while there is a puff to draw. Brick gable, one family in eight, smokes at every rank (D2), and main's buildings in that view put up no puff. A listing of every object one held frame draws, in copies of both arms, shows it as the only extra draw in each of the three finishes, with 6 puffs. At 1× full main's own smoke is in view too, so both arms draw 68. Under reduced motion no puff draws, so the 36 loads above show the arms' calls equal. - **Triangles:** 0.902–0.990× main's at 1×, and at most 1.015× elsewhere (Toybox 0.25× full). The 0.5× and 0.25× loads equal the reduced-motion ones, apart from each 0.5× full load, which draws 156 fewer triangles on both arms. With motion, a 1× full load's triangles change between loads of the same build by multiples of 80, one puff. - The review measured the same with motion, and named the Puffs mesh the same way. **The bound holds, as D10 reads it:** the families' own art batches draw main's calls in every load, with and without motion, and the copy draws at most 1.015× main's triangles (Toybox 0.25× full; it was 1.001× before the eaves, which Far keeps as roof parts). The Puffs mesh is life's, shared by every building that smokes, which D2 asks of Brick gable. So it lies outside the families' bound and counts against D10's + 2. Step 2's bench, which loads with motion, should show main + 1 call at 1× default. Plan #3561's D10 records this reading. Each reference city draws at most 0.962× main's triangles per finish and tier, within BUDGETS. No trim was needed against these loads. The first draft drew windows on every floor and cost 1.5–2.7× main's looks per rank in Die-cast and Toybox, so the families were trimmed to wall-high window strips and leaner dressing before any load ran. ## Draw checks: the game draws as before - mock/map.html built from main d3604f1 and from the final head 4bd6436 (`git archive` trees outside the checkout, vite `build()`, `preview()`), loaded once per view (1×, 0.5×, 0.25×) in the four finishes at default growth and with `grown=full`, under reduced motion: all 24 pairs draw identical calls and triangles, with no page or console error. - **With motion**, as the bench loads it, the same 24 views per arm, run twice (96 loads, no page or console error): head and base draw the same calls in all 48 pairs. Their triangles are identical in 43 pairs. The other 5 are all 1× full in Die-cast, Toybox and Micro-voxel, and differ by multiples of 80, one smoke puff. Main's own loads of those views differ from each other the same way, and the values overlap across arms (Toybox main {479,879, 479,959}, head {479,559, 479,879}; Die-cast main {431,599, 431,919}, head {431,919}), so this is load noise. Classic is identical in every load. The review's 144 loads with motion (3 rounds) found the same. - **Arm split** in Toybox at 1× default, five loads under reduced motion (main a, head a, main b, head b, main c), run three times. The pixels split by arm were 168, 148 and 147, against controls of 144/162, 216/153 and 191/110, so they lie within the controls' range of 110–216. Two main loads differ in 39–41k pixels, which is load noise. - **The bench is not run:** this step changes nothing the game draws. `lookOf` and every key the game uses are unchanged (the family keys come after the trees), and the head draws the base's calls in every load, with and without motion. The families' own cost is held to D10's bound above, before step 2's bench. ## Renders for the Reviewer The Cities Yard's Families section, `mock/cities.html?finish=<1|2|3>&detail=<0|1|2>`, viewport 2560 × 1400 at device scale 2, reduced motion, one image per family row through ranks 1–20: - At 42f8173: Die-cast, Toybox and Micro-voxel at Full, in the neutral roofs and in player 1's colour (48 rows), and Wind tower at Mid and at Far in each of the three finishes (6 rows). Every row shows one ground plan through ranks 1–20, the walls climbing under the same roof, the ground dressing unchanged, and the dressing above the base growing with rank. - At the final head, every family but Pagoda builds the same geometry as at 262e376: the same positions, normals, colours and paint in every finish, tier and palette, and in Classic's geometry. - Pagoda's row before the eaves (262e376) and at the final head, in Die-cast, Toybox and Micro-voxel at Full, Mid and Far, in the neutral roofs and in player 1's colour (36 rows): - **Micro-voxel at Mid:** before, several ranks (Pagoda 2, 4 and 6 among them) drew a brown timber box under a small blue square. Now every rank draws a plot-wide blue roof, with a roof on each storey. - **Micro-voxel at Full:** now each storey's roof is plot-wide at every rank. - **Die-cast:** reads as before, with a thin straight eave under each hip. - **Toybox at Full:** each roof's edge is inked as a thin line, as before. A first try with the hip's base on the eave's rim drew a thick black band round every roof, because the ink shell welded the two rims into one edge. - No WebGL or shader error was logged on any of these pages. ## Play - The esbuild bundles of src/ai/sim.ts (a3c510e5…) and src/bench/net.ts (4ffd0007…) built from main and from the final head are byte-identical. So the 14 AI matches and `npm run bench` are not run. - `npm run towns -- --part monument --json` prints byte-identical rows on main and on the final head (5 rows, 5.8 s each), which shows `lookOf` unchanged. ## Gates - `sak-gate fast` #1 (tracker gate run 318) on 42f8173: passed, `npm test` 984/984 in 30.4 s. - `sak-gate full` #2 (tracker gate run 322) on 262e376: passed, `npm test` 984/984 in 33.8 s and `npm run test:phone` 180/180 in 199.5 s. - `sak-gate fast` #1 (tracker gate run 324) on c34772b: passed, `npm test` 985/985 in 30.9 s. - `sak-gate full` #2 (tracker gate run 329) on 4bd6436, the final head: passed, `npm test` 985/985 in 30.4 s and `npm run test:phone` 180/180 in 202.2 s. - `sak-gate full` #1 (tracker gate run 334) on 4bd6436 again, in this run: passed, `npm test` 985/985 in 30.8 s and `npm run test:phone` 180/180 in 199.9 s.
agra merged commit e483667cfb into task/3561-a-growing-building-keeps-its-style-and-g 2026-10-24 18:05:58 +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!143
No description provided.