fix: a coarse lot is a parcel, not a 700 m building
The unidentified slab on downtown San Francisco was the lot ladder's own coarsest rung. Tower fill of 1.5–2.2 assembles a Salesforce-shaped site from 40 m lots; on a 400 m lot it is a 600–880 m cube, rotated with the district's street grid, downtown palette. The same pose on the Bay Area board uses 40 m lots and does not show it. Cap both axes at NEIGHBOURHOOD_LOT_METRES. Fine rungs are unchanged because 40 m and 80 m already sit under the cap. The test that would have named this on the first photograph now exists.
This commit is contained in:
@@ -28,7 +28,13 @@ import test from "node:test";
|
||||
import * as THREE from "three";
|
||||
|
||||
import { setReconcile } from "../../cities/reconcile.ts";
|
||||
import { createBlocks, detailLotMetres, LOT_BUDGET, updateBlocksDetail } from "../../engine/blocks.ts";
|
||||
import {
|
||||
createBlocks,
|
||||
detailLotMetres,
|
||||
LOT_BUDGET,
|
||||
NEIGHBOURHOOD_LOT_METRES,
|
||||
updateBlocksDetail,
|
||||
} from "../../engine/blocks.ts";
|
||||
import type { City, District } from "../../engine/types.ts";
|
||||
import { World } from "../../engine/world.ts";
|
||||
|
||||
@@ -219,3 +225,41 @@ test("every rung is drawn from the same mesh, geometry and material", async () =
|
||||
assert.equal(blocks.boundingSphere, null, "a stale sphere would hide the city it was not built at");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The largest building footprint currently packed, in scene units.
|
||||
*
|
||||
* Same extract as `smallestFootprint`: the first three entries of each 4×4
|
||||
* instance matrix are the X-axis of the building, whose length is its width.
|
||||
* Depth is the Z-axis at offsets 8, 9, 10.
|
||||
*/
|
||||
function largestFootprintM(mesh: THREE.InstancedMesh, metresPerUnit: number): number {
|
||||
const m = mesh.instanceMatrix.array as Float32Array;
|
||||
let largest = 0;
|
||||
for (let i = 0; i < mesh.count; i++) {
|
||||
const at = i * 16;
|
||||
const width = Math.hypot(m[at]!, m[at + 1]!, m[at + 2]!);
|
||||
const depth = Math.hypot(m[at + 8]!, m[at + 9]!, m[at + 10]!);
|
||||
const span = Math.max(width, depth) * metresPerUnit;
|
||||
if (span > largest) largest = span;
|
||||
}
|
||||
return largest;
|
||||
}
|
||||
|
||||
test("a coarse lot is a parcel, not a 700 m building", async () => {
|
||||
const world = await built();
|
||||
const blocks = createBlocks(world);
|
||||
/*
|
||||
* Budget 100 forces the coarsest rung — 400 m lots on this fixture, the
|
||||
* same rung the merged board picked over FiDi when the 700 m slab was
|
||||
* photographed. Without the cap, tower fill of 1.5–2.2 on a 400 m lot is
|
||||
* a 600–880 m cube. With it, nothing on the board is wider than a block.
|
||||
*/
|
||||
packAt(world, blocks, 100);
|
||||
assert.ok(blocks.count > 0, "the coarsest rung packed nothing");
|
||||
const widest = largestFootprintM(blocks, world.metresPerUnit);
|
||||
assert.ok(
|
||||
widest <= NEIGHBOURHOOD_LOT_METRES + 1,
|
||||
`a ${widest.toFixed(0)} m building on a coarse lot is the FiDi slab`,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user