qa.sh --strict: 18 passed · 0 failed · 0 warn · 0 skipped. selfcheck 157,647/157,647, fingerprint 0x5f76e76 unmoved. No tag — John's playtest session rules the epoch. THE FIVE ASKS. [C] wardrobe wired via exported WARDROBE_BASE + stockBaseFor(shop), preloaded inside the existing STOCK_REAL gate: ON under ?stock=real (52 pack items, 69 garment ids on rendered meshes, 1 atlas, exactly 2 requests) and CHEAPER (59->55 draws, 111->100 textures); OFF is what ships by default, A/B'd against a reverted control tree — default arm identical on every counter (160 URLs same hash, 131/18,018 street, 59/28,505 interior). [D] three gates wired (r41_shots.py writes to TMPDIR so gate runs never rewrite committed shots). [E] clips_verify wired — 46 clips / 6 groups / 3,498,124 B. [B's bookmark finding] CONFIRMED AND WIDER: street_noon, shopfront_detail, crossroads_busy AND market_square all give 3 distinct hashes across 3 boots; only night_neon is stable. RULING: no gate pins a screenshot byte-hash on any bookmark — everything R41 asserts is a counter. Filed to B: one await document.fonts.ready in buildings.js. [C's drawSweep] CONFIRMED INDEPENDENTLY with F's own sweep (never calls C's): GLB-on worst 123 @ dept/auto. The phantom control (one stale room left in scene) reads 197 — +74 on every reading — decomposing R39/R40's '188' as 116 real + ~72 phantom. NEW GATES, controls demonstrated. r41_denylist.mjs (ruling 3): 738 files / 197.3 MiB scanned by path AND bytes, 7 banned names, 0 hits; the control plants a banned manifest row every run -> RED on 2 names -> removed -> GREEN. r41_integration.py: wardrobe both arms · interior <=350 on both instruments · ?noassets=1 zero across six fetch classes over a 3-shop walk (control fetches 5/6) · ?clips=0 142 URLs and ?classic=1 125 URLs with zero R41 cargo. BUDGETS. Street: noon 282 · NIGHT 291/120,093 — margin 9, INTACT · classic 269 byte-exact · ?clips=0 NIGHT 291, delta 0. No lane spent a street draw. Interior: true pre-R41 116, R41 123 @ dept/auto, margin 227; over 33 real shops worst 110; pub 49 quiet / 77 gig night. FILED BACK: flags_check's _enter_record_shop went RED on 8 smokes because §41.4's bins put every one of the first six record-shop counters inside 2.6 m — widened to the whole open set, verified not a game defect. smoke_djdance read _actions[0] and D's per-instance clip swapping made the mixer multi-action, so a healthy dancer was called a stall — now follows the clip by name. D's r41_shots.py browse arm is deterministically red here (re-enters the winner after a ~3 s/candidate scan, by which time the occupant has left) — wired WARN-level with the reason at the call site, fix handed to D. F's own r39_transmission.py had an unmeasured 30 s goto default that flaked once under a full strict run; pinned to 90 s. 11 proof frames in docs/shots/laneF_r41/ with sidecars: street_postures (5 rigs, 3 clips playing) against its ?clips=0 control (1 clip) · street_lean · browse_interior · pub_furnished + the honest kit before/after pair · record_dj_booth · opshop_wardrobe on/off · credits_panel with ODbL on screen. Every humanoid frame carries its R10 human-sized line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
355 lines
19 KiB
Python
355 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
"""PROCITY Lane F — R41 §41.6 INTEGRATION GATE: the seams F wired, and the budgets F is answerable for.
|
|
|
|
tools/.venv/bin/python tools/qa/r41_integration.py [--no-sweep]
|
|
|
|
Five arms, each carrying its own control (R25 vacuous-gate law — a gate that has never been able to
|
|
fail is a gate nobody can trust):
|
|
|
|
1. THE WARDROBE, BOTH ARMS Lane C's §41.4 op-shop pack, turned on by F's one line. ON
|
|
(F's ask #1 from C) (?stock=real): the room resolves base `assets/wardrobe/`, the
|
|
52-item pack, real garment ids/titles on rendered meshes, and the
|
|
GPU sampling the atlas file itself. OFF (default boot) is the
|
|
CONTROL: zero wardrobe requests, base null, zero garment ids —
|
|
the procedural canvas, which is what ships by default.
|
|
|
|
2. THE INTERIOR BUDGET, ON F'S 12 types x 6 archetypes, GLB on and GLB+stock=real, ≤350 law.
|
|
OWN INSTRUMENT This does NOT call PROCITY_C.drawSweep: it is F's own loop with
|
|
(F's ask #5 from C) F's own scene bookkeeping, because the thing C found broken in
|
|
R39/R40 was exactly the sweep's scene bookkeeping. Ships the
|
|
PHANTOM CONTROL: the same loop with one stale room deliberately
|
|
left in the scene reproduces the pre-R41 inflation, so "the
|
|
instrument was wrong, not the rooms" is a measurement here too.
|
|
|
|
3. ?noassets=1 STILL CLEAN Boot + enter a shop: zero GLB, zero clip GLB, zero motion
|
|
manifest, zero wardrobe, zero stock-pack requests. CONTROL: the
|
|
same walk on a ?stock=real boot fetches all of those classes.
|
|
|
|
4. ?clips=0 / ?classic=1 Neither boot may carry one byte of R41 cargo (clips, wardrobe,
|
|
CARRY NO R41 CARGO kit fittings via the manifest are C's, measured separately).
|
|
CONTROL: the default boot does fetch the clip groups.
|
|
|
|
5. DENY-LIST AT RUNTIME Ruling 3 again, from the other side: every URL the running game
|
|
requests across street + interior + gig night is checked against
|
|
the banned names. The static byte scan (r41_denylist.mjs) proves
|
|
nothing banned is IN web/; this proves nothing banned is ASKED
|
|
FOR. CONTROL: the matcher is shown firing on a synthetic URL.
|
|
|
|
Fresh headless context per arm, own port-isolated no-store server (this repo's ES-module cache burn).
|
|
Exit 0 green, 1 red.
|
|
"""
|
|
import sys, os, json, time, socket, subprocess, pathlib
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parent.parent.parent
|
|
PORT = int(os.environ.get('PROCITY_R41_INT_PORT', '8988'))
|
|
HOST = f'http://127.0.0.1:{PORT}'
|
|
SEED = 20261990
|
|
DRAW_LAW = 350
|
|
# The town is FROZEN for every measured arm: everything below moves on wall-clock time (R38's recorded
|
|
# trap), and a live town measured twice differs from itself.
|
|
FREEZE = 'pop=0&weather=0&tram=0&magpie=0&washing=0'
|
|
|
|
BANNED = ['character_kit_modular', 'exports/bodies/', 'elsa_coronation_hair_wig_kh3',
|
|
'daphne_sexy', 'mocaponline', 'sk_mannequin', 'anatomy/']
|
|
|
|
fails = []
|
|
def FAIL(m): fails.append(m); print(f" \033[31m✗ FAIL\033[0m {m}")
|
|
def OK(m): print(f" \033[32m✓\033[0m {m}")
|
|
def head(m): print(f"\n\033[1m{m}\033[0m")
|
|
def note(m): print(f" \033[33m·\033[0m {m}")
|
|
def check(cond, m):
|
|
(OK if cond else FAIL)(m)
|
|
return cond
|
|
|
|
NOSTORE = r'''
|
|
import sys, http.server, functools
|
|
class H(http.server.SimpleHTTPRequestHandler):
|
|
def end_headers(self):
|
|
self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate')
|
|
super().end_headers()
|
|
def log_message(self, *a): pass
|
|
http.server.HTTPServer(('127.0.0.1', int(sys.argv[1])),
|
|
functools.partial(H, directory=sys.argv[2])).serve_forever()
|
|
'''
|
|
|
|
|
|
def port_up(port):
|
|
with socket.socket() as s:
|
|
s.settimeout(0.4); return s.connect_ex(('127.0.0.1', port)) == 0
|
|
|
|
|
|
def serve(root, port):
|
|
proc = subprocess.Popen([sys.executable, '-c', NOSTORE, str(port), str(root)],
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
for _ in range(80):
|
|
if port_up(port): return proc
|
|
time.sleep(0.1)
|
|
proc.terminate(); raise SystemExit(f'could not serve on :{port}')
|
|
|
|
|
|
def new_page(p):
|
|
b = p.chromium.launch()
|
|
pg = b.new_page(viewport={'width': 1280, 'height': 720})
|
|
errs, reqs = [], []
|
|
pg.on('console', lambda m: errs.append(m.text) if m.type == 'error' else None)
|
|
pg.on('pageerror', lambda e: errs.append(str(e)))
|
|
pg.on('request', lambda r: reqs.append(r.url))
|
|
return b, pg, errs, reqs
|
|
|
|
|
|
def boot(pg, query):
|
|
pg.goto(f'{HOST}/index.html?seed={SEED}&dbg=1&{FREEZE}' + (('&' + query) if query else ''))
|
|
pg.wait_for_function('window.DBG && window.DBG.ready === true', timeout=45000)
|
|
pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }")
|
|
|
|
|
|
def paths(reqs):
|
|
"""server-relative paths only — blob:/data: URLs are canvas textures, not a fetch surface."""
|
|
return [u.split('?')[0].split(f':{PORT}/')[-1] for u in reqs if not u.startswith(('blob:', 'data:'))]
|
|
|
|
|
|
# ── arm 1: THE WARDROBE ──────────────────────────────────────────────────────────────────────────
|
|
def arm_wardrobe(p):
|
|
head('1. THE WARDROBE — Lane C §41.4 turned on by F, and the default arm that is its control')
|
|
out = {}
|
|
for q, label in (('stock=real', 'ON (?stock=real)'), ('', 'OFF (default boot)')):
|
|
b, pg, errs, reqs = new_page(p)
|
|
try:
|
|
boot(pg, q)
|
|
entered = pg.evaluate("() => window.DBG.enterShop('opshop')")
|
|
pg.wait_for_timeout(2500) # atlas decode + the room's first frames
|
|
si = pg.evaluate("() => window.DBG.stockInfo()")
|
|
inf = pg.evaluate("() => window.DBG.info()")
|
|
wr = [u for u in paths(reqs) if 'wardrobe' in u]
|
|
ids = [i for i in (si.get('renderedIds') or []) if str(i).startswith('wr_')]
|
|
tex = [t for t in (si.get('texUrls') or []) if 'wardrobe_atlas' in t]
|
|
out[label] = dict(si=si, inf=inf, wr=wr, ids=ids, tex=tex, errs=errs)
|
|
note(f"{label}: shop {entered.get('entered')} \"{entered.get('name')}\" · base={si.get('base')!r} · "
|
|
f"packItems={si.get('packItems')} · {inf.get('drawCalls')} draws / {inf.get('tris')} tris")
|
|
if q:
|
|
check(si.get('base') == 'assets/wardrobe/', f'{label}: room resolved base assets/wardrobe/ (got {si.get("base")!r})')
|
|
check(si.get('packItems') == 52, f'{label}: the 52-item pack is resolved AT BUILD TIME (getStockPack is sync)')
|
|
check(len(ids) > 0, f'{label}: {len(ids)} real garment ids on rendered meshes '
|
|
f'(e.g. {", ".join(str(t) for t in (si.get("renderedTitles") or [])[:3])})')
|
|
check(len(tex) == 1, f'{label}: the GPU is sampling the wardrobe atlas itself, {len(tex)} atlas texture')
|
|
check(sorted(wr) == ['assets/wardrobe/stock_opshop_index.json', 'assets/wardrobe/wardrobe_atlas_00.webp'],
|
|
f'{label}: exactly 2 wardrobe requests — the index and ONE atlas ({len(wr)} seen)')
|
|
else:
|
|
check(not wr, f'{label}: 0 wardrobe requests — THE CONTROL, and what ships by default')
|
|
check(si.get('base') is None and si.get('packItems') == 0,
|
|
f'{label}: base null, no pack — the procedural garment canvas, unchanged')
|
|
check(not ids, f'{label}: 0 wardrobe item ids rendered')
|
|
check(not errs, f'{label}: 0 console errors')
|
|
finally:
|
|
b.close()
|
|
on, off = out['ON (?stock=real)'], out['OFF (default boot)']
|
|
d_draw = on['inf']['drawCalls'] - off['inf']['drawCalls']
|
|
d_tex = on['inf']['textures'] - off['inf']['textures']
|
|
note(f"the swap is a WIN, not a cost: {off['inf']['drawCalls']} → {on['inf']['drawCalls']} draws ({d_draw:+d}), "
|
|
f"{off['inf']['textures']} → {on['inf']['textures']} textures ({d_tex:+d}) — 52 garments off ONE shared atlas")
|
|
check(d_draw <= 0 and d_tex <= 0, 'the wardrobe costs no draws and no textures (one atlas replaces N canvases)')
|
|
|
|
|
|
# ── arm 2: THE INTERIOR BUDGET, F'S OWN SWEEP ────────────────────────────────────────────────────
|
|
SWEEP = r"""
|
|
async ({ glb, stock, phantom }) => {
|
|
const S = window.PROCITY_C, THREE = S.THREE;
|
|
// F's OWN bookkeeping: strip EVERY interior out of the scene first, including the page's `current`.
|
|
// This is the exact step whose absence made R39/R40's numbers carry a phantom second room.
|
|
const strip = () => { for (const o of S.scene.children.filter(o => o.userData && o.userData.kind === 'interior')) S.scene.remove(o); };
|
|
strip();
|
|
let manifest = null;
|
|
if (glb) manifest = await fetch('assets/manifest.json?x=' + Date.now()).then(r => r.ok ? r.json() : null).catch(() => null);
|
|
// THE PHANTOM CONTROL: put one unrelated room in the scene and LEAVE IT THERE for the whole sweep —
|
|
// the pre-R41 behaviour, reproduced deliberately.
|
|
let ghost = null;
|
|
if (phantom) {
|
|
ghost = S.buildInterior({ id: 'record', type: 'record', seed: 1990, storeys: 1 }, THREE,
|
|
{ useGLB: glb, manifest, stock: stock ? 'real' : undefined });
|
|
S.scene.add(ghost.group); if (glb) await ghost.glbReady;
|
|
}
|
|
const measure = async (type, arch) => {
|
|
const r = S.buildInterior({ id: type, type, seed: 1990, storeys: 1 }, THREE,
|
|
{ archetype: arch, useGLB: glb, manifest,
|
|
stock: stock ? 'real' : undefined,
|
|
stockBase: (stock && type === 'opshop') ? S.WARDROBE_BASE : undefined });
|
|
S.scene.add(r.group); if (glb) await r.glbReady;
|
|
S.camera.position.set(r.dims.W * 0.3, r.dims.H * 0.6, r.dims.D / 2 - 0.8);
|
|
S.camera.lookAt(0, 0.9, -r.dims.D * 0.15);
|
|
S.renderer.info.reset(); S.renderer.render(S.scene, S.camera);
|
|
const d = S.renderer.info.render.calls;
|
|
S.scene.remove(r.group); r.dispose();
|
|
return d;
|
|
};
|
|
let worst = 0, worstAt = '', rooms = 0; const perType = {};
|
|
for (const t of S.SHOP_TYPES) {
|
|
let tw = 0;
|
|
for (const a of [undefined, ...S.ARCHETYPE_KEYS]) {
|
|
const d = await measure(t, a); rooms++;
|
|
if (d > tw) tw = d;
|
|
if (d > worst) { worst = d; worstAt = `${t}/${a || 'auto'}`; }
|
|
}
|
|
perType[t] = tw;
|
|
}
|
|
if (ghost) { S.scene.remove(ghost.group); ghost.dispose(); }
|
|
S.rebuild();
|
|
return { worst, worstAt, rooms, perType };
|
|
}
|
|
"""
|
|
|
|
|
|
def arm_budget(p):
|
|
head(f'2. THE INTERIOR BUDGET — F\'s own sweep (NOT PROCITY_C.drawSweep), 12 types x 6 archetypes, law ≤{DRAW_LAW}')
|
|
b, pg, errs, reqs = new_page(p)
|
|
try:
|
|
pg.goto(f'{HOST}/interior_test.html?seed={SEED}&stock=real')
|
|
pg.wait_for_function('window.PROCITY_C && window.PROCITY_C.SHOP_TYPES', timeout=45000)
|
|
pg.wait_for_timeout(1500) # let the page's own stock preloads resolve
|
|
res = {}
|
|
for label, opts in (('GLB off', dict(glb=False, stock=False, phantom=False)),
|
|
('GLB on', dict(glb=True, stock=False, phantom=False)),
|
|
('GLB on + stock=real', dict(glb=True, stock=True, phantom=False))):
|
|
r = pg.evaluate(SWEEP, opts)
|
|
res[label] = r
|
|
note(f"{label}: worst {r['worst']} @ {r['worstAt']} over {r['rooms']} rooms "
|
|
f"(margin {DRAW_LAW - r['worst']})")
|
|
check(r['worst'] <= DRAW_LAW, f'{label}: worst room {r["worst"]} ≤ {DRAW_LAW} — margin {DRAW_LAW - r["worst"]}')
|
|
# the headline number C published, independently re-derived
|
|
worst_on = res['GLB on']['worst']
|
|
check(res['GLB on']['worstAt'].startswith('dept'),
|
|
f"the worst room is a dept room ({res['GLB on']['worstAt']}) — C's §41.4 headline, on F's instrument")
|
|
# THE PHANTOM CONTROL — reproduce the pre-R41 sweep bug on purpose
|
|
ph = pg.evaluate(SWEEP, dict(glb=True, stock=False, phantom=True))
|
|
infl = ph['worst'] - worst_on
|
|
note(f"PHANTOM CONTROL (one stale room left in the scene, the pre-R41 behaviour): "
|
|
f"worst {ph['worst']} @ {ph['worstAt']} — inflation +{infl} draws")
|
|
check(infl > 0, f"the phantom is real and reproducible: leaving one room in the scene adds +{infl} draws to EVERY "
|
|
f"reading — C's §41.4-0 finding confirmed on an independent instrument")
|
|
check(ph['worst'] <= DRAW_LAW, f'even the inflated reading is under the law ({ph["worst"]} ≤ {DRAW_LAW}) — '
|
|
f'no historical number was ever a real breach')
|
|
check(not errs, f'0 console errors over {sum(r["rooms"] for r in res.values()) + ph["rooms"]} room builds')
|
|
return res, ph
|
|
finally:
|
|
b.close()
|
|
|
|
|
|
# ── arm 3: ?noassets=1 ───────────────────────────────────────────────────────────────────────────
|
|
CARGO = {
|
|
'GLB (props/rigs)': lambda u: u.endswith('.glb'),
|
|
'clip group': lambda u: '/models/clips/' in u,
|
|
'motion manifest': lambda u: 'motion_manifest.json' in u,
|
|
'wardrobe': lambda u: 'assets/wardrobe/' in u,
|
|
'stock pack': lambda u: 'stock_' in u and u.endswith('_index.json'),
|
|
'asset manifest': lambda u: u.endswith('assets/manifest.json'),
|
|
}
|
|
|
|
|
|
def walk_and_classify(p, q):
|
|
b, pg, errs, reqs = new_page(p)
|
|
try:
|
|
boot(pg, q)
|
|
pg.evaluate("() => window.DBG.shot('street_noon')")
|
|
for sel in ('opshop', 'record', 'pub'):
|
|
pg.evaluate(f"() => window.DBG.enterShop('{sel}')")
|
|
pg.wait_for_timeout(700)
|
|
pg.evaluate("() => window.DBG.exitShop()")
|
|
pg.wait_for_timeout(200)
|
|
pg.wait_for_timeout(800)
|
|
ps = paths(reqs)
|
|
return {k: [u for u in ps if f(u)] for k, f in CARGO.items()}, ps, errs
|
|
finally:
|
|
b.close()
|
|
|
|
|
|
def arm_noassets(p):
|
|
head('3. ?noassets=1 — the asset law, over a walk that enters three shops (op shop, record, pub)')
|
|
clean, ps_clean, errs_clean = walk_and_classify(p, 'noassets=1')
|
|
for k, hits in clean.items():
|
|
check(not hits, f'?noassets=1: 0 {k} requests' + (f' — SAW {hits[:3]}' if hits else ''))
|
|
check(not errs_clean, f'?noassets=1: 0 console errors ({len(ps_clean)} requests swept)')
|
|
ctrl, ps_ctrl, _ = walk_and_classify(p, 'stock=real')
|
|
got = [k for k, v in ctrl.items() if v]
|
|
note(f'CONTROL (?stock=real, same walk): {len(ps_ctrl)} requests, cargo classes present = {got}')
|
|
check(len(got) >= 5, f'the classifier is not vacuous — the control boot fetches {len(got)}/6 cargo classes')
|
|
return clean, ctrl
|
|
|
|
|
|
# ── arm 4: ?clips=0 / ?classic=1 ─────────────────────────────────────────────────────────────────
|
|
def arm_clip_gates(p):
|
|
head('4. ?clips=0 and ?classic=1 — neither boot may carry one byte of R41 cargo')
|
|
res = {}
|
|
for q, label in (('clips=0', '?clips=0'), ('classic=1', '?classic=1'), ('', 'default')):
|
|
b, pg, errs, reqs = new_page(p)
|
|
try:
|
|
boot(pg, q)
|
|
pg.wait_for_timeout(2000) # a wrongly-gated dynamic import has time to land
|
|
ps = paths(reqs)
|
|
clips = [u for u in ps if '/models/clips/' in u or 'motion_manifest.json' in u or 'clipbank.js' in u]
|
|
wr = [u for u in ps if 'assets/wardrobe/' in u]
|
|
stats = pg.evaluate("() => (window.PROCITY.citizens && window.PROCITY.citizens.clipStats) "
|
|
"? window.PROCITY.citizens.clipStats() : null")
|
|
res[label] = dict(n=len(set(ps)), clips=clips, wr=wr, stats=stats, errs=errs)
|
|
note(f'{label}: {len(set(ps))} distinct URLs · clip cargo {len(clips)} · wardrobe {len(wr)} · clipStats {stats}')
|
|
if q:
|
|
check(not clips, f'{label}: 0 clip GLBs, 0 motion manifest, 0 clipbank.js')
|
|
check(not wr, f'{label}: 0 wardrobe requests')
|
|
check(not errs, f'{label}: 0 console errors')
|
|
finally:
|
|
b.close()
|
|
check(len(res['default']['clips']) > 0,
|
|
f"CONTROL: the default boot DOES fetch the clip library ({len(res['default']['clips'])} requests) — "
|
|
f"so the two zero-arms above are a gate, not a tautology")
|
|
return res
|
|
|
|
|
|
# ── arm 5: the deny-list, from the network side ──────────────────────────────────────────────────
|
|
def arm_denylist_runtime(p):
|
|
head('5. DENY-LIST AT RUNTIME (ruling 3) — nothing banned is even ASKED FOR, across a full walk')
|
|
b, pg, errs, reqs = new_page(p)
|
|
try:
|
|
boot(pg, 'stock=real&gigs=1')
|
|
pg.evaluate("() => window.DBG.setSegment(5)")
|
|
pg.evaluate("() => window.DBG.shot('venue_night')")
|
|
for sel in ('record', 'pub', 'opshop'):
|
|
pg.evaluate(f"() => window.DBG.enterShop('{sel}')")
|
|
pg.wait_for_timeout(900)
|
|
pg.evaluate("() => window.DBG.exitShop()")
|
|
pg.wait_for_timeout(200)
|
|
pg.wait_for_timeout(1200)
|
|
ps = paths(reqs)
|
|
hits = [u for u in ps if any(t in u.lower() for t in BANNED)]
|
|
check(not hits, f'{len(set(ps))} distinct URLs across street + gig night + 3 interiors — 0 banned names'
|
|
+ (f' — SAW {hits[:3]}' if hits else ''))
|
|
probe = 'assets/models/character_kit_modular/exports/bodies/body_07.glb'
|
|
check(any(t in probe.lower() for t in BANNED), 'CONTROL: the matcher fires on a synthetic banned URL')
|
|
check(not errs, '0 console errors over the walk')
|
|
finally:
|
|
b.close()
|
|
|
|
|
|
def main():
|
|
proc = serve(ROOT / 'web', PORT)
|
|
try:
|
|
from playwright.sync_api import sync_playwright
|
|
with sync_playwright() as p:
|
|
arm_wardrobe(p)
|
|
if '--no-sweep' not in sys.argv:
|
|
arm_budget(p)
|
|
arm_noassets(p)
|
|
arm_clip_gates(p)
|
|
arm_denylist_runtime(p)
|
|
finally:
|
|
proc.terminate()
|
|
print('')
|
|
if fails:
|
|
print(f'\033[31m● FAIL\033[0m — {len(fails)} problem(s):')
|
|
for f in fails: print(f' · {f}')
|
|
return 1
|
|
print('\033[32m● PASS\033[0m — wardrobe both arms, interior budget on F\'s own instrument, '
|
|
'?noassets clean, clip gates cargo-free, deny-list clean at runtime')
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|