From 9a26f9ab8c6691ba3cbe32ac4bc4525f4a9c7106 Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Thu, 15 Oct 2026 18:30:42 +0900 Subject: [PATCH] =?UTF-8?q?P23-03:=20=E7=94=9F=E6=B4=BB=E3=81=AE=E8=A6=B3?= =?UTF-8?q?=E5=AF=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../p23-03/REPAIR28_TRAFFIC_PROJECTION.md | 26 + .../p23-03/repair28_frame180_projection.json | 2011 +++++++++++++++++ tools/check_p23_life_observation_evidence.py | 38 +- tools/test_p23_life_observation_evidence.py | 101 +- 4 files changed, 2172 insertions(+), 4 deletions(-) create mode 100644 docs/plans/original-completion-2026-10-02/execution/p23-03/REPAIR28_TRAFFIC_PROJECTION.md create mode 100644 tests/fixtures/p23-03/repair28_frame180_projection.json diff --git a/docs/plans/original-completion-2026-10-02/execution/p23-03/REPAIR28_TRAFFIC_PROJECTION.md b/docs/plans/original-completion-2026-10-02/execution/p23-03/REPAIR28_TRAFFIC_PROJECTION.md new file mode 100644 index 0000000..cddc75d --- /dev/null +++ b/docs/plans/original-completion-2026-10-02/execution/p23-03/REPAIR28_TRAFFIC_PROJECTION.md @@ -0,0 +1,26 @@ +# Repair 28: traffic projection checker + +Scope: checker, its Python tests, one minimal decoded fixture and this note. Renderer, Runtime, save paths and controller unchanged. No git writes, Godot/native/OS, Runner or full regression operations. + +## Separate routing and defect records + +- Normal negative validate 97b6cdf9 failed because the current index was absent. Historical 49 was preserved by canonical rename retaining index SHA c0cc9e…. That missing-current-proof result is not evidence of this static defect. +- Independent readiness 887f9684 (READINESS_19FC3F5_ACTUAL.json) identifies the actual checker overconstraint. ROOT_19FC3F5_BAND_MISMATCH_REPRO.json and OPUS_19FC3F5_BAND_SCOPE_DIRECTION.json describe its repro and authorized scope. + +All seven repair27 input files were read/decoded directly and their byte counts and SHA256 checked against MANIFEST.json (original paths retained in fixture provenance). The original frame-180 landing densities remain zero. The new 45 KB fixture contains only the unchanged decoded fields used by overlay validation: report frame/source metadata, actor drawings, cabins, traffic and camera/elevator track fields; raw world bounds, elevators and staff tasks. It is an extraction, not a raw receipt. No unused megabytes of state, rest or motion were copied. The positive test is explicitly COUNTERFACTUAL: only its in-memory report densities at (0,1,0) and (1,1,0) become 1, covering guests 018/019. It grants no engine/OS acceptance. + +## Correction and caller audit + +compare_traffic passes its actual snapshot into validate_life_overlay(report, root=ROOT, snapshot=None). inspect_stream already binds that state to its raw object and invokes compare_traffic; native layout and OS writer/reader stream validation therefore receive raw authority. The controller run_p23_life_observation.py has no direct validate_life_overlay/compare_traffic caller. Offline/OS drawing paths already use drawing_context to bind report/state/rest and retain compare_rest, compare_cabin_geometry and compare_actor_geometry. No new drawing gate was added there. Existing direct unit callers without state remain fail closed. + +Cabin exemptions apply only to passenger entries belonging to that same raw shaft; cabin geometry reuses compare_cabin_geometry. A floor entry cannot inherit a passenger exemption by copying its key. Carried exemptions require actual active rescue transport binding and reuse compare_actor_geometry for original binding, track and raised-region fitting, excluding boarded/queue placements. Existing rest-authority checks remain unchanged. Missing-carrier KeyError remains deferred and fails closed. + +Serial, consumed serial, reported passes, source child/pass order, source connection/parent restrictions, band rectangle and same-report checks remain unchanged. Floor cores keep the same strict intersection predicate. Every intersecting actor contributes a clipped horizontal interval. Sorted interval union must leave a nonzero uncovered span; no ratio or threshold was introduced. + +## Local verification + +- Python 3 -B compile() of both changed Python files: passed without writing bytecode. +- Entire checker suite: 108 tests passed (all 99 existing plus 9 new); original R13 row11/29 negative retained. New tests cover counterfactual cabin success, occupied and blocked full union occlusion, spoofed cabin rectangle/membership, absent raw state, floor copy of cabin key, missing carrier binding, and carried binding/track/position mutations. Existing reversed report/source pass tests remain. +- git diff --check: passed. + +Root native/component, A/B on both layouts, normal OS, one full177 and independent Opus all-condition pixel review are expected after submission and do not block worker submission. No such unexecuted checks are claimed passed. Registered timeout 900 s, seven conditions, six categories, 30/88 and formal credit 0 remain unchanged. diff --git a/tests/fixtures/p23-03/repair28_frame180_projection.json b/tests/fixtures/p23-03/repair28_frame180_projection.json new file mode 100644 index 0000000..f508b8d --- /dev/null +++ b/tests/fixtures/p23-03/repair28_frame180_projection.json @@ -0,0 +1,2011 @@ +{ + "description": "Unmodified relevant decoded fields from actual frame-180; tests apply COUNTERFACTUAL density=1 in memory. Not engine acceptance evidence or raw receipts.", + "provenance": { + "candidate": "19fc3f50c7ef66223a86b438d4327c5b1b308ed1", + "capture": { + "acceptance": "pending", + "input_method": "diagnostic", + "label": "frame-180", + "motion": "38fa0ea73899e16ded98c14d06c13e4630db2a74a3b4547e0b6d6b2e95dda12f", + "pid": 17350, + "report": "eeabe1205307cf7a460dba8b320c3e284c41223b16ee6887fa31e2018414e8eb", + "rest_audit": "74e6f40cdfa031587896827cdaaa8fab67d953dbb7aab42a83ca1c6c993825e2", + "state": "182f098f5c0072bf5a83572930a6a82a8512cbd1da0a5c386a05cdc76ec0cb33", + "state_after": "182f098f5c0072bf5a83572930a6a82a8512cbd1da0a5c386a05cdc76ec0cb33", + "ticks_usec": 6391490 + }, + "files": [ + { + "path": "objects/182f098f5c0072bf5a83572930a6a82a8512cbd1da0a5c386a05cdc76ec0cb33.json", + "original": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-03-native-impact-19fc3f5-r01/layout-A/objects/182f098f5c0072bf5a83572930a6a82a8512cbd1da0a5c386a05cdc76ec0cb33.json", + "bytes": 1036025, + "sha256": "182f098f5c0072bf5a83572930a6a82a8512cbd1da0a5c386a05cdc76ec0cb33" + }, + { + "path": "objects/38fa0ea73899e16ded98c14d06c13e4630db2a74a3b4547e0b6d6b2e95dda12f.json", + "original": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-03-native-impact-19fc3f5-r01/layout-A/objects/38fa0ea73899e16ded98c14d06c13e4630db2a74a3b4547e0b6d6b2e95dda12f.json", + "bytes": 66789, + "sha256": "38fa0ea73899e16ded98c14d06c13e4630db2a74a3b4547e0b6d6b2e95dda12f" + }, + { + "path": "objects/74e6f40cdfa031587896827cdaaa8fab67d953dbb7aab42a83ca1c6c993825e2.json", + "original": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-03-native-impact-19fc3f5-r01/layout-A/objects/74e6f40cdfa031587896827cdaaa8fab67d953dbb7aab42a83ca1c6c993825e2.json", + "bytes": 286, + "sha256": "74e6f40cdfa031587896827cdaaa8fab67d953dbb7aab42a83ca1c6c993825e2" + }, + { + "path": "objects/eeabe1205307cf7a460dba8b320c3e284c41223b16ee6887fa31e2018414e8eb.json", + "original": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-03-native-impact-19fc3f5-r01/layout-A/objects/eeabe1205307cf7a460dba8b320c3e284c41223b16ee6887fa31e2018414e8eb.json", + "bytes": 106546, + "sha256": "eeabe1205307cf7a460dba8b320c3e284c41223b16ee6887fa31e2018414e8eb" + }, + { + "path": "READINESS_19FC3F5_ACTUAL.json", + "original": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/execution/p23-03/READINESS_19FC3F5_ACTUAL.json", + "bytes": 9457, + "sha256": "4eaf2e4c82168d701734802c704c9687232376732ce0f9e28766f395f7cc21b0" + }, + { + "path": "ROOT_19FC3F5_BAND_MISMATCH_REPRO.json", + "original": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/execution/p23-03/ROOT_19FC3F5_BAND_MISMATCH_REPRO.json", + "bytes": 1770, + "sha256": "0d311c447b2d82bd604e3023b30178e5b74dc2a5275590e59305aed9df28c7b6" + }, + { + "path": "OPUS_19FC3F5_BAND_SCOPE_DIRECTION.json", + "original": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/execution/p23-03/OPUS_19FC3F5_BAND_SCOPE_DIRECTION.json", + "bytes": 4465, + "sha256": "ce2df30ad24085ae8122cc599613ff14a4a00a3306dd185eef17b67566ee0cd1" + } + ], + "purpose": "Immutable actual diagnostic context plus explicitly counterfactual band test; not normal OS/pixel acceptance" + }, + "report": { + "observation_serial": 22, + "overlay_consumed_serial": 22, + "overlay_passes": [ + "traffic", + "actors" + ], + "overlay_child_indices": [ + 0, + 1, + 2 + ], + "actor_drawings": [ + { + "key": "guest/p23-020", + "center": [ + 576.0, + 621.0 + ], + "at": [ + 560.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 5, + "variant": 5, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-021", + "center": [ + 624.0, + 621.0 + ], + "at": [ + 608.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 6, + "variant": 6, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-022", + "center": [ + 672.0, + 621.0 + ], + "at": [ + 656.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 7, + "variant": 7, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-024", + "center": [ + 528.0, + 621.0 + ], + "at": [ + 512.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 9, + "variant": 9, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": true, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-025", + "center": [ + 384.0, + 621.0 + ], + "at": [ + 368.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 10, + "variant": 10, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-026", + "center": [ + 480.0, + 621.0 + ], + "at": [ + 464.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 11, + "variant": 11, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": true, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-027", + "center": [ + 432.0, + 621.0 + ], + "at": [ + 416.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 12, + "variant": 12, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-028", + "center": [ + 336.0, + 621.0 + ], + "at": [ + 320.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 13, + "variant": 13, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-029", + "center": [ + 816.0, + 621.0 + ], + "at": [ + 800.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 14, + "variant": 14, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-030", + "center": [ + 864.0, + 621.0 + ], + "at": [ + 848.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 8, + "variant": 8, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-031", + "center": [ + 768.0, + 621.0 + ], + "at": [ + 752.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 9, + "variant": 9, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-032", + "center": [ + 1056.0, + 621.0 + ], + "at": [ + 1040.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 10, + "variant": 10, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-033", + "center": [ + 912.0, + 621.0 + ], + "at": [ + 896.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 11, + "variant": 11, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": true, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-034", + "center": [ + 960.0, + 621.0 + ], + "at": [ + 944.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 12, + "variant": 12, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-035", + "center": [ + 1008.0, + 621.0 + ], + "at": [ + 992.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 13, + "variant": 13, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-036", + "center": [ + 1104.0, + 621.0 + ], + "at": [ + 1088.0, + 614.0 + ], + "scale": 2.0, + "mode": "wait", + "frame": 14, + "variant": 14, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": false, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + } + ], + "cabins": [ + { + "shaft_id": "transport.shaft.0.0.0.2", + "rect": [ + 708.0, + 541.0, + 72.0, + 57.0 + ], + "pixel": 3.0, + "operational": true, + "doors_open": false, + "passengers": [ + { + "key": "guest/p23-018", + "center": [ + 726.0, + 565.0 + ], + "at": [ + 718.0, + 575.0 + ], + "scale": 1.0, + "mode": "idle", + "frame": 10, + "variant": 10, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": true, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-019", + "center": [ + 750.0, + 565.0 + ], + "at": [ + 742.0, + 575.0 + ], + "scale": 1.0, + "mode": "idle", + "frame": 11, + "variant": 11, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": true, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-017", + "center": [ + 726.0, + 538.0 + ], + "at": [ + 718.0, + 548.0 + ], + "scale": 1.0, + "mode": "idle", + "frame": 9, + "variant": 9, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": true, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + }, + { + "key": "guest/p23-023", + "center": [ + 750.0, + 538.0 + ], + "at": [ + 742.0, + 548.0 + ], + "scale": 1.0, + "mode": "idle", + "frame": 8, + "variant": 8, + "staff": false, + "state": 3, + "receiving": false, + "carrying": false, + "activity": "", + "riding": true, + "flip": false, + "cell": [ + 0, + 0, + 0 + ] + } + ], + "badge_at": [ + 708.0, + 527.0 + ], + "badge": "満員 4/4人" + } + ], + "traffic": [ + { + "cell": [ + -12, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 122.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 127.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -11, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 170.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 175.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -10, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 218.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 223.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -9, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 266.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 271.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -8, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 314.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 319.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -7, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 362.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 367.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -6, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 410.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 415.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -5, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 458.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 463.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -4, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 506.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 511.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -3, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 554.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 559.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -2, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 602.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 607.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + -1, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 650.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 655.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 0, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 27, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 698.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 703.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 1, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 746.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 751.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 2, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 794.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 799.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 3, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 842.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 847.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 4, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 890.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 895.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 5, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 938.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 943.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 6, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 986.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 991.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 7, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 1034.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 1039.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 8, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 1082.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 1087.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 9, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 1130.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 1135.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 10, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 1178.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 1183.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 11, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 1226.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 1231.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 12, + 0, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 1274.0, + 587.0, + 44.0, + 68.0 + ], + "band_rect": [ + 1279.28, + 648.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 0, + 1, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 698.0, + 515.0, + 44.0, + 68.0 + ], + "band_rect": [ + 703.28, + 576.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 1, + 1, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 746.0, + 515.0, + 44.0, + 68.0 + ], + "band_rect": [ + 751.28, + 576.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 2, + 1, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 794.0, + 515.0, + 44.0, + 68.0 + ], + "band_rect": [ + 799.28, + 576.5, + 33.44, + 5.0 + ] + }, + { + "cell": [ + 3, + 1, + 0 + ], + "traffic": { + "base_capacity": 4, + "current_density": 0, + "dynamic_cost": 0, + "temporary_block": false + }, + "rect": [ + 842.0, + 515.0, + 44.0, + 68.0 + ], + "band_rect": [ + 847.28, + 576.5, + 33.44, + 5.0 + ] + } + ], + "layer": 0, + "viewport": [ + 1440.0, + 810.0 + ], + "zoom": 1.0, + "pan": [ + 0.0, + 0.0 + ], + "elevator_tracks": { + "transport.shaft.0.0.0.2": { + "floor": 0.7, + "from": 0.9, + "at": 0.0 + } + }, + "animation_time": 0.0, + "paused": true + }, + "snapshot": { + "systems": { + "runtime": { + "world": { + "bounds": { + "min_x": -12, + "max_x": 12, + "min_y": 0, + "max_y": 6 + } + }, + "elevators": { + "version": 1, + "current_minute": 841, + "request_sequence": 48, + "shafts": [ + { + "config": { + "shaft_id": "transport.shaft.0.0.0.2", + "module_ids": [ + "facility_00000003" + ], + "stops": [ + [ + 0, + 0, + 0 + ], + [ + 0, + 1, + 0 + ] + ], + "width": 2, + "capacity": 4, + "travel_minutes_per_floor": 10, + "access_id": "" + }, + "operational": true, + "operator_open": true, + "powered": true, + "current_floor": 0, + "target_floor": 1, + "boarding_floor": 0, + "phase": "TRAVEL", + "remaining_minutes": 3, + "phase_total_minutes": 10, + "phase_started_at": 834, + "phase_origin_floor": 0, + "phase_passenger_count": 4, + "boarded": [ + "guest:p23-018", + "guest:p23-019", + "guest:p23-017", + "guest:p23-023" + ] + } + ], + "trips": [ + { + "actor_id": "guest:p23-017", + "request_id": "transport.trip.000000000020", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 728, + "state": "BOARDED" + }, + { + "actor_id": "guest:p23-018", + "request_id": "transport.trip.000000000018", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 727, + "state": "BOARDED" + }, + { + "actor_id": "guest:p23-019", + "request_id": "transport.trip.000000000019", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 727, + "state": "BOARDED" + }, + { + "actor_id": "guest:p23-020", + "request_id": "transport.trip.000000000024", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 743, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-021", + "request_id": "transport.trip.000000000023", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 742, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-022", + "request_id": "transport.trip.000000000022", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 741, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-023", + "request_id": "transport.trip.000000000021", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 740, + "state": "BOARDED" + }, + { + "actor_id": "guest:p23-024", + "request_id": "transport.trip.000000000025", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 756, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-025", + "request_id": "transport.trip.000000000028", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 757, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-026", + "request_id": "transport.trip.000000000026", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 756, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-027", + "request_id": "transport.trip.000000000027", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 756, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-028", + "request_id": "transport.trip.000000000029", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 771, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-029", + "request_id": "transport.trip.000000000031", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 772, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-030", + "request_id": "transport.trip.000000000032", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 772, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-031", + "request_id": "transport.trip.000000000030", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 771, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-032", + "request_id": "transport.trip.000000000036", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 787, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-033", + "request_id": "transport.trip.000000000033", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 786, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-034", + "request_id": "transport.trip.000000000034", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 786, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-035", + "request_id": "transport.trip.000000000035", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 786, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-036", + "request_id": "transport.trip.000000000037", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 802, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-037", + "request_id": "transport.trip.000000000038", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 802, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-038", + "request_id": "transport.trip.000000000039", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 802, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-039", + "request_id": "transport.trip.000000000040", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 802, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-040", + "request_id": "transport.trip.000000000042", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 816, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-041", + "request_id": "transport.trip.000000000044", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 818, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-042", + "request_id": "transport.trip.000000000041", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 815, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-043", + "request_id": "transport.trip.000000000043", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 817, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-044", + "request_id": "transport.trip.000000000046", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 832, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-045", + "request_id": "transport.trip.000000000047", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 832, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-046", + "request_id": "transport.trip.000000000045", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 829, + "state": "QUEUED" + }, + { + "actor_id": "guest:p23-047", + "request_id": "transport.trip.000000000048", + "shaft_id": "transport.shaft.0.0.0.2", + "connector_id": "transport.shaft.0.0.0.2.link.0.1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 0, + 1, + 0 + ], + "enqueued_at": 832, + "state": "QUEUED" + } + ] + }, + "staff": { + "tasks": [] + } + } + } + } +} diff --git a/tools/check_p23_life_observation_evidence.py b/tools/check_p23_life_observation_evidence.py index 8dc8d1a..3501717 100644 --- a/tools/check_p23_life_observation_evidence.py +++ b/tools/check_p23_life_observation_evidence.py @@ -812,10 +812,10 @@ def compare_traffic(snapshot, report): rows.append(dict(cell=cell, traffic={k: v for k, v in saved.items() if k != 'cell'}, rect=rect, band_rect=band)) close(report['traffic'], rows, 'traffic and cell rect') - validate_life_overlay(report) + validate_life_overlay(report, snapshot=snapshot) -def validate_life_overlay(report, root=ROOT): +def validate_life_overlay(report, root=ROOT, snapshot=None): """Check consumed frame and source wiring, not merely reported order flags.""" serial = report.get('observation_serial') require(type(serial) is int and serial > 0 @@ -842,6 +842,25 @@ def validate_life_overlay(report, root=ROOT): < overlay.find('_draw_observed_actor(actor, _life_overlay)') < overlay.find('_draw_actors(_life_overlay)'), 'LifeOverlay source pass order') actors = list(report['actor_drawings']) + [a for c in report['cabins'] for a in c['passengers']] + # Projection authority comes from raw state, never riding/region flags. + cabin_actors, carried_keys = set(), set() + if snapshot is not None: + if report['cabins']: + compare_cabin_geometry(snapshot, report) + shafts = {s['shaft_id']: s for s in elevator_report(snapshot)['shafts']} + for cabin in report['cabins']: + members = {key.replace('staff:', 'staff/', 1).replace('guest:', 'guest/', 1) + for key in shafts[cabin['shaft_id']]['boarded']} + cabin_actors.update(id(a) for a in cabin['passengers'] if a['key'] in members) + tasks = snapshot['systems']['runtime']['staff']['tasks'] + carried_keys = {'staff/' + t['target_entity_id'] for t in tasks + if t['task_type'] == 'rescue_transport' + and t['state'] not in ('WORKING', 'COMPLETED', 'CANCELLED', 'FAILED') + and t['assigned_staff_id']} + carried_keys.difference_update(report.get('boarded', {}), report.get('queue_positions', {})) + if carried_keys: + # Reuse the original raw binding, track and raised-region fit checks. + compare_actor_geometry(snapshot, report) for row in report.get('traffic', []): x, y, w, h = row['rect'] thickness = max(2, min(5, h * .16)) @@ -850,11 +869,24 @@ def validate_life_overlay(report, root=ROOT): close(row['band_rect'], expected, 'traffic band rectangle') if row['traffic']['current_density'] <= 0 and not row['traffic']['temporary_block']: continue + intervals = [] for actor in actors: core = actor_identity_core(actor) intersects = (core[0] < expected[0] + expected[2] and expected[0] < core[0] + core[2] and core[1] < expected[1] + expected[3] and expected[1] < core[1] + core[3]) - require(not intersects, 'traffic band covers actor core: ' + actor['key']) + require(not intersects or id(actor) in cabin_actors + or (actor in report['actor_drawings'] and actor['key'] in carried_keys), + 'floor actor core over traffic band: ' + actor['key']) + if intersects: + intervals.append((max(core[0], expected[0]), + min(core[0] + core[2], expected[0] + expected[2]))) + end = expected[0] + for left, right in sorted(intervals): + if left > end: + break + end = max(end, right) + require(end < expected[0] + expected[2], + 'traffic band fully occluded: ' + str(row.get('cell'))) def compare_queues(snapshot, report): diff --git a/tools/test_p23_life_observation_evidence.py b/tools/test_p23_life_observation_evidence.py index f303854..7bb3d24 100644 --- a/tools/test_p23_life_observation_evidence.py +++ b/tools/test_p23_life_observation_evidence.py @@ -157,7 +157,7 @@ class LifeOverlayTests(unittest.TestCase): def test_core_crossed_by_actual_band_fails(self): report = self.report() report['actor_drawings'][0]['at'][1] = 60 - with self.assertRaisesRegex(ValueError, 'traffic band covers actor core'): + with self.assertRaisesRegex(ValueError, 'floor actor core over traffic band'): check.validate_life_overlay(report) def test_forged_band_geometry_fails(self): @@ -177,6 +177,105 @@ class LifeOverlayTests(unittest.TestCase): check.validate_life_overlay(self.report()) +class ProjectionBandTests(unittest.TestCase): + def setUp(self): + fixture = json.loads((check.ROOT / 'tests/fixtures/p23-03/repair28_frame180_projection.json').read_text()) + self.report, self.snapshot = fixture['report'], fixture['snapshot'] + + def occupied(self): + for row in self.report['traffic']: + if row['cell'] in ([0, 1, 0], [1, 1, 0]): + self.assertEqual(row['traffic']['current_density'], 0) + row['traffic']['current_density'] = 1 + + def test_counterfactual_actual_frame180_density_one(self): + """COUNTERFACTUAL density substitution; never occupied-band engine evidence.""" + self.occupied() + for key in ('guest/p23-018', 'guest/p23-019'): + self.assertTrue(any(a['key'] == key for c in self.report['cabins'] for a in c['passengers'])) + check.validate_life_overlay(self.report, snapshot=self.snapshot) + + def test_cabin_passenger_union_fully_occludes_band(self): + self.occupied() + row = next(r for r in self.report['traffic'] if r['cell'] == [0, 1, 0]) + x, y, w, h = row['band_rect'] + # Each core covers only part; their touching intervals cover the union. + for i, actor in enumerate(self.report['cabins'][0]['passengers']): + actor.update(scale=2, flip=False, mode='idle', at=[x+i*12-12, y-2]) + with self.assertRaisesRegex(ValueError, 'traffic band fully occluded:'): + check.validate_life_overlay(self.report, snapshot=self.snapshot) + + def test_spoofed_cabin_rectangle_fails_raw_recomputation(self): + self.occupied() + self.report['cabins'][0]['rect'][1] += 1 + with self.assertRaisesRegex(ValueError, 'cabin/rect'): + check.validate_life_overlay(self.report, snapshot=self.snapshot) + + def test_flag_only_cabin_projection_without_raw_membership_fails(self): + self.occupied() + for cabin in self.report['cabins']: + for actor in cabin['passengers']: + actor['key'] = 'guest/spoof-' + actor['key'] + actor['riding'] = True + with self.assertRaisesRegex(ValueError, 'floor actor core over traffic band'): + check.validate_life_overlay(self.report, snapshot=self.snapshot) + + def test_no_raw_state_never_exempts_cabin_projection(self): + self.occupied() + with self.assertRaisesRegex(ValueError, 'floor actor core over traffic band'): + check.validate_life_overlay(self.report) + + def test_floor_copy_of_raw_cabin_member_keeps_zero_intersection(self): + self.occupied() + self.report['actor_drawings'].append(copy.deepcopy(self.report['cabins'][0]['passengers'][0])) + with self.assertRaisesRegex(ValueError, 'floor actor core over traffic band'): + check.validate_life_overlay(self.report, snapshot=self.snapshot) + + def test_blocked_band_uses_same_projection_and_legibility_rules(self): + row = next(r for r in self.report['traffic'] if r['cell'] == [0, 1, 0]) + row['traffic']['temporary_block'] = True + check.validate_life_overlay(self.report, snapshot=self.snapshot) + x, y, w, h = row['band_rect'] + for i, actor in enumerate(self.report['cabins'][0]['passengers']): + actor.update(scale=2, flip=False, mode='idle', at=[x+i*12-12, y-2]) + with self.assertRaisesRegex(ValueError, 'traffic band fully occluded:'): + check.validate_life_overlay(self.report, snapshot=self.snapshot) + + def test_flag_only_carried_projection_without_binding_fails(self): + self.occupied() + actor = copy.deepcopy(self.report['cabins'][0]['passengers'][0]) + actor.update(key='staff/spoof', staff=True, riding=False, region='carried') + self.report['actor_drawings'].append(actor) + self.report['carried_patients'] = {'spoof': 'carrier'} + with self.assertRaisesRegex(ValueError, 'floor actor core over traffic band: staff/spoof'): + check.validate_life_overlay(self.report, snapshot=self.snapshot) + + def test_carried_projection_requires_original_binding_tracks_and_fit(self): + fixture = AnchoredGeometryTests() + fixture.setUp() + fixture.person('staff/carrier', [0, 0, 0], 'AVAILABLE') + fixture.person('staff/patient', [0, 0, 0], 'INCAPACITATED') + fixture.runtime['staff']['tasks'] = [dict(task_type='rescue_transport', state='MOVING', + assigned_staff_id='carrier', target_entity_id='patient', rescue_facility_id='')] + fixture.report['carried_patients'] = {'patient': 'carrier'} + fixture.drawing('staff/carrier', [108, 214]) + fixture.drawing('staff/patient', [108, 166], 'collapsed') + for key, value in LifeOverlayTests().report().items(): + if key.startswith('overlay_') or key == 'observation_serial': + fixture.report[key] = value + # Synthetic band crosses the raised patient, with uncovered span. + fixture.report['traffic'] = [dict(cell=[0, 1, 0], rect=[78, 100.5, 60, 92], + band_rect=[85.2, 184, 45.6, 5], traffic=dict(current_density=1, temporary_block=False))] + check.validate_life_overlay(fixture.report, snapshot=fixture.snapshot) + for mutation in ('binding', 'track', 'position'): + report, snapshot = copy.deepcopy(fixture.report), copy.deepcopy(fixture.snapshot) + if mutation == 'binding': snapshot['systems']['runtime']['staff']['tasks'] = [] + if mutation == 'track': report['actor_tracks']['staff/carrier']['cell'] = [1, 0, 0] + if mutation == 'position': report['actor_drawings'][1]['at'][1] += 1 + with self.subTest(mutation=mutation), self.assertRaises(ValueError): + check.validate_life_overlay(report, snapshot=snapshot) + + class SetupBudgetTests(unittest.TestCase): def setUp(self): self.source = (check.ROOT / 'tests/fixtures/p23-03/observation.gd').read_text()