Lane B round-6: place street_bin + bus_shelter GLBs (on the street graph, collision-verified)
Consumes the two orphaned street GLBs via the use-if-ready furniture path (fail-soft; ?noassets →
primitives, 0 network).
- Bins: genuinely sparse (~1/block, s+=78m, seeded side), decoupled from benches — country town not
a mall. GLB on its 0.57x0.68 footprint.
- Bus shelters ON THE STREET GRAPH (future tram/bus stops): deterministic rule = midpoint of every
MAIN edge with hashEdge(id)%3===0, road-side footpath by the kerb, long axis along the edge, open
front to road -> 1-3/town (2 in seed 20261990). Solid oriented-rect collider (furniture now returns
colliders; chunks.js merges them into getColliders) -> walk-into-it is blocked. Exported
busShelterStops(plan) -> [{edgeId,x,z,yaw}] as the stop-list contract for the vehicle loop.
- Perf: worst view WITH rig-fleet citizens 94k tris / 148 draws (<=200k / <=300, big margin);
deterministic; all-flags-on (winmap+dig+roster) composes clean, 0 errors. street_bin GLB is 3k tris
-> flagged to Lane E as a decimation candidate (bounded by the sparse cadence for now).
qa.sh --strict GREEN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
35981ade5f
commit
24cef5a7bb
@ -4,6 +4,21 @@
|
||||
plus the game shell. It runs on my hand-written fixture **and** auto-integrates with Lane A's
|
||||
`generatePlan` (which landed mid-session). Everything below was verified live in a browser.
|
||||
|
||||
## Round 6 (Fable's ROUND6 → Lane B) — placed the orphaned street props · qa GREEN
|
||||
|
||||
Wired the two published-but-unconsumed street GLBs (`street_bin_01`, `bus_shelter_01`) via the
|
||||
use-if-ready furniture path (fail-soft; `?noassets` → primitives, 0 network). Files: `furniture.js`,
|
||||
`chunks.js` (collider merge).
|
||||
- **Bins**: sparse (~1/block, `s += 78 m`, seeded), decoupled from benches — country town not a mall.
|
||||
- **Bus shelters on the street graph** (future tram/bus stops): deterministic rule = midpoint of every
|
||||
MAIN edge with `hashEdge(id)%3===0`, road-side footpath, long axis along the edge, open front to road
|
||||
→ **1–3/town** (2 in seed 20261990). **Solid collider** (furniture now returns colliders → `chunks`
|
||||
merges into `getColliders`); walk-into-it is blocked. **Exposed `busShelterStops(plan)` → the stop
|
||||
list contract** for whoever builds the vehicle loop.
|
||||
- **Perf**: worst view WITH rig-fleet citizens **94k tris / 148 draws** (≤200k / ≤300, big margin);
|
||||
deterministic; all-flags-on composes clean (0 errors). Flagged the 3k-tri `street_bin` GLB to Lane E as
|
||||
a decimation candidate (bounded by the sparse cadence for now). Shot: `docs/shots/laneB/bus_shelter.png`.
|
||||
|
||||
## Round 5 (Fable's ROUND5 → Lane B) — v2 window trick, behind `?winmap=1` · qa GREEN (4/4)
|
||||
|
||||
- **Parallax interior-mapping window glass** (V2_IDEAS "Vuntra window trick"), behind **`?winmap=1`
|
||||
|
||||
@ -1,5 +1,33 @@
|
||||
# LANE B — NOTES (measured)
|
||||
|
||||
## Round 6 (Fable's ROUND6 → Lane B) — place the orphaned street props
|
||||
|
||||
Placed the two published-but-unconsumed street GLBs (`procity_street_bin_01`, `procity_street_bus_shelter_01`)
|
||||
via the existing use-if-ready furniture GLB path (fail-soft to primitives; `?noassets` → primitives,
|
||||
**0 network** verified). All in `furniture.js` (+ a 3-line collider merge in `chunks.js`).
|
||||
|
||||
- **Bins** — genuinely sparse now (one loop, ~1 per block: `s += 78 m`, seeded side per edge), a country
|
||||
town not a mall. Decoupled from benches. GLB (3.0k tris) on its 0.57×0.68 footprint, primitive fallback.
|
||||
- **Bus shelters — on the street graph, and this is the future tram/bus-stop contract.** Deterministic
|
||||
rule: **the midpoint of every MAIN edge whose `hashEdge(id) % 3 === 0`, on the road-side footpath by the
|
||||
kerb, long axis along the edge, open front to the road** — yields **1–3 per town** (2 in seed 20261990).
|
||||
The GLB (2.33×2.4×1.41, 8.0k tris) gets a **solid oriented-rect collider** so the player rounds it
|
||||
(furniture now returns `colliders`; `chunks.js` merges them into `getColliders`). Collision-verified: a
|
||||
straight walk into it is blocked.
|
||||
- **→ whoever builds the V2 tram/bus loop:** call **`busShelterStops(plan)`** (exported from
|
||||
`furniture.js`) → `[{ edgeId, x, z, yaw }]`. It re-derives the stops from the exact same rule the
|
||||
shelters are placed by — pure function of the plan, zero side effects. That is the stop list; don't
|
||||
re-invent it. To move/space stops, change the one rule + `busShelterStops` together.
|
||||
- **Perf (measured, worst view WITH the rig-fleet citizens, midday):** **94k tris · 148 draws** — both
|
||||
well under the ≤200k / ≤300 gates (~100k tri and ~150 draw margin). Determinism holds (seed → same
|
||||
props). All-flags-on (`?winmap=1&dig=1&roster=stream`) composes clean, 0 console errors. Shot:
|
||||
`docs/shots/laneB/bus_shelter.png`. `qa.sh --strict` GREEN.
|
||||
- **→ Lane E (decimation candidate, not blocking):** the `street_bin` GLB is **3.0k tris** — heavy for
|
||||
a prop placed ~15×; I bounded it with the sparse cadence, but a decimate to ~500 tris would give back
|
||||
tri headroom if the combined-lane budget tightens. (Same class as the deferred `novelty_record`.)
|
||||
|
||||
---
|
||||
|
||||
## Round 5 (Fable's ROUND5 → Lane B) — v2: the Vuntra window trick, behind `?winmap=1`
|
||||
|
||||
- **Parallax interior-mapping glass** (V2_IDEAS "window trick"). Behind **`?winmap=1`, default-off.**
|
||||
|
||||
BIN
docs/shots/laneB/bus_shelter.png
Normal file
BIN
docs/shots/laneB/bus_shelter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 795 KiB |
@ -35,7 +35,10 @@ export function createChunkManager(plan, scene, ctx) {
|
||||
lastBuildMs = performance.now() - t0;
|
||||
scene.add(buildings.group, furniture.group);
|
||||
if (buildings.doorMesh) doorMeshes.push(buildings.doorMesh);
|
||||
live.set(key, { buildings, furniture, colliders: buildings.colliders, cx, cz });
|
||||
// player colliders = building/yard rects + any furniture keep-outs (bus shelters)
|
||||
const colliders = furniture.colliders && furniture.colliders.length
|
||||
? buildings.colliders.concat(furniture.colliders) : buildings.colliders;
|
||||
live.set(key, { buildings, furniture, colliders, cx, cz });
|
||||
// optional lifecycle hook (Lane D/F per-chunk spawning, ambient, LOD — LANE_F_NOTES §8).
|
||||
// Inert unless a consumer sets ctx.onChunkBuilt; citizens currently drive off plan.streets instead.
|
||||
if (ctx.onChunkBuilt) ctx.onChunkBuilt(key, { cx, cz, buildings: buildings.group, furniture: furniture.group, data });
|
||||
|
||||
@ -15,19 +15,24 @@ import { rng, frange } from '../core/prng.js';
|
||||
import { chunkOf, resolveEdges } from './planutil.js';
|
||||
|
||||
const FOOT = 3.5;
|
||||
// bus-shelter footprint (from manifest: 2.33 × 1.41 m) + a facing tweak applied to the along-edge
|
||||
// yaw so the open front reads toward the road (verified in-browser; π flips it if the GLB is mirrored).
|
||||
const SHELTER_W = 2.33, SHELTER_D = 1.41, SHELTER_YAW = 0;
|
||||
|
||||
// ── 3GOD-depot furniture GLB upgrade (fail-soft; ?noassets → primitives only, zero network) ──────
|
||||
// Each mapped type loads ONCE at module init. buildChunkFurniture checks `_glbReady[type]`
|
||||
// SYNCHRONOUSLY at build time — so a chunk built before the GLB lands uses its primitive, and every
|
||||
// chunk built after uses the GLB (streaming rebuilds pick it up). No async write into a live chunk.
|
||||
const NOASSETS = (() => { try { const p = new URLSearchParams(location.search); return p.has('noassets') && p.get('noassets') !== '0'; } catch { return false; } })();
|
||||
// Only LIGHT GLBs (≤5k tris/glb_law) are enabled — a heavy prop × many instances blows the ≤200k
|
||||
// tri gate. Measured: bench 1.9k ✓, food_cart 5.0k ✓, streetlight 5.0k ✓, but novelty_record is
|
||||
// **26.5k tris** (5× over) → it stays on the low-poly PRIMITIVE until Lane E decimates it (same
|
||||
// treatment as the ped rigs — see LANE_B_NOTES). Placement (record-store adjacency) is unchanged.
|
||||
// Only LIGHT GLBs (≤~8k tris) are enabled — a heavy prop × many instances blows the ≤200k tri gate.
|
||||
// Measured: bench 1.9k ✓, food_cart 5.0k ✓, bin 3.0k ✓, bus_shelter 8.0k ✓ (only 1–3 per town), but
|
||||
// novelty_record is **26.5k tris** (5× over) → it stays on the low-poly PRIMITIVE until Lane E
|
||||
// decimates it (same treatment as the ped rigs — see LANE_B_NOTES).
|
||||
const GLB_FILES = {
|
||||
bench: 'procity_street_bench_01.glb',
|
||||
food_cart: 'procity_street_food_cart_01.glb',
|
||||
bench: 'procity_street_bench_01.glb',
|
||||
food_cart: 'procity_street_food_cart_01.glb',
|
||||
bin: 'procity_street_bin_01.glb',
|
||||
bus_shelter: 'procity_street_bus_shelter_01.glb',
|
||||
};
|
||||
const _glbReady = {}; // type → { geo, mat } once (and if) the GLB loads and merges to one mesh
|
||||
|
||||
@ -161,6 +166,7 @@ export function buildChunkFurniture(chunkData, ctx, cx, cz) {
|
||||
const edges = ctx._edges || (ctx._edges = resolveEdges(ctx.plan));
|
||||
|
||||
const lists = { pole: [], lamp: [], bench: [], bin: [], busstop: [], tree: [], novelty: [], foodcart: [] };
|
||||
const colliders = []; // oriented-rect keep-outs (bus shelters) → player collision (via chunks.js)
|
||||
const here = (x, z) => { const c = chunkOf(x, z); return c.cx === cx && c.cz === cz; };
|
||||
|
||||
const pushYaw = (list, x, y, z, yaw, sx = 1, sy = 1, sz = 1) => {
|
||||
@ -189,14 +195,21 @@ export function buildChunkFurniture(chunkData, ctx, cx, cz) {
|
||||
}
|
||||
side = -side;
|
||||
}
|
||||
// benches + bins every ~26m on the building side of the footpath, facing the road
|
||||
// benches every ~26m on the building side of the footpath, facing the road
|
||||
for (let s = 14; s < len - 6; s += 26) {
|
||||
const bside = (Math.floor(s / 26) % 2) ? 1 : -1;
|
||||
const off = (halfRoad + FOOT - 0.8) * bside;
|
||||
const bx = e.ax + ux * s + px * off, bz = e.az + uz * s + pz * off;
|
||||
if (here(bx, bz)) pushYaw(lists.bench, bx, 0, bz, yaw + (bside > 0 ? Math.PI : 0));
|
||||
const nx = bx + ux * 1.4, nz = bz + uz * 1.4;
|
||||
if (here(nx, nz)) pushYaw(lists.bin, nx, 0, nz, 0);
|
||||
}
|
||||
// bins: genuinely sparse (~1 per block), seeded, one side of the footpath — a country town, not a
|
||||
// mall. Wide cadence also bounds the 3k-tri bin GLB's contribution to the ≤200k tri gate.
|
||||
const binH = hashEdge(e.id);
|
||||
for (let s = 30; s < len - 8; s += 78) {
|
||||
const bside = ((binH + Math.floor(s / 78)) % 2) ? 1 : -1;
|
||||
const off = (halfRoad + FOOT - 0.6) * bside;
|
||||
const x = e.ax + ux * s + px * off, z = e.az + uz * s + pz * off;
|
||||
if (here(x, z)) pushYaw(lists.bin, x, 0, z, 0);
|
||||
}
|
||||
// gum trees only along side/lane verges (main street has no verge room)
|
||||
if (e.kind === 'side' || e.kind === 'lane') {
|
||||
@ -208,11 +221,18 @@ export function buildChunkFurniture(chunkData, ctx, cx, cz) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// one bus stop near each main-street edge's near end
|
||||
if (e.kind === 'main') {
|
||||
const s = 10, off = (halfRoad + FOOT - 0.9);
|
||||
const x = e.ax + ux * s + px * off, z = e.az + uz * s + pz * off;
|
||||
if (here(x, z)) pushYaw(lists.busstop, x, 0, z, yaw + Math.PI);
|
||||
// Bus shelters — 1–3 per town, ON THE STREET GRAPH (they are the future tram/bus stops).
|
||||
// Deterministic rule (see busShelterStops() + LANE_B_NOTES): at the MIDPOINT of every MAIN edge
|
||||
// whose id hashes to 0 mod 3, on the road-side footpath near the kerb, long axis (2.33 m) ALONG
|
||||
// the edge, open front to the road. A solid collider so the player rounds it (collision-verified).
|
||||
if (e.kind === 'main' && hashEdge(e.id) % 3 === 0) {
|
||||
const off = halfRoad + 1.7; // on the footpath, hard by the kerb
|
||||
const sx = e.ax + ux * (len * 0.5) + px * off, sz = e.az + uz * (len * 0.5) + pz * off;
|
||||
const syaw = Math.atan2(-uz, ux) + SHELTER_YAW; // long axis along the edge (+ facing tweak)
|
||||
if (here(sx, sz)) {
|
||||
pushYaw(lists.busstop, sx, 0, sz, syaw);
|
||||
colliders.push({ x: sx, z: sz, ry: syaw, hw: SHELTER_W / 2, hd: SHELTER_D / 2, use: 'shelter' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,8 +267,8 @@ export function buildChunkFurniture(chunkData, ctx, cx, cz) {
|
||||
emit(t.pole, mat.dark, lists.pole);
|
||||
emit(t.lamp, mat.lamp, lists.lamp, false);
|
||||
emit(...glb('bench', t.bench, mat.wood), lists.bench);
|
||||
emit(t.bin, mat.bin, lists.bin);
|
||||
emit(t.busstop, mat.shelter, lists.busstop);
|
||||
emit(...glb('bin', t.bin, mat.bin), lists.bin);
|
||||
emit(...glb('bus_shelter', t.busstop, mat.shelter), lists.busstop);
|
||||
emit(t.treeGeo, mat.tree, lists.tree, false);
|
||||
emit(...glb('novelty_record', t.novelty, mat.novelty), lists.novelty);
|
||||
emit(...glb('food_cart', t.foodcart, mat.foodcart), lists.foodcart);
|
||||
@ -261,7 +281,22 @@ export function buildChunkFurniture(chunkData, ctx, cx, cz) {
|
||||
// shared templates/materials intentionally NOT disposed (reused by other chunks)
|
||||
group.clear();
|
||||
}
|
||||
return { group, applyNight, dispose };
|
||||
return { group, applyNight, dispose, colliders };
|
||||
}
|
||||
|
||||
function hashEdge(id) { const s = String(id); let h = 0; for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; return h >>> 0; } // String(): generatePlan uses numeric edge ids
|
||||
|
||||
// The town's bus/tram stops, derived from the SAME deterministic rule buildChunkFurniture places the
|
||||
// shelters by, so the future vehicle loop can enumerate stops without touching furniture internals.
|
||||
// Rule: midpoint of every MAIN edge whose id hashes to 0 mod 3, on the road-side footpath (kerb).
|
||||
// Returns [{ edgeId, x, z, yaw }] in plan order. Zero side effects; pure function of the plan.
|
||||
export function busShelterStops(plan) {
|
||||
const stops = [];
|
||||
for (const e of resolveEdges(plan)) {
|
||||
if (e.kind !== 'main' || hashEdge(e.id) % 3 !== 0) continue;
|
||||
const dx = e.bx - e.ax, dz = e.bz - e.az, len = Math.hypot(dx, dz) || 1;
|
||||
const ux = dx / len, uz = dz / len, px = -uz, pz = ux, off = e.width / 2 + 1.7;
|
||||
stops.push({ edgeId: e.id, x: e.ax + ux * (len * 0.5) + px * off, z: e.az + uz * (len * 0.5) + pz * off, yaw: Math.atan2(-uz, ux) + SHELTER_YAW });
|
||||
}
|
||||
return stops;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user