Lane F R7 F2: harness → strict + wire ?stock=real (real GODVERSE covers in the crates)
qa.sh: flags harness warn_gate → run_gate — flags-off regression + the 4 existing flags +
all-on combo are now STRICT (a harness FAIL fails qa). New flags stay warn-level in-harness.
?stock=real wiring (F owns the shell + interior bridge): shell preloads the record stock pack
(preloadStockPack('record'), fail-soft, off under ?noassets) + passes stockReal to the bridge.
interior_mode builds makeStockAdapter(getStockPack(shop.type)) and passes it to BOTH buildInterior
(static crate sleeves) and dig.open (riffle) — null pack → parody canvas (Lane C fail-soft).
Harness: added stock=real smoke (assert non-parody = batched userData.isStock atlas meshes > 0;
verified 7 real sleeves in the record shop) + weather=1 smoke (auto-skips until Lane B lands),
both warn-level (decision #4). qa.sh --strict GREEN (5 gates); crates now show real covers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0b84560457
commit
b94f86b49f
@ -12,8 +12,8 @@ C1 `?stock=real` (`3724a65`) all landed. F executes flip → strict harness →
|
||||
| task | status |
|
||||
|---|---|
|
||||
| **F1 — roster flip (prime-law amendment)** | ✅ **DONE + verified.** `index.html`: streamed roster is now the **default** (`chunkStream:{radius:2}` per D's spec); `?roster=v1` = escape hatch (fixed roster); `?roster=stream` still accepted (no-op). Hours-aware `setNightLivelyChunks` on the open-late block. **Same commit** re-pins the flags-off baseline: **old (163 draws/19k tris, roster-off) → new (164 draws/~66k tris, roster-on)** — draws barely move (E1's merge = ~1 draw/rig, the whole point), tris = the crowd (D worst-view 65,899; drift-WARN now draws-only, tris under the hard cap). **Plan goldens UNCHANGED (`0x3fa36874`).** Also fixed the harness's roster probe (was reading a nonexistent prop → blind) + flipped the assertion to require streamMode ON + added an escape-hatch check. Verified: default boot streamMode:true (146–150 active), `?roster=v1` streamMode:false (96), both render, 0 errors, ≤300 draws, harness GREEN. |
|
||||
| **F2 — harness to strict** | ⏳ next: flags failures fail qa.sh; add `stock=real` + `weather=1` smokes (new flags at warn). |
|
||||
| **F3 — tag v2.0-beta** | ⏳ after F2 + B weather / A osm-parity land (decision #6). |
|
||||
| **F2 — harness to strict + new-flag smokes** | ✅ **DONE + verified.** `qa.sh` flags gate `warn_gate → run_gate` — the harness (flags-off regression + 4 existing flags + all-on combo) is now **STRICT** (a FAIL fails qa). Wired **`?stock=real`** (F owned the seam): shell preloads the record pack + passes `stockReal`; `interior_mode` builds `makeStockAdapter(getStockPack(shop.type))` and passes it to **both** `buildInterior` (static crate sleeves) and `dig.open` (riffle) — fail-soft to parody if the pack's absent. Added `stock=real` + `weather=1` smokes at **warn-level** (decision #4). Verified: crates show **real GODVERSE covers** (smoke: 7 real atlas sleeve meshes, non-parody batched); weather auto-skips (Lane B not landed); `qa.sh --strict` GREEN (5 gates). |
|
||||
| **F3 — tag v2.0-beta** | ⏳ after B weather / A osm-parity land (decision #6). Flip + stock done; harness strict-green. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
BIN
docs/shots/laneF/stock_real_dig.png
Normal file
BIN
docs/shots/laneF/stock_real_dig.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
@ -245,6 +245,54 @@ def interior_budget(p):
|
||||
finally:
|
||||
b.close()
|
||||
|
||||
def smoke_stock(p):
|
||||
"""NEW FLAG (warn-level, decision #4): ?stock=real → real GODVERSE sleeves in the crates.
|
||||
Assert non-parody: real sleeves are batched atlas meshes tagged userData.isStock (Lane C recipe)."""
|
||||
head('SMOKE: stock=real (?stock=real — real sleeves; new flag → warn-level)')
|
||||
b, pg, errs = new_page(p)
|
||||
try:
|
||||
boot(pg, 'stock=real&dig=1')
|
||||
ready = False
|
||||
try: # the shell preloads the pack async — wait for getStockPack('record') to resolve
|
||||
pg.wait_for_function(
|
||||
"async () => { const m = await import('./js/interiors/interiors.js');"
|
||||
" return !!(m.getStockPack && m.getStockPack('record')); }", timeout=12000)
|
||||
ready = True
|
||||
except Exception:
|
||||
pass
|
||||
res = pg.evaluate("""() => {
|
||||
const P=window.PROCITY, D=window.DBG; D.setSegment(2);
|
||||
const rec=(P.plan.shops||[]).find(s=>s.type==='record' && P.isOpen(s));
|
||||
if(!rec) return {err:'no open record shop'};
|
||||
D.enterShop(rec.id);
|
||||
let n=0; P.interiorMode.current.group.traverse(o=>{ if(o.userData&&o.userData.isStock&&o.material&&o.material.map) n++; });
|
||||
return { shop: rec.name, isStock: n };
|
||||
}""")
|
||||
if res.get('err'): WARN(f"stock=real: {res['err']}")
|
||||
elif not ready: WARN(f"stock=real: pack didn't preload in 12s — parody fallback (fail-soft); {res['isStock']} isStock meshes")
|
||||
elif res['isStock'] > 0: OK(f"stock=real: {res['isStock']} real atlas sleeve mesh(es) in '{res['shop']}' — non-parody, batched")
|
||||
else: WARN(f"stock=real: pack loaded but 0 isStock atlas meshes in '{res['shop']}'")
|
||||
if errs: WARN(f"stock=real: {len(errs)} console error(s); first: {errs[0][:140]}")
|
||||
finally:
|
||||
b.close()
|
||||
|
||||
def smoke_weather(p):
|
||||
"""NEW FLAG (warn-level): ?weather=1 (Lane B). Auto-skips if not landed yet."""
|
||||
b, pg, errs = new_page(p)
|
||||
try:
|
||||
boot(pg, 'weather=1')
|
||||
w = pg.evaluate("() => { const P=window.PROCITY||{}; const w=P.weather||(P.lighting&&P.lighting.weather); "
|
||||
"return w ? { active:!!w.active, particles:!!(w.particles||w.rain), wet:!!w.wet } : null; }")
|
||||
if not w:
|
||||
print("· weather=1 not landed (no weather subsystem) — skipping (Lane B)")
|
||||
return
|
||||
head('SMOKE: weather=1 (new flag → warn-level)')
|
||||
if w['active'] and w['particles']: OK(f"weather=1 active with particle layer: {w}")
|
||||
else: WARN(f"weather=1 present but incomplete: {w}")
|
||||
if errs: WARN(f"weather=1: {len(errs)} console error(s); first: {errs[0][:140]}")
|
||||
finally:
|
||||
b.close()
|
||||
|
||||
def main():
|
||||
srv = ensure_server()
|
||||
try:
|
||||
@ -260,6 +308,8 @@ def main():
|
||||
for fl in flags:
|
||||
smoke(p, fl.split('=')[0], fl)
|
||||
smoke(p, 'ALL-ON combo', '&'.join(flags), is_combo=True)
|
||||
smoke_stock(p) # new flag (warn-level) — ?stock=real
|
||||
smoke_weather(p) # new flag (warn-level) — ?weather=1 (auto-skips if not landed)
|
||||
finally:
|
||||
if srv: srv.terminate()
|
||||
|
||||
|
||||
@ -82,13 +82,15 @@ fi
|
||||
|
||||
# ── Gate 4 (warn-level, round 6): v2 flag enforcement harness ────────────────
|
||||
# Flags-off regression (prime-law) + per-flag + all-on-combo smokes. Needs the Playwright venv +
|
||||
# a browser (self-contained server), auto-skips where absent. Non-blocking this round; strict in r7.
|
||||
# a browser (self-contained server), auto-skips where absent. STRICT as of R7 (F2): a harness FAIL
|
||||
# now fails qa (flags-off regression + the four existing flags + all-on combo). New flags (stock,
|
||||
# weather) report at warn inside the harness, so they never trip this gate until they graduate.
|
||||
# Skip explicitly with --no-flags (fast determinism-only runs).
|
||||
FLAGS_SKIP=0; for a in "$@"; do [ "$a" = "--no-flags" ] && FLAGS_SKIP=1; done
|
||||
if [ "$FLAGS_SKIP" = 1 ]; then
|
||||
soft_skip "v2 flags harness" "--no-flags"
|
||||
elif [ -x tools/.venv/bin/python ] && tools/.venv/bin/python -c "import playwright" 2>/dev/null; then
|
||||
warn_gate "v2 flags harness (flags-off regression · per-flag · all-on combo)" \
|
||||
run_gate "v2 flags harness (flags-off regression · per-flag · all-on combo · STRICT r7)" \
|
||||
tools/.venv/bin/python tools/flags_check.py
|
||||
else
|
||||
soft_skip "v2 flags harness" "Playwright venv absent (python3 -m venv tools/.venv && …/pip install playwright)"
|
||||
|
||||
@ -55,7 +55,7 @@ import { createMinimap } from './js/world/minimap.js';
|
||||
import { createInteriorMode } from './js/world/interior_mode.js'; // [Lane F integration] Lane C interior bridge
|
||||
import { CitizenSim } from './js/citizens/sim.js'; // [Lane F integration] Lane D street pedestrians
|
||||
import { loadPedFleet } from './js/citizens/rigs.js'; // [Lane F §3.4] Lane D rig fleet (GLB peds/keepers)
|
||||
import { preloadManifest } from './js/interiors/interiors.js'; // [Lane F §3.4] Lane E manifest → interior GLB upgrades
|
||||
import { preloadManifest, preloadStockPack } from './js/interiors/interiors.js'; // [Lane F] Lane E manifest + Lane C/E stock pack
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('[procity] init failed:', err);
|
||||
@ -142,7 +142,11 @@ if (!NOASSETS) preloadManifest(); // prime the manifest so the first interior
|
||||
// [Lane F integration] interior bridge — owns the 'interior' branch (Lane C buildInterior + Lane D keeper)
|
||||
// [Lane F §F2] ?dig=1 enables Lane C's crate-riffle inside record interiors (v2). Off ⇒ byte-identical to v1.
|
||||
const DIG_ON = params.has('dig') && params.get('dig') !== '0';
|
||||
const interiorMode = createInteriorMode({ THREE, renderer, camera, plan, fleet, useGLB: !NOASSETS, dig: DIG_ON });
|
||||
// [Lane F §F2 stock=real] real GODVERSE sleeves in the crates (v2). Preload the record pack at boot so
|
||||
// the first interior/dig can batch real covers; fail-soft (missing pack → parody canvas). Off under ?noassets.
|
||||
const STOCK_REAL = params.get('stock') === 'real' && !NOASSETS;
|
||||
if (STOCK_REAL) preloadStockPack('record');
|
||||
const interiorMode = createInteriorMode({ THREE, renderer, camera, plan, fleet, useGLB: !NOASSETS, dig: DIG_ON, stockReal: STOCK_REAL });
|
||||
// [Lane F §F2] the riffle is cursor-driven (DOM buttons + click-a-sleeve), so release pointer-lock while it's
|
||||
// open and re-lock (on click) after. digActive gates the unlock→leaveShop guard below so opening a bin
|
||||
// doesn't read as "walked out of the shop".
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
// The interior renders into its OWN dark THREE.Scene (the street scene is frozen, not disposed —
|
||||
// CITY_SPEC L3 "pause, not dispose"), so returning to the street is instant.
|
||||
|
||||
import { buildInterior } from '../interiors/interiors.js';
|
||||
import { buildInterior, getStockPack, makeStockAdapter } from '../interiors/interiors.js';
|
||||
import { KeeperManager } from '../citizens/keepers.js'; // Lane D — shopkeeper behind the counter
|
||||
import { createDig, binSeed } from '../interiors/dig.js'; // Lane C — crate-riffle (v2, gated on ?dig=1)
|
||||
|
||||
@ -25,7 +25,8 @@ const SPEED = 3.2; // interior walk speed (matches the Lane C tester)
|
||||
const ARM_DIST = 2.0; // must step this far from the door before the exit arms (spawn is ~1.5m away)
|
||||
const EXIT_DIST = 1.0; // …then coming back within this distance leaves to the street
|
||||
|
||||
export function createInteriorMode({ THREE, renderer, camera, plan, fleet = null, useGLB = false, dig: digEnabled = false }) {
|
||||
export function createInteriorMode({ THREE, renderer, camera, plan, fleet = null, useGLB = false,
|
||||
dig: digEnabled = false, stockReal = false }) {
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color('#0e0b07');
|
||||
|
||||
@ -35,6 +36,7 @@ export function createInteriorMode({ THREE, renderer, camera, plan, fleet = null
|
||||
|
||||
let current = null; // active Lane C interior handle
|
||||
let currentShop = null; // the shop record for the active interior (dig per-bin seeding)
|
||||
let currentAdapter = null; // [F2 §stock] ?stock=real stockAdapter for this shop (null → parody fallback)
|
||||
let doorReturn = null; // { x, y, z, ry } on the street to restore on exit
|
||||
let exitArmed = false; // disarmed at spawn (spawn sits by the door); arms once player steps inside
|
||||
let dig = null; // [F2] Lane C crate-riffle instance, created lazily on first use (only if ?dig=1)
|
||||
@ -78,6 +80,7 @@ export function createInteriorMode({ THREE, renderer, camera, plan, fleet = null
|
||||
seed: binSeed((currentShop && currentShop.seed) || 1, key), count: 16,
|
||||
shopName: (current && current.recipe && current.recipe.label) || (currentShop && currentShop.name),
|
||||
shop: currentShop,
|
||||
stockAdapter: currentAdapter, // [F2 §stock=real] real covers in the riffle (null → parody)
|
||||
onClose: () => window.dispatchEvent(new CustomEvent('procity:digClose')),
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent('procity:digOpen')); // shell releases pointer-lock for the cursor
|
||||
@ -123,7 +126,10 @@ export function createInteriorMode({ THREE, renderer, camera, plan, fleet = null
|
||||
// enter: build the room, show it, drop the player just inside the door. Returns the Lane C handle.
|
||||
function enter(shop, name) {
|
||||
if (current) exit(); // never stack interiors
|
||||
current = buildInterior(shop, THREE, { useGLB }); // [Lane F §3.4] depot GLB fittings when assets are on
|
||||
// [F2 §stock=real] real GODVERSE sleeves in this shop's crates. makeStockAdapter(null) → null →
|
||||
// Lane C fails soft to the parody canvas, so a missing/unpreloaded pack just shows placeholders.
|
||||
currentAdapter = stockReal ? makeStockAdapter(getStockPack(shop.type)) : null;
|
||||
current = buildInterior(shop, THREE, { useGLB, stockAdapter: currentAdapter }); // static room sleeves
|
||||
currentShop = shop;
|
||||
scene.add(current.group);
|
||||
// Lane D keeper at the counter — Lane C tags the stand pose on the counter interactable
|
||||
@ -191,6 +197,7 @@ export function createInteriorMode({ THREE, renderer, camera, plan, fleet = null
|
||||
current.dispose();
|
||||
current = null;
|
||||
currentShop = null;
|
||||
currentAdapter = null;
|
||||
banner.style.display = 'none';
|
||||
if (doorReturn) {
|
||||
camera.position.set(doorReturn.x, doorReturn.y, doorReturn.z);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user