perf: a fine lot rung is walked when a pose asks, not at boot
`createBlocks` walked every reachable rung of every detail district before the first frame. The 160 m reference and the coarser rungs are what the opening poses actually draw; 40 m and 80 m were 14 extra megabytes and the second of boot, spent on downtowns you had not flown to. Those rungs are still priced the same way — a district that would never fit `TIER_DISTRICT_LOTS` still does not get one — and they are walked the first time the budget would pick them. A second close pose reuses the walk. The live mesh is still one `InstancedMesh` sized to the lot budget. `blocksLotLadder.test.ts` holds that a coarse pack does not grow the store and a full-budget pack does, once.
This commit is contained in:
+224
-67
@@ -143,6 +143,11 @@ const TIER_SEED_STRIDE = 104_729;
|
||||
*
|
||||
* A district that is refused a rung simply draws its finest built one when the
|
||||
* board asks for a finer; see the coarse-fill in `createBlocks`.
|
||||
*
|
||||
* **Finer than the reference is not walked at boot.** The gate still decides
|
||||
* whether a district *may* have a 40 m or 80 m rung; the walk itself waits for
|
||||
* a pose whose budget would pick that rung. A district you never stand over at
|
||||
* 2 km is a district that never spends the second and the megabytes.
|
||||
*/
|
||||
const TIER_DISTRICT_LOTS = 2_500;
|
||||
|
||||
@@ -455,16 +460,16 @@ export function createBlocks(
|
||||
|
||||
/**
|
||||
* One rung's walk of one district: step the lattice, keep what lands on
|
||||
* buildable ground, and push what survives onto `boxes`.
|
||||
* buildable ground, and return what survives.
|
||||
*
|
||||
* This is the loop that has always been here, lifted out of it — every line
|
||||
* in it is about the lattice and none of them is about which rung it is
|
||||
* walking. It is now called several times for a detail district, once per lot
|
||||
* size on `DETAIL_LOT_TIERS`, and `lot` and `seed` are the whole difference
|
||||
* between two calls.
|
||||
* walking. Boot calls it for the reference rung and every coarser one;
|
||||
* `updateBlocksDetail` calls it the first time a pose would pick a finer.
|
||||
* `lot` and `seed` are the whole difference between two calls.
|
||||
*/
|
||||
function emit(district: District, lot: number, seed: number): DetailTier {
|
||||
const start = boxes.length;
|
||||
function walkLots(district: District, lot: number, seed: number): Box[] {
|
||||
const lotBoxes: Box[] = [];
|
||||
const lotIsABlockHere = lot * world.metresPerUnit <= NEIGHBOURHOOD_LOT_METRES;
|
||||
const rand = seededRandom(seed);
|
||||
|
||||
@@ -562,7 +567,7 @@ export function createBlocks(
|
||||
continue;
|
||||
}
|
||||
|
||||
boxes.push({
|
||||
lotBoxes.push({
|
||||
x,
|
||||
z,
|
||||
y: world.groundAt(lat, lng),
|
||||
@@ -576,7 +581,14 @@ export function createBlocks(
|
||||
});
|
||||
}
|
||||
}
|
||||
return { start, count: boxes.length - start };
|
||||
return lotBoxes;
|
||||
}
|
||||
|
||||
function emit(district: District, lot: number, seed: number): DetailTier {
|
||||
const start = boxes.length;
|
||||
const lotBoxes = walkLots(district, lot, seed);
|
||||
for (const box of lotBoxes) boxes.push(box);
|
||||
return { start, count: lotBoxes.length };
|
||||
}
|
||||
|
||||
const coarsestTier = DETAIL_LOT_TIERS.length - 1;
|
||||
@@ -588,10 +600,10 @@ export function createBlocks(
|
||||
seedBase += 7919;
|
||||
|
||||
/*
|
||||
* A detail district is walked once per rung it can reach; everything else
|
||||
* keeps the scene-unit `LOT` it has always had, in the one slot the
|
||||
* coarse-fill below then hands to every rung, so no existing board moves by
|
||||
* a lot.
|
||||
* A detail district is walked at the reference rung and every coarser one;
|
||||
* everything else keeps the scene-unit `LOT` it has always had, in the one
|
||||
* slot the coarse-fill below then hands to every rung, so no existing
|
||||
* board moves by a lot. Fine rungs wait for a pose that would pick them.
|
||||
*/
|
||||
const tiers: (DetailTier | null)[] = DETAIL_LOT_TIERS.map(() => null);
|
||||
if (district.detail === true) {
|
||||
@@ -608,23 +620,15 @@ export function createBlocks(
|
||||
tiers[t] = emit(district, metres(t), stream(t));
|
||||
}
|
||||
/*
|
||||
* Finer than the reference, and **priced before it is walked**. Lots go
|
||||
* as the inverse square of their size and they do so tightly — across the
|
||||
* whole board 160 m builds 56,327 and 40 m builds 901,502, which is
|
||||
* 16.00× against a predicted 16 — so the reference rung's own count is a
|
||||
* good enough estimate of the finer one to decide whether it is worth
|
||||
* walking at all. It has to be an estimate rather than a measurement:
|
||||
* walking a district at 40 m to discover it was 64,000 lots is the cost
|
||||
* this is avoiding, not the cost of keeping them.
|
||||
*
|
||||
* `break` rather than `continue`: the rungs get monotonically dearer as
|
||||
* they get finer, so the first one that is too dear ends the ladder.
|
||||
* Finer than the reference is priced the same way it always was, and
|
||||
* **not walked**. Lots go as the inverse square of their size — 160 m
|
||||
* builds 56,327 and 40 m would build 901,502 ungated — so the reference
|
||||
* count is enough to decide whether a district may ever have a 40 m
|
||||
* rung. Walking that rung for every downtown at boot is the second of
|
||||
* boot this file used to spend. `updateBlocksDetail` walks it the first
|
||||
* time the budget would pick it; a district that never comes that close
|
||||
* never spends it.
|
||||
*/
|
||||
for (let t = DETAIL_LOT_REFERENCE - 1; t >= 0; t--) {
|
||||
const finer = DETAIL_LOT_TIERS[DETAIL_LOT_REFERENCE]! / DETAIL_LOT_TIERS[t]!;
|
||||
if (reference.count * finer * finer > TIER_DISTRICT_LOTS) break;
|
||||
tiers[t] = emit(district, metres(t), stream(t));
|
||||
}
|
||||
} else {
|
||||
tiers[coarsestTier] = emit(district, LOT, seed);
|
||||
}
|
||||
@@ -645,19 +649,7 @@ export function createBlocks(
|
||||
* West Covina — which has no 80 m rung, being 4,009 lots at 160 — is drawn
|
||||
* at 160. It is fifteen kilometres away and the difference does not read.
|
||||
*/
|
||||
const filled: DetailTier[] = [];
|
||||
let carry: DetailTier | null = null;
|
||||
for (let t = coarsestTier; t >= 0; t--) {
|
||||
const own = tiers[t] ?? null;
|
||||
if (own !== null && own.count > 0) carry = own;
|
||||
filled[t] = carry ?? { start: districtStart, count: 0 };
|
||||
}
|
||||
let back: DetailTier | null = null;
|
||||
for (let t = 0; t <= coarsestTier; t++) {
|
||||
const here = filled[t]!;
|
||||
if (here.count > 0) back = here;
|
||||
else if (back !== null) filled[t] = back;
|
||||
}
|
||||
const filled = fillTiers(tiers, districtStart);
|
||||
|
||||
/*
|
||||
* The vertical extent, walked here because the horizontal reach is not it.
|
||||
@@ -700,6 +692,14 @@ export function createBlocks(
|
||||
y1,
|
||||
detail: district.detail === true,
|
||||
tiers: filled,
|
||||
...(district.detail === true
|
||||
? {
|
||||
owned: tiers.slice(),
|
||||
district,
|
||||
seed,
|
||||
referenceCount: tiers[DETAIL_LOT_REFERENCE]?.count ?? 0,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -757,32 +757,29 @@ export function createBlocks(
|
||||
|
||||
/**
|
||||
* How many instances the mesh has room for — **and it is no longer
|
||||
* `boxes.length`**, because with a ladder in the store `boxes` holds every
|
||||
* rung of every district and only one rung of each is ever drawn.
|
||||
* `boxes.length`**, because only one rung of each district is ever drawn
|
||||
* and the fine rungs are not in `boxes` at boot.
|
||||
*
|
||||
* `InstancedMesh` fixes its capacity at construction, so this has to be a
|
||||
* bound rather than a guess, and it is one: the packed set is a subset of the
|
||||
* districts drawn at one rung `i`, so it is at most `totalAt(i)`; and the
|
||||
* selection in `updateBlocksDetail` only settles on a rung finer than the
|
||||
* coarsest if that rung's own visible sum came in under the budget, which is
|
||||
* never above `LOT_BUDGET`. So the ceiling is the larger of the whole
|
||||
* board at its coarsest rung and the budget itself, and never larger than the
|
||||
* board at the rung in question. The coarsest rung is the fallback when even
|
||||
* it is over budget, which is why it is the one term with no budget in it.
|
||||
*
|
||||
* Measured on the merged board it comes out at exactly **19,000** — the
|
||||
* budget itself, because no rung's whole-board total is under it — against the
|
||||
* 59,166 the flat 160 m board allocated: the live buffers get *smaller*,
|
||||
* because the mesh no longer has to be able to draw every metro at once at a
|
||||
* rung nothing can afford. The store behind it is the part that grows.
|
||||
* `InstancedMesh` fixes its capacity at construction. A packed set is at
|
||||
* most `LOT_BUDGET`, except when even the coarsest rung is over that, in
|
||||
* which case the coarsest rung is the fallback and must fit. Fine rungs
|
||||
* append to the store later, but they still pack through the budget, so
|
||||
* the live buffer does not grow.
|
||||
*/
|
||||
let capacity = boxes.length;
|
||||
if (hasDetail) {
|
||||
const totalAt = (t: number) => districtRanges.reduce((n, r) => n + r.tiers[t]!.count, 0);
|
||||
capacity = totalAt(coarsestTier);
|
||||
for (let t = 0; t < coarsestTier; t++) {
|
||||
capacity = Math.max(capacity, Math.min(totalAt(t), LOT_BUDGET));
|
||||
}
|
||||
/*
|
||||
* Fine rungs are not in `boxes` yet. A packed set is still at most
|
||||
* `LOT_BUDGET` (or the whole coarsest rung if even that is over), so the
|
||||
* mesh has to be able to hold the budget even though boot never walked
|
||||
* 40 m. On the merged board this is still 19,000 — the coarsest rung is
|
||||
* 18,742 and the budget is the larger number — same allocation as before.
|
||||
*/
|
||||
const coarsestLots = districtRanges.reduce(
|
||||
(n, r) => n + r.tiers[coarsestTier]!.count,
|
||||
0,
|
||||
);
|
||||
capacity = Math.max(coarsestLots, LOT_BUDGET);
|
||||
}
|
||||
|
||||
// The per-instance facade data, drawn from a stream of its own.
|
||||
@@ -872,10 +869,10 @@ export function createBlocks(
|
||||
* which windows are lit. Moving the matrices alone would light a tower's
|
||||
* windows on a warehouse.
|
||||
*
|
||||
* **The copy now holds every rung**, which is what the ladder costs: it is
|
||||
* the whole of the price, and it is paid in memory and boot rather than per
|
||||
* frame. The 4.7 MiB the flat board's detail copy held becomes whatever the
|
||||
* ladder built, and nothing about it is touched between repacks.
|
||||
* **The copy holds the rungs boot walked**, which is the reference and every
|
||||
* coarser one. Fine rungs append here the first time a pose would pick them,
|
||||
* so a district you never stand over at 2 km never spends the walk. The live
|
||||
* mesh is still packed from this copy; only the copy grows.
|
||||
*/
|
||||
const store: DetailStore = {
|
||||
ranges: districtRanges,
|
||||
@@ -887,6 +884,9 @@ export function createBlocks(
|
||||
// Nothing has been packed yet, so nothing is being drawn at any rung. `null`
|
||||
// rather than `coarsestTier` for the same reason the field exists.
|
||||
detailTier: null,
|
||||
walkLots,
|
||||
windows,
|
||||
metresPerUnit: world.metresPerUnit,
|
||||
};
|
||||
mesh.userData.detail = store;
|
||||
/*
|
||||
@@ -909,6 +909,13 @@ export function createBlocks(
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/** How many lots the store is holding — every walked rung, not the packed set. */
|
||||
export function storedLotCount(mesh: THREE.InstancedMesh): number {
|
||||
const store = mesh.userData.detail as DetailStore | undefined;
|
||||
if (store === undefined) return mesh.count;
|
||||
return (store.srcMatrix.length / 16) | 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* One district's lots, and the box that contains them.
|
||||
*
|
||||
@@ -949,6 +956,16 @@ interface DetailRange {
|
||||
* A base district has one lattice and every index is it.
|
||||
*/
|
||||
tiers: DetailTier[];
|
||||
/**
|
||||
* The rungs this district has actually walked, sparse. Missing fine rungs
|
||||
* are walked on demand; `tiers` is the coarse-fill of this. Absent on a
|
||||
* base district, which has one lattice.
|
||||
*/
|
||||
owned?: (DetailTier | null)[];
|
||||
district?: District;
|
||||
seed?: number;
|
||||
/** Lot count at `DETAIL_LOT_REFERENCE`, for pricing a finer rung. */
|
||||
referenceCount?: number;
|
||||
}
|
||||
|
||||
/** Where one rung of one district's lots sit in the store's arrays. */
|
||||
@@ -957,6 +974,71 @@ interface DetailTier {
|
||||
count: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten a sparse owned-rung list into a dense ladder.
|
||||
*
|
||||
* A district that was refused a fine rung still has to answer when the board
|
||||
* asks for one, and the answer is its own finest built rung. Walking from the
|
||||
* coarse end inwards, each index takes the nearest built rung that is no
|
||||
* finer than it; a second pass fine-ward covers a district whose coarse rungs
|
||||
* all came out empty.
|
||||
*/
|
||||
function fillTiers(owned: (DetailTier | null)[], fallbackStart: number): DetailTier[] {
|
||||
const coarsest = DETAIL_LOT_TIERS.length - 1;
|
||||
const filled: DetailTier[] = [];
|
||||
let carry: DetailTier | null = null;
|
||||
for (let t = coarsest; t >= 0; t--) {
|
||||
const own = owned[t] ?? null;
|
||||
if (own !== null && own.count > 0) carry = own;
|
||||
filled[t] = carry ?? { start: fallbackStart, count: 0 };
|
||||
}
|
||||
let back: DetailTier | null = null;
|
||||
for (let t = 0; t <= coarsest; t++) {
|
||||
const here = filled[t]!;
|
||||
if (here.count > 0) back = here;
|
||||
else if (back !== null) filled[t] = back;
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
|
||||
function growFloat32(src: Float32Array, extra: number): Float32Array {
|
||||
const next = new Float32Array(src.length + extra);
|
||||
next.set(src);
|
||||
return next;
|
||||
}
|
||||
|
||||
function composeLots(store: DetailStore, more: readonly Box[]): DetailTier {
|
||||
const start = (store.srcMatrix.length / 16) | 0;
|
||||
const n = more.length;
|
||||
if (n === 0) return { start, count: 0 };
|
||||
const srcMatrix = growFloat32(store.srcMatrix, n * 16);
|
||||
const srcColor = growFloat32(store.srcColor, n * 3);
|
||||
const srcFacade = growFloat32(store.srcFacade, n * 2);
|
||||
const windows = store.windows ?? (() => 0);
|
||||
const matrix = new THREE.Matrix4();
|
||||
const quat = new THREE.Quaternion();
|
||||
const pos = new THREE.Vector3();
|
||||
const scl = new THREE.Vector3();
|
||||
const up = new THREE.Vector3(0, 1, 0);
|
||||
more.forEach((b, i) => {
|
||||
const at = start + i;
|
||||
pos.set(b.x, b.y, b.z);
|
||||
quat.setFromAxisAngle(up, b.rot);
|
||||
scl.set(b.w, b.h, b.d);
|
||||
matrix.compose(pos, quat, scl);
|
||||
matrix.toArray(srcMatrix, at * 16);
|
||||
srcColor[at * 3] = b.color.r;
|
||||
srcColor[at * 3 + 1] = b.color.g;
|
||||
srcColor[at * 3 + 2] = b.color.b;
|
||||
srcFacade[at * 2] = b.commercial;
|
||||
srcFacade[at * 2 + 1] = windows();
|
||||
});
|
||||
store.srcMatrix = srcMatrix;
|
||||
store.srcColor = srcColor;
|
||||
store.srcFacade = srcFacade;
|
||||
return { start, count: n };
|
||||
}
|
||||
|
||||
interface DetailStore {
|
||||
ranges: DetailRange[];
|
||||
srcMatrix: Float32Array;
|
||||
@@ -1002,6 +1084,14 @@ interface DetailStore {
|
||||
* next decision gets made on.
|
||||
*/
|
||||
detailTier: number | null;
|
||||
/**
|
||||
* Enough to walk a finer rung after boot: the world the lots stand on, the
|
||||
* lattice walker, and the facade stream continued from where boot left it.
|
||||
* Absent on a board with no detail districts.
|
||||
*/
|
||||
walkLots?: (district: District, lot: number, seed: number) => Box[];
|
||||
windows?: () => number;
|
||||
metresPerUnit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1026,6 +1116,60 @@ interface DetailStore {
|
||||
*/
|
||||
const DETAIL_FRUSTUM_PAD = 0.02;
|
||||
|
||||
function estimatedCount(range: DetailRange, t: number): number {
|
||||
const owned = range.owned?.[t];
|
||||
if (owned) return owned.count;
|
||||
if (range.owned && t < DETAIL_LOT_REFERENCE && range.referenceCount !== undefined) {
|
||||
const finer = DETAIL_LOT_TIERS[DETAIL_LOT_REFERENCE]! / DETAIL_LOT_TIERS[t]!;
|
||||
const estimate = range.referenceCount * finer * finer;
|
||||
if (estimate > TIER_DISTRICT_LOTS) return range.tiers[t]!.count;
|
||||
return estimate;
|
||||
}
|
||||
return range.tiers[t]!.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk any finer rung the budget is about to pick, for the districts in frame.
|
||||
*
|
||||
* Boot left those rungs sparse. The first pose whose visible sum would fit at
|
||||
* 40 m or 80 m pays the walk; every later pose on the same districts reuses it.
|
||||
*/
|
||||
function ensureFineRungs(store: DetailStore, visible: DetailRange[], wanted: number): void {
|
||||
if (wanted >= DETAIL_LOT_REFERENCE) return;
|
||||
const walk = store.walkLots;
|
||||
const mpu = store.metresPerUnit;
|
||||
if (!walk || mpu === undefined || mpu <= 0) return;
|
||||
const coarsest = DETAIL_LOT_TIERS.length - 1;
|
||||
for (const range of visible) {
|
||||
const owned = range.owned;
|
||||
const district = range.district;
|
||||
if (!owned || !district || range.seed === undefined || range.referenceCount === undefined) {
|
||||
continue;
|
||||
}
|
||||
let grew = false;
|
||||
for (let t = DETAIL_LOT_REFERENCE - 1; t >= wanted; t--) {
|
||||
if (owned[t]) continue;
|
||||
const finer = DETAIL_LOT_TIERS[DETAIL_LOT_REFERENCE]! / DETAIL_LOT_TIERS[t]!;
|
||||
if (range.referenceCount * finer * finer > TIER_DISTRICT_LOTS) break;
|
||||
const more = walk(
|
||||
district,
|
||||
DETAIL_LOT_TIERS[t]! / mpu,
|
||||
range.seed + (t - DETAIL_LOT_REFERENCE) * TIER_SEED_STRIDE,
|
||||
);
|
||||
owned[t] = composeLots(store, more);
|
||||
for (const box of more) {
|
||||
if (box.y < range.y0) range.y0 = box.y;
|
||||
if (box.y + box.h > range.y1) range.y1 = box.y + box.h;
|
||||
}
|
||||
grew = true;
|
||||
}
|
||||
if (grew) {
|
||||
const fallback = owned[coarsest]?.start ?? range.tiers[coarsest]!.start;
|
||||
range.tiers = fillTiers(owned, fallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Held rather than allocated: the frustum test runs once per district. */
|
||||
const REACH_BOX = new THREE.Box3();
|
||||
|
||||
@@ -1140,6 +1284,19 @@ export function updateBlocksDetail(
|
||||
*/
|
||||
const budget = Math.min(lotBudget, LOT_BUDGET);
|
||||
const coarsest = DETAIL_LOT_TIERS.length - 1;
|
||||
const sumEst = (t: number) => {
|
||||
let n = 0;
|
||||
for (const r of visible) n += estimatedCount(r, t);
|
||||
return n;
|
||||
};
|
||||
let want = coarsest;
|
||||
for (let t = 0; t < coarsest; t++) {
|
||||
if (sumEst(t) <= budget) {
|
||||
want = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ensureFineRungs(store, visible, want);
|
||||
const sumAt = (t: number) => {
|
||||
let n = 0;
|
||||
for (const r of visible) n += r.tiers[t]!.count;
|
||||
|
||||
@@ -113,14 +113,12 @@ test("without a frustum the packing is exactly what it always was", async () =>
|
||||
const world = await built();
|
||||
const blocks = createBlocks(world);
|
||||
/*
|
||||
* Capacity stopped being "every lot that exists" when lot size became a
|
||||
* ladder: the store holds every rung of every district and only one rung of
|
||||
* each is ever drawn, so the mesh is allocated against a bound — the whole
|
||||
* board at its finest reachable rung, or the lot budget, whichever is
|
||||
* smaller, and never below the whole board at its coarsest. On this fixture
|
||||
* the detail districts are far too large for a rung finer than the reference,
|
||||
* so the board's finest reachable rung *is* the reference and the two
|
||||
* statements below still measure exactly what they measured before.
|
||||
* Capacity is no longer "every lot that exists": only one rung of each
|
||||
* district is drawn, and fine rungs are not walked at boot, so the mesh is
|
||||
* allocated against the lot budget (or the coarsest rung if that is larger).
|
||||
* On this fixture the detail districts are far too large for a rung finer
|
||||
* than the reference, so a full-reach pack draws the reference and leaves
|
||||
* room in the buffer.
|
||||
*/
|
||||
const capacity = (blocks.instanceMatrix.array.length / 16) | 0;
|
||||
assert.ok(capacity > 200, `the fixture is too small to be a test: ${capacity} lots`);
|
||||
@@ -140,10 +138,13 @@ test("without a frustum the packing is exactly what it always was", async () =>
|
||||
assert.ok(base > 0, "the base district vanished");
|
||||
|
||||
// In reach of both: every lot on the board, in one contiguous run — at the
|
||||
// finest rung it has, which on this fixture is the only rung it has.
|
||||
// finest rung it has, which on this fixture is the reference.
|
||||
updateBlocksDetail(blocks, x, z, 1_000);
|
||||
assert.equal(blocks.count, capacity, "reach alone must still be able to draw the whole board");
|
||||
assert.ok(base < capacity, "the fixture has no detail lots to cull");
|
||||
assert.ok(blocks.count > base, "the fixture has no detail lots to cull");
|
||||
assert.ok(
|
||||
blocks.count <= capacity,
|
||||
`reach packed ${blocks.count} past a ${capacity}-lot buffer`,
|
||||
);
|
||||
});
|
||||
|
||||
test("a frustum packs strictly less, and never more than there is room for", async () => {
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
detailLotMetres,
|
||||
LOT_BUDGET,
|
||||
NEIGHBOURHOOD_LOT_METRES,
|
||||
storedLotCount,
|
||||
updateBlocksDetail,
|
||||
} from "../../engine/blocks.ts";
|
||||
import type { City, District } from "../../engine/types.ts";
|
||||
@@ -263,3 +264,27 @@ test("a coarse lot is a parcel, not a 700 m building", async () => {
|
||||
`a ${widest.toFixed(0)} m building on a coarse lot is the FiDi slab`,
|
||||
);
|
||||
});
|
||||
|
||||
test("fine rungs wait for a pose that would pick them", async () => {
|
||||
const world = await built();
|
||||
const blocks = createBlocks(world);
|
||||
const atBoot = storedLotCount(blocks);
|
||||
packAt(world, blocks, 100);
|
||||
assert.equal(
|
||||
storedLotCount(blocks),
|
||||
atBoot,
|
||||
"a coarse pose walked a fine rung the camera never asked for",
|
||||
);
|
||||
packAt(world, blocks, LOT_BUDGET);
|
||||
const afterFine = storedLotCount(blocks);
|
||||
assert.ok(
|
||||
afterFine > atBoot,
|
||||
`a full-budget pose did not walk a fine rung: store stayed at ${atBoot}`,
|
||||
);
|
||||
packAt(world, blocks, LOT_BUDGET);
|
||||
assert.equal(
|
||||
storedLotCount(blocks),
|
||||
afterFine,
|
||||
"the second close pose walked the same rung again",
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user