P23-03: 生活の観察
This commit is contained in:
+1
-1
@@ -3707,7 +3707,7 @@
|
|||||||
"template": "P23 life observation: <checks> checks, <failures> failures",
|
"template": "P23 life observation: <checks> checks, <failures> failures",
|
||||||
"required": true,
|
"required": true,
|
||||||
"fixed_check_count_used": false,
|
"fixed_check_count_used": false,
|
||||||
"source_sha256_at_schema_audit": "88c64fdb81937fc7dca24a87e74b568db8e35a3471c7f4ee7bc41c7d0ddeae24",
|
"source_sha256_at_schema_audit": "5c52db4dd70f9702c096e918d849703d14aec1acb88bb2458ca01049da71ce3c",
|
||||||
"authority": {
|
"authority": {
|
||||||
"path": "docs/spec/acceptance/P23-03.json",
|
"path": "docs/spec/acceptance/P23-03.json",
|
||||||
"scope": "component and read-only observation only; native unexecuted; whole unit blocked"
|
"scope": "component and read-only observation only; native unexecuted; whole unit blocked"
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Repair 35 — source submission
|
||||||
|
|
||||||
|
Scope: P23-03 cabin-intrusion presentation repair only. Source input is
|
||||||
|
`93248f4d79a3be71d7a1ac0592e9219d405cd6b7`; recorded tested Q is
|
||||||
|
`428f653cdac375c1d4589e6c2d6138e914acf9da`. No Runtime, demand, assets,
|
||||||
|
acceptance, policy, process ownership or timeout changes.
|
||||||
|
|
||||||
|
## Exact inputs and Phase 0
|
||||||
|
|
||||||
|
First read the repair35 `EXACT_INPUTS.json` under
|
||||||
|
`/Users/korizatou114/Projects/TheTree-managed-worktrees/p23-03-code/.runtime/P23-03/build/audit-support/cabin-repair35-exact-inputs/`.
|
||||||
|
Read its complete supplied `original_full_request.txt`, `actual_Opus_repair.json`
|
||||||
|
and `actual_Opus_run.json`; all three supplied SHA256 pins match. Provenance-only
|
||||||
|
source locations were not needed. Read the sibling actual-worker preflight.
|
||||||
|
|
||||||
|
Read the SHA-verified preserved all87 probe
|
||||||
|
`audit-support/cabin-repair34-exact-inputs/CURRENT932_ALL_CABIN_INTRUSION_PROBE.json`
|
||||||
|
under the same BUILD (SHA256
|
||||||
|
`da395d697e0ec1bd2fb805757aa320314bbc840260f6062d12ecf5d859ceebd0`).
|
||||||
|
Read all 87 complete collision rows and all their whole SHA-verified typed
|
||||||
|
state/report objects: 19 distinct stream/state/report pairs, 31 distinct objects.
|
||||||
|
Also decoded all referenced objects in all 161 current stream captures and read
|
||||||
|
every file in the supplied r16 stage (1962 files, including the actual Q sources).
|
||||||
|
No required evidence was truncated, compressed, relabelled or edited.
|
||||||
|
|
||||||
|
Used that preserved evidence to apply the new candidate-region rule: 70 rows
|
||||||
|
select a corridor landing and 17 require identity aggregation because no lane is
|
||||||
|
available. All 87 candidate regions intersect a cabin; outside-class rows: zero.
|
||||||
|
This is static application to old negative evidence, not new rendered proof.
|
||||||
|
|
||||||
|
The existing `API_AND_STATE_MAP.md` remains the deterministic-demand/save-load
|
||||||
|
audit. Directly checked current GuestRuntime enum (`MOVING` = 3, lines 6–19),
|
||||||
|
WorldService `get_occupant`/`get_entity_cells`/`is_walkable` (194/204/228), and
|
||||||
|
ElevatorService's copied public report (345). These read-only APIs supply the
|
||||||
|
same queue lane topology and full-width shaft geometry; no new API is needed.
|
||||||
|
Current complete dependency SHA256:
|
||||||
|
|
||||||
|
| Source | SHA256 |
|
||||||
|
| --- | --- |
|
||||||
|
| scripts/world/world_service.gd | add67a0799f961a7dce1d382ea6dac67022fbd013f8c4c6b11321df96f37944a |
|
||||||
|
| scripts/transport/elevator_service.gd | cea09dd9204551c26b31b30cd3aa847cc77e3b75fdac31662b9694a95fb3580c |
|
||||||
|
| scripts/guest/guest_runtime.gd | 57ba99e5d977b5302987f4359a19056f1ab09c8fb7e4762ee8d79cbb9316741a |
|
||||||
|
|
||||||
|
The immutable spec matches `8568457d…a9c89f` using the existing checker's
|
||||||
|
`read_text().strip()` hash convention. Original acceptance and recorded complete
|
||||||
|
Opus repair/run directions remain authoritative.
|
||||||
|
|
||||||
|
## Six pose classes
|
||||||
|
|
||||||
|
| Case | Product, world_preview.gd | Independent checker |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| (a) MOVING/idle footprint actor | 1608 candidate region, 1651 cabin rule, 1743 center before fitting | 1532 candidate region, 1577 cabin rule, 1584 center before jitter |
|
||||||
|
| (b) Lift-to-corridor walking transit | 1555 merged track bounds, 1565 landing becomes non-transit | 1532 merged track bounds, 1458 landing becomes non-transit |
|
||||||
|
| (c) Carrier and raised patient | 1624 recursively relocated carrier region, then minus 12p | 1556 recursively relocated carrier region, then minus 12p; 1612 center |
|
||||||
|
| (d) Fractional cabin crossing adjacent floor | 1565 positive-area intersection with actual cabin rect | 1458 intersection after independent cabin/badge validation at 1505 |
|
||||||
|
| (e) No corridor lane | 1590 landing badge anchor, 1759 aggregation with every identity | 1482 landing badge anchor, 1703 identity aggregation |
|
||||||
|
| (f) Queue plus relocated actor | 1602 prefer unreserved lane, otherwise existing packing | 1487 reservation choice, 1712 existing packing |
|
||||||
|
|
||||||
|
Room slots, queue placement/overflow, cabin/passenger geometry and overlay order
|
||||||
|
are retained. The new landing centers preserve original jitter; carried patients
|
||||||
|
use the relocated carrier before the 12p raise. No-lane actors share a counted
|
||||||
|
aggregation at the floor's landing badge anchor, retaining key/id/state/cell/mode.
|
||||||
|
Report-only region/boarded assertions cannot authorize a position.
|
||||||
|
|
||||||
|
`validate_cabin_intrusion`, `validate_actor_nonoverlap`, `actor_track_transit`
|
||||||
|
are byte-identical to the supplied Q checker; `PIXEL_PEOPLE_CORE_SHA256` is
|
||||||
|
unchanged. The existing P23-03 marker source-audit SHA alone was updated; all
|
||||||
|
other marker rows and suite-policy are preserved.
|
||||||
|
|
||||||
|
`run_p23_life_observation.py:315` inspects both completed headless layout streams
|
||||||
|
inside the existing exception/receipt/cleanup path, before OS collection.
|
||||||
|
Failure therefore prevents OS collection. This is staging, not OS evidence.
|
||||||
|
|
||||||
|
## Verification and remaining Root work
|
||||||
|
|
||||||
|
- `py_compile` passes for the runner, checker and checker tests.
|
||||||
|
- The full Python checker test file passes: 142 tests, including the seven new
|
||||||
|
tests covering (a)–(f), old in-cabin positions, tampered flags, missing actors,
|
||||||
|
missing aggregation identities, combined no-lane identities and unwarranted
|
||||||
|
relocation. Existing 135 tests are retained.
|
||||||
|
- Native assertions were extended at `tests/ui/test_p23_life_observation.gd:56`
|
||||||
|
for same-floor and fractional moving cabins on both layers; not executed here.
|
||||||
|
- No Godot, native/OS collection, full regression, Git writes, Runner operations
|
||||||
|
or subagents were run. Root native/OS evidence is missing.
|
||||||
|
|
||||||
|
Root staging remains: affected native/UI test; typed headless A/B with early
|
||||||
|
strict inspection and new-output intrusion probe requiring zero; then current
|
||||||
|
S/M/T/Q OS r17; then one final full 179 and independent review. Keep all r16
|
||||||
|
negative inputs and the all87 probe versioned. Seven conditions,
|
||||||
|
30/88/formal-13 and no partial credit remain unchanged.
|
||||||
@@ -1561,6 +1561,50 @@ func _life_track_region(key: String, cell: Vector3i, model: Dictionary) -> Dicti
|
|||||||
return {&"bounds": rect, &"transit": transit}
|
return {&"bounds": rect, &"transit": transit}
|
||||||
|
|
||||||
|
|
||||||
|
## Keep non-passengers on a real corridor landing when a cabin crosses their pose.
|
||||||
|
func _life_landing_region(region: Dictionary, cell: Vector3i, model: Dictionary) -> Dictionary:
|
||||||
|
var blocked := false
|
||||||
|
for cabin: Dictionary in model[&"cabins"]:
|
||||||
|
if (region[&"bounds"] as Rect2).intersects(cabin[&"rect"]):
|
||||||
|
blocked = true
|
||||||
|
break
|
||||||
|
if not blocked:
|
||||||
|
return region
|
||||||
|
var world: Variant = _service(&"world_service")
|
||||||
|
var facilities: Variant = _service(&"facility_service")
|
||||||
|
var footprint: Array = model[&"shaft_footprint"]
|
||||||
|
var origins: Array = footprint.filter(func(c: Vector3i) -> bool: return c.y == cell.y and c.z == cell.z)
|
||||||
|
origins.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x))
|
||||||
|
var lane: Array[Vector3i] = []
|
||||||
|
for origin: Vector3i in origins:
|
||||||
|
for direction: int in [-1, 1]:
|
||||||
|
for distance: int in range(1, 9):
|
||||||
|
var target := origin + Vector3i(direction*distance, 0, 0)
|
||||||
|
if world == null or footprint.has(target) or not world.is_walkable(target):
|
||||||
|
break
|
||||||
|
var facility: Variant = facilities.get_instance(world.get_occupant(target)) if facilities != null else null
|
||||||
|
if facility != null and facility.definition_id not in [&"guest_corridor", &"service_corridor"]:
|
||||||
|
break
|
||||||
|
if not lane.has(target):
|
||||||
|
lane.append(target)
|
||||||
|
lane.sort_custom(func(a: Vector3i, b: Vector3i) -> bool: return absi(a.x-cell.x) < absi(b.x-cell.x) or (absi(a.x-cell.x) == absi(b.x-cell.x) and a.x < b.x))
|
||||||
|
if lane.is_empty():
|
||||||
|
var anchor := get_cell_rect(cell).position+Vector2(-80, -16)
|
||||||
|
var nearest := INF
|
||||||
|
for badge: Dictionary in model[&"landing_badges"]:
|
||||||
|
var landing: Vector3i = badge[&"cell"]
|
||||||
|
if landing.y == cell.y and landing.z == cell.z and absf(landing.x-cell.x) < nearest:
|
||||||
|
nearest = absf(landing.x-cell.x)
|
||||||
|
anchor = badge[&"at"]
|
||||||
|
return {&"bounds": region[&"bounds"], &"transit": false, &"no_lane": true, &"badge_at": anchor}
|
||||||
|
var selected: Vector3i = lane[0]
|
||||||
|
for target: Vector3i in lane:
|
||||||
|
if not model[&"queue_positions"].values().has(cell_to_canvas(target)):
|
||||||
|
selected = target
|
||||||
|
break
|
||||||
|
return {&"bounds": get_cell_rect(selected), &"transit": false, &"center": cell_to_canvas(selected)}
|
||||||
|
|
||||||
|
|
||||||
func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary) -> Dictionary:
|
func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary) -> Dictionary:
|
||||||
var key: String = row[&"key"]
|
var key: String = row[&"key"]
|
||||||
var cell: Vector3i = person[&"cell"]
|
var cell: Vector3i = person[&"cell"]
|
||||||
@@ -1580,10 +1624,13 @@ func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary)
|
|||||||
if model[&"carried_patients"].has(id):
|
if model[&"carried_patients"].has(id):
|
||||||
for carrier: Dictionary in model[&"staff"]:
|
for carrier: Dictionary in model[&"staff"]:
|
||||||
if carrier[&"id"] == model[&"carried_patients"][id]:
|
if carrier[&"id"] == model[&"carried_patients"][id]:
|
||||||
result = _life_track_region("staff/"+String(carrier[&"id"]), carrier[&"cell"], model)
|
result = _life_actor_region({&"key": "staff/"+String(carrier[&"id"]), &"staff": true}, carrier, model)
|
||||||
var rect: Rect2 = result[&"bounds"]
|
var rect: Rect2 = result[&"bounds"]
|
||||||
|
rect = rect.grow(p)
|
||||||
rect.position.y -= 12*p
|
rect.position.y -= 12*p
|
||||||
result[&"bounds"] = rect
|
result[&"bounds"] = rect
|
||||||
|
if result.has(&"center"):
|
||||||
|
result[&"center"] -= Vector2(0, 12*p)
|
||||||
elif model[&"resting_staff"].has(id):
|
elif model[&"resting_staff"].has(id):
|
||||||
facility_id = model[&"resting_staff"][id]
|
facility_id = model[&"resting_staff"][id]
|
||||||
elif model[&"treated_patients"].has(id):
|
elif model[&"treated_patients"].has(id):
|
||||||
@@ -1600,6 +1647,8 @@ func _life_actor_region(row: Dictionary, person: Dictionary, model: Dictionary)
|
|||||||
var cells: Array[Vector3i] = world.get_entity_cells(facility_id)
|
var cells: Array[Vector3i] = world.get_entity_cells(facility_id)
|
||||||
if not cells.is_empty():
|
if not cells.is_empty():
|
||||||
result = {&"bounds": _cell_bounds_rect(cells), &"transit": false}
|
result = {&"bounds": _cell_bounds_rect(cells), &"transit": false}
|
||||||
|
if not model[&"queue_positions"].has(key) and not result.has(&"center") and not result.get(&"no_lane", false):
|
||||||
|
result = _life_landing_region(result, cell, model)
|
||||||
result[&"bounds"] = (result[&"bounds"] as Rect2).grow(-p)
|
result[&"bounds"] = (result[&"bounds"] as Rect2).grow(-p)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -1680,6 +1729,7 @@ func _fit_life_actors(model: Dictionary) -> void:
|
|||||||
for person: Dictionary in model[&"staff"]:
|
for person: Dictionary in model[&"staff"]:
|
||||||
people["staff/"+String(person[&"id"])] = person
|
people["staff/"+String(person[&"id"])] = person
|
||||||
var members: Array = []
|
var members: Array = []
|
||||||
|
var stranded: Array = []
|
||||||
var all_rows: Array = model[&"actor_drawings"].duplicate()
|
var all_rows: Array = model[&"actor_drawings"].duplicate()
|
||||||
var passenger_bounds: Dictionary = {}
|
var passenger_bounds: Dictionary = {}
|
||||||
for cabin: Dictionary in model[&"cabins"]:
|
for cabin: Dictionary in model[&"cabins"]:
|
||||||
@@ -1690,12 +1740,35 @@ func _fit_life_actors(model: Dictionary) -> void:
|
|||||||
var person: Dictionary = people[row[&"key"]]
|
var person: Dictionary = people[row[&"key"]]
|
||||||
row[&"cell"] = person[&"cell"]
|
row[&"cell"] = person[&"cell"]
|
||||||
var region := _life_actor_region(row, person, model) if not row[&"riding"] else {&"bounds": passenger_bounds[row[&"key"]], &"transit": false}
|
var region := _life_actor_region(row, person, model) if not row[&"riding"] else {&"bounds": passenger_bounds[row[&"key"]], &"transit": false}
|
||||||
|
if region.has(&"center"):
|
||||||
|
var jitter := Vector2.ZERO
|
||||||
|
if not model[&"carried_patients"].has(person[&"id"]):
|
||||||
|
var track: Dictionary = model[&"actor_tracks"][row[&"key"]]
|
||||||
|
var t := 1.0 if model[&"paused"] else clampf((_animation_time-float(track[&"at"]))/0.42, 0, 1)
|
||||||
|
jitter = row[&"center"]-cell_to_canvas(track[&"from"]).lerp(cell_to_canvas(person[&"cell"]), t)
|
||||||
|
row[&"center"] = region[&"center"]+jitter
|
||||||
|
row.merge(_life_scaled(row, row[&"scale"]), true)
|
||||||
region[&"row"] = row
|
region[&"row"] = row
|
||||||
if not region[&"transit"]:
|
if region.get(&"no_lane", false):
|
||||||
|
stranded.append(region)
|
||||||
|
elif not region[&"transit"]:
|
||||||
members.append(region)
|
members.append(region)
|
||||||
var groups := _life_fitted_groups(members)
|
var groups := _life_fitted_groups(members)
|
||||||
model[&"actor_aggregations"] = []
|
model[&"actor_aggregations"] = []
|
||||||
var hidden: Dictionary = {}
|
var hidden: Dictionary = {}
|
||||||
|
for member: Dictionary in stranded:
|
||||||
|
var row: Dictionary = member[&"row"]
|
||||||
|
hidden[row[&"key"]] = true
|
||||||
|
var aggregate: Dictionary = {}
|
||||||
|
for existing: Dictionary in model[&"actor_aggregations"]:
|
||||||
|
if existing[&"at"] == member[&"badge_at"]:
|
||||||
|
aggregate = existing
|
||||||
|
break
|
||||||
|
if aggregate.is_empty():
|
||||||
|
aggregate = {&"members": [], &"drawn_keys": [], &"at": member[&"badge_at"], &"text": ""}
|
||||||
|
model[&"actor_aggregations"].append(aggregate)
|
||||||
|
aggregate[&"members"].append({&"key": row[&"key"], &"id": people[row[&"key"]][&"id"], &"state": row[&"state"], &"cell": row[&"cell"], &"mode": row[&"mode"]})
|
||||||
|
aggregate[&"text"] = "+%d" % aggregate[&"members"].size()
|
||||||
for group: Array in groups:
|
for group: Array in groups:
|
||||||
group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"]))
|
group.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a[&"row"][&"center"].x < b[&"row"][&"center"].x or (a[&"row"][&"center"].x == b[&"row"][&"center"].x and a[&"row"][&"key"] < b[&"row"][&"key"]))
|
||||||
var base := 1.0
|
var base := 1.0
|
||||||
|
|||||||
@@ -10,19 +10,20 @@ class QueueWorld extends RefCounted:
|
|||||||
func get_bounds() -> Dictionary:
|
func get_bounds() -> Dictionary:
|
||||||
return {&"min_x": -1, &"max_x": 2, &"min_y": 0, &"max_y": 1}
|
return {&"min_x": -1, &"max_x": 2, &"min_y": 0, &"max_y": 1}
|
||||||
func is_walkable(cell: Vector3i) -> bool:
|
func is_walkable(cell: Vector3i) -> bool:
|
||||||
return cell.y == 0 and cell.x >= -1 and cell.x <= 2
|
return cell.y >= 0 and cell.y <= 1 and cell.x >= -1 and cell.x <= 2
|
||||||
func get_occupant(_cell: Vector3i) -> StringName:
|
func get_occupant(_cell: Vector3i) -> StringName:
|
||||||
return &""
|
return &""
|
||||||
|
|
||||||
class QueueLift extends RefCounted:
|
class QueueLift extends RefCounted:
|
||||||
var layer := 0
|
var layer := 0
|
||||||
|
var visual_floor := 0.3
|
||||||
func get_report() -> Dictionary:
|
func get_report() -> Dictionary:
|
||||||
var trips: Array = []
|
var trips: Array = []
|
||||||
for i: int in 3:
|
for i: int in 3:
|
||||||
trips.append({&"state": PathService.ELEVATOR_REQUEST_QUEUED, &"from": Vector3i(0,0,layer),
|
trips.append({&"state": PathService.ELEVATOR_REQUEST_QUEUED, &"from": Vector3i(0,0,layer),
|
||||||
&"actor_id": "guest:q%d" % i, &"enqueued_at": i})
|
&"actor_id": "guest:q%d" % i, &"enqueued_at": i})
|
||||||
return {&"shafts": [{&"shaft_id": &"wide", &"stops": [Vector3i(0,0,layer), Vector3i(0,1,layer)],
|
return {&"shafts": [{&"shaft_id": &"wide", &"stops": [Vector3i(0,0,layer), Vector3i(0,1,layer)],
|
||||||
&"width": 2, &"capacity": 4, &"boarded": [], &"visual_floor": 0.3,
|
&"width": 2, &"capacity": 4, &"boarded": [], &"visual_floor": visual_floor,
|
||||||
&"operational": true, &"phase": &"PICKUP"}], &"trips": trips}
|
&"operational": true, &"phase": &"PICKUP"}], &"trips": trips}
|
||||||
|
|
||||||
class QueueRuntime extends RefCounted:
|
class QueueRuntime extends RefCounted:
|
||||||
@@ -52,6 +53,28 @@ func repair28_geometry() -> void:
|
|||||||
expect(not cabins[0][&"rect"].has_point(at), "queue anchor outside empty cabin")
|
expect(not cabins[0][&"rect"].has_point(at), "queue anchor outside empty cabin")
|
||||||
expect(cabins[0][&"passenger_count"] == 0 and cabins[0][&"passengers"].is_empty(), "empty cabin has no passenger")
|
expect(cabins[0][&"passenger_count"] == 0 and cabins[0][&"passengers"].is_empty(), "empty cabin has no passenger")
|
||||||
expect(cabins[0][&"operational"] and not cabins[0][&"doors_open"] and cabins[0][&"phase"] == &"PICKUP", "typed moving cabin state")
|
expect(cabins[0][&"operational"] and not cabins[0][&"doors_open"] and cabins[0][&"phase"] == &"PICKUP", "typed moving cabin state")
|
||||||
|
for floor: float in [0.0, 0.8]:
|
||||||
|
fixture.elevators.visual_floor = floor
|
||||||
|
var pose_model: Dictionary = view._observe_queues()
|
||||||
|
var cell := Vector3i(0, 0 if floor == 0 else 1, layer)
|
||||||
|
var key := "guest/landing"
|
||||||
|
var person := {&"id": &"landing", &"cell": cell, &"service": &"", &"target": &""}
|
||||||
|
pose_model.merge({&"boarded": {}, &"elevator_tracks": {}, &"paused": true,
|
||||||
|
&"actor_tracks": {key: {&"cell": cell, &"from": cell, &"at": 0.0, &"direction": 1.0}},
|
||||||
|
&"rooms": {}, &"guests": [person], &"staff": [], &"carried_patients": {},
|
||||||
|
&"resting_staff": {}, &"treated_patients": {}})
|
||||||
|
pose_model[&"cabins"] = view._observe_elevators(pose_model)
|
||||||
|
var row: Dictionary = view._actor_drawing(view.cell_to_canvas(cell), key, false,
|
||||||
|
GuestRuntime.State.MOVING, false, false, &"", false, pose_model)
|
||||||
|
var region: Dictionary = view._life_actor_region(row, person, pose_model)
|
||||||
|
expect(region.has(&"center") and not region[&"transit"], "same-floor and fractional cabin use landing region")
|
||||||
|
expect(not (region[&"bounds"] as Rect2).intersects(pose_model[&"cabins"][0][&"rect"]), "landing bounds outside cabin")
|
||||||
|
pose_model[&"actor_drawings"] = [row]
|
||||||
|
view._fit_life_actors(pose_model)
|
||||||
|
expect(pose_model[&"actor_drawings"].size() == 1 and pose_model[&"actor_aggregations"].is_empty(), "landing actor retained")
|
||||||
|
expect(not view._life_core(row).intersects(pose_model[&"cabins"][0][&"rect"]), "fitted non-passenger core outside cabin")
|
||||||
|
expect(row[&"cell"] == cell and not row[&"riding"] and row[&"state"] == GuestRuntime.State.MOVING, "landing placement preserves actual state")
|
||||||
|
fixture.elevators.visual_floor = 0.3
|
||||||
for operational: bool in [true,false]:
|
for operational: bool in [true,false]:
|
||||||
for phase: StringName in [&"IDLE", &"BOARDING", &"PICKUP"]:
|
for phase: StringName in [&"IDLE", &"BOARDING", &"PICKUP"]:
|
||||||
var shaft: Dictionary = fixture.elevators.get_report()[&"shafts"][0]
|
var shaft: Dictionary = fixture.elevators.get_report()[&"shafts"][0]
|
||||||
|
|||||||
@@ -1455,6 +1455,40 @@ def actor_track_transit(report, key, cell):
|
|||||||
return not report['paused'] and origin != cell and now-started < .42
|
return not report['paused'] and origin != cell and now-started < .42
|
||||||
|
|
||||||
|
|
||||||
|
def landing_actor_region(state, report, transform, cell, region, transit):
|
||||||
|
"""Derive corridor landings from raw topology, never from actor flags."""
|
||||||
|
if not any(intersects(region, cabin['rect']) for cabin in report['cabins']):
|
||||||
|
return dict(bounds=region, transit=transit)
|
||||||
|
runtime = state['systems']['runtime']
|
||||||
|
footprint = {(stop[0]+dx, stop[1], stop[2])
|
||||||
|
for shaft in elevator_report(state)['shafts'] for stop in shaft['stops']
|
||||||
|
for dx in range(max(int(shaft.get('width', 2)), 1))}
|
||||||
|
walkable = {tuple(c) for c in runtime['world']['walkable_cells']}
|
||||||
|
facilities = codec.indexed(runtime['facilities']['instances'], 'instance_id')
|
||||||
|
owners = {tuple(c): r['entity_id'] for r in runtime['world']['occupancies'] for c in r['cells']}
|
||||||
|
distance = lambda c: (abs(c[0]-cell[0]), c[0])
|
||||||
|
origins = sorted((c for c in footprint if c[1:] == tuple(cell[1:])), key=distance)
|
||||||
|
lane = set()
|
||||||
|
for origin in origins:
|
||||||
|
for direction in (-1, 1):
|
||||||
|
for step in range(1, 9):
|
||||||
|
target = (origin[0]+direction*step, origin[1], origin[2])
|
||||||
|
facility = facilities.get(owners.get(target))
|
||||||
|
if (target in footprint or target not in walkable or
|
||||||
|
(facility and facility['definition_id'] not in ('guest_corridor', 'service_corridor'))):
|
||||||
|
break
|
||||||
|
lane.add(target)
|
||||||
|
lane = sorted(lane, key=distance)
|
||||||
|
if not lane:
|
||||||
|
rect = transform.rect(cell)
|
||||||
|
badges = [b for b in report['landing_badges'] if b['cell'][1:] == list(cell[1:])]
|
||||||
|
anchor = min(badges, key=lambda b: distance(b['cell']))['at'] if badges else [rect[0]-80, rect[1]-16]
|
||||||
|
return dict(bounds=region, transit=False, no_lane=True, badge_at=anchor)
|
||||||
|
reserved = list(report['queue_positions'].values())
|
||||||
|
selected = next((c for c in lane if transform.center(c) not in reserved), lane[0])
|
||||||
|
return dict(bounds=transform.rect(selected), transit=False, center=transform.center(selected))
|
||||||
|
|
||||||
|
|
||||||
def compare_actor_geometry(state, report):
|
def compare_actor_geometry(state, report):
|
||||||
runtime = state['systems']['runtime']
|
runtime = state['systems']['runtime']
|
||||||
compare_queues(state, report)
|
compare_queues(state, report)
|
||||||
@@ -1467,6 +1501,9 @@ def compare_actor_geometry(state, report):
|
|||||||
facilities = codec.indexed(runtime['facilities']['instances'], 'instance_id')
|
facilities = codec.indexed(runtime['facilities']['instances'], 'instance_id')
|
||||||
staff = codec.indexed(runtime['staff']['staff'], 'staff_id')
|
staff = codec.indexed(runtime['staff']['staff'], 'staff_id')
|
||||||
occupancy = {r['entity_id']: r['cells'] for r in runtime['world']['occupancies']}
|
occupancy = {r['entity_id']: r['cells'] for r in runtime['world']['occupancies']}
|
||||||
|
# Cabin rectangles and badges are independently recomputed before use below.
|
||||||
|
if report['cabins']:
|
||||||
|
compare_cabin_geometry(state, report)
|
||||||
counts, expected = {}, []
|
counts, expected = {}, []
|
||||||
def track_position(key, cell):
|
def track_position(key, cell):
|
||||||
track = report['actor_tracks'][key]
|
track = report['actor_tracks'][key]
|
||||||
@@ -1489,11 +1526,62 @@ def compare_actor_geometry(state, report):
|
|||||||
anchors = room_slots(transform.room(cells), p, definition)
|
anchors = room_slots(transform.room(cells), p, definition)
|
||||||
index = users.index(identifier)
|
index = users.index(identifier)
|
||||||
return [anchors[index][0], anchors[index][1]+dy] if index < len(anchors) else None
|
return [anchors[index][0], anchors[index][1]+dy] if index < len(anchors) else None
|
||||||
|
people = {'guest/'+r['guest_id']: r for r in runtime['guests']['guests']}
|
||||||
|
people.update({'staff/'+sid: r for sid, r in staff.items()})
|
||||||
|
regions = {}
|
||||||
|
def actor_region(key):
|
||||||
|
if key in regions:
|
||||||
|
return regions[key]
|
||||||
|
person = people[key]
|
||||||
|
cell = person['current_cell']
|
||||||
|
rect = transform.rect(cell)
|
||||||
|
transit = actor_track_transit(report, key, cell)
|
||||||
|
if transit:
|
||||||
|
other = transform.rect(report['actor_tracks'][key]['from'])
|
||||||
|
x, y = min(rect[0], other[0]), min(rect[1], other[1])
|
||||||
|
rect = [x, y, max(rect[0]+rect[2], other[0]+other[2])-x,
|
||||||
|
max(rect[1]+rect[3], other[1]+other[3])-y]
|
||||||
|
result = dict(bounds=rect, transit=transit)
|
||||||
|
fid = ''
|
||||||
|
if key in report['queue_positions']:
|
||||||
|
cx, cy = report['queue_positions'][key]
|
||||||
|
result = dict(bounds=[cx-transform.dim[0]/2, cy-height/2, transform.dim[0], height], transit=False)
|
||||||
|
elif key.startswith('guest/'):
|
||||||
|
target = person['target_facility_id']
|
||||||
|
if person['current_service_id'] and person['guest_id'] in report['rooms'].get(target, {}).get('people', []):
|
||||||
|
fid = target
|
||||||
|
else:
|
||||||
|
sid = person['staff_id']
|
||||||
|
if sid in report['carried_patients']:
|
||||||
|
result = copy.deepcopy(actor_region('staff/'+report['carried_patients'][sid]))
|
||||||
|
result['bounds'][1] -= 12*p
|
||||||
|
if 'center' in result:
|
||||||
|
result['center'][1] -= 12*p
|
||||||
|
elif sid in report['resting_staff']:
|
||||||
|
fid = report['resting_staff'][sid]
|
||||||
|
elif sid in report['treated_patients']:
|
||||||
|
fid = report['treated_patients'][sid]
|
||||||
|
else:
|
||||||
|
target = person['assigned_facility_id']
|
||||||
|
cells = occupancy.get(target, [])
|
||||||
|
workers = report['rooms'].get(target, {}).get('staff', [])
|
||||||
|
if person['state'] in ('WORKING', 'AVAILABLE') and cell in cells and sid in workers:
|
||||||
|
rect = transform.room(cells)
|
||||||
|
columns = max(int(rect[2]/(16*p)), 1)
|
||||||
|
slot = workers.index(sid)
|
||||||
|
if rect[3] >= 40*p and slot < columns:
|
||||||
|
result = dict(bounds=[rect[0]+slot*rect[2]/columns, rect[1], rect[2]/columns, rect[3]], transit=False)
|
||||||
|
if fid and occupancy.get(fid):
|
||||||
|
result = dict(bounds=transform.room(occupancy[fid]), transit=False)
|
||||||
|
if key not in report['queue_positions'] and 'center' not in result and not result.get('no_lane'):
|
||||||
|
result = landing_actor_region(state, report, transform, cell, result['bounds'], result['transit'])
|
||||||
|
regions[key] = result
|
||||||
|
return result
|
||||||
for guest in runtime['guests']['guests']:
|
for guest in runtime['guests']['guests']:
|
||||||
key, cell = 'guest/'+guest['guest_id'], guest['current_cell']
|
key, cell = 'guest/'+guest['guest_id'], guest['current_cell']
|
||||||
if key in report['boarded'] or key in report['queue_hidden'] or cell[2] != report['layer']:
|
if key in report['boarded'] or key in report['queue_hidden'] or cell[2] != report['layer']:
|
||||||
continue
|
continue
|
||||||
center = track_position(key, cell)
|
center = actor_region(key).get('center', track_position(key, cell))[:]
|
||||||
if key in report['queue_positions']:
|
if key in report['queue_positions']:
|
||||||
center = report['queue_positions'][key]
|
center = report['queue_positions'][key]
|
||||||
elif guest['current_service_id']:
|
elif guest['current_service_id']:
|
||||||
@@ -1511,7 +1599,7 @@ def compare_actor_geometry(state, report):
|
|||||||
key, cell = 'staff/'+sid, member['current_cell']
|
key, cell = 'staff/'+sid, member['current_cell']
|
||||||
if key in report['boarded'] or key in report['queue_hidden'] or cell[2] != report['layer']:
|
if key in report['boarded'] or key in report['queue_hidden'] or cell[2] != report['layer']:
|
||||||
continue
|
continue
|
||||||
center = track_position(key, cell)
|
center = actor_region(key).get('center', track_position(key, cell))[:]
|
||||||
special = True
|
special = True
|
||||||
if key in report['queue_positions']:
|
if key in report['queue_positions']:
|
||||||
special = False
|
special = False
|
||||||
@@ -1521,6 +1609,7 @@ def compare_actor_geometry(state, report):
|
|||||||
if carrier:
|
if carrier:
|
||||||
center = track_position('staff/'+carrier['staff_id'], carrier['current_cell'])
|
center = track_position('staff/'+carrier['staff_id'], carrier['current_cell'])
|
||||||
center[1] -= 12*p
|
center[1] -= 12*p
|
||||||
|
center = actor_region(key).get('center', center)[:]
|
||||||
elif sid in report['resting_staff']:
|
elif sid in report['resting_staff']:
|
||||||
center = slot_position(report['resting_staff'][sid], sid, 'staff_waiting_room', center, -height*.5)
|
center = slot_position(report['resting_staff'][sid], sid, 'staff_waiting_room', center, -height*.5)
|
||||||
elif sid in report['treated_patients']:
|
elif sid in report['treated_patients']:
|
||||||
@@ -1555,55 +1644,16 @@ def compare_actor_geometry(state, report):
|
|||||||
carried = bool(staff.get(key.removeprefix('staff/'), {}).get('carried_inventory_id')) if is_staff else False
|
carried = bool(staff.get(key.removeprefix('staff/'), {}).get('carried_inventory_id')) if is_staff else False
|
||||||
expected.append((key, center, is_staff, False, carried, True))
|
expected.append((key, center, is_staff, False, carried, True))
|
||||||
cabin_bounds[key] = cabin['rect']
|
cabin_bounds[key] = cabin['rect']
|
||||||
people = {'guest/'+r['guest_id']: r for r in runtime['guests']['guests']}
|
members, stranded = [], []
|
||||||
people.update({'staff/'+sid: r for sid, r in staff.items()})
|
|
||||||
def tracked_bounds(key, cell):
|
|
||||||
track = report['actor_tracks'][key]
|
|
||||||
transit = actor_track_transit(report, key, cell)
|
|
||||||
rect = transform.rect(cell)
|
|
||||||
if transit:
|
|
||||||
other = transform.rect(track['from'])
|
|
||||||
x, y = min(rect[0], other[0]), min(rect[1], other[1])
|
|
||||||
rect = [x, y, max(rect[0]+rect[2], other[0]+other[2])-x,
|
|
||||||
max(rect[1]+rect[3], other[1]+other[3])-y]
|
|
||||||
return rect, transit
|
|
||||||
members = []
|
|
||||||
for key, center, is_staff, receiving, carrying, riding in expected:
|
for key, center, is_staff, receiving, carrying, riding in expected:
|
||||||
person = people[key]
|
person = people[key]
|
||||||
cell = person['current_cell']
|
cell = person['current_cell']
|
||||||
track = report['actor_tracks'].get(key, {})
|
track = report['actor_tracks'].get(key, {})
|
||||||
region, transit = (cabin_bounds[key], False) if riding else tracked_bounds(key, cell)
|
relocation = {} if riding else actor_region(key)
|
||||||
fid = ''
|
if riding:
|
||||||
if not riding:
|
region, transit = cabin_bounds[key], False
|
||||||
if key in report['queue_positions']:
|
else:
|
||||||
cx, cy = report['queue_positions'][key]
|
region, transit = relocation['bounds'], relocation['transit']
|
||||||
region, transit = [cx-transform.dim[0]/2, cy-height/2, transform.dim[0], height], False
|
|
||||||
elif not is_staff:
|
|
||||||
target = person['target_facility_id']
|
|
||||||
if receiving and target in facilities and key.split('/', 1)[1] in report['rooms'].get(target, {}).get('people', []):
|
|
||||||
fid = target
|
|
||||||
else:
|
|
||||||
sid = person['staff_id']
|
|
||||||
if sid in report['carried_patients']:
|
|
||||||
carrier = staff[report['carried_patients'][sid]]
|
|
||||||
region, transit = tracked_bounds('staff/'+carrier['staff_id'], carrier['current_cell'])
|
|
||||||
region[1] -= 12*p
|
|
||||||
elif sid in report['resting_staff']:
|
|
||||||
fid = report['resting_staff'][sid]
|
|
||||||
elif sid in report['treated_patients']:
|
|
||||||
fid = report['treated_patients'][sid]
|
|
||||||
else:
|
|
||||||
target = person['assigned_facility_id']
|
|
||||||
cells = occupancy.get(target, [])
|
|
||||||
workers = report['rooms'].get(target, {}).get('staff', [])
|
|
||||||
if person['state'] in ('WORKING', 'AVAILABLE') and target in facilities and cell in cells and sid in workers:
|
|
||||||
rect = transform.room(cells)
|
|
||||||
columns = max(int(rect[2]/(16*p)), 1)
|
|
||||||
slot = workers.index(sid)
|
|
||||||
if rect[3] >= 40*p and slot < columns:
|
|
||||||
region, transit = [rect[0]+slot*rect[2]/columns, rect[1], rect[2]/columns, rect[3]], False
|
|
||||||
if fid and occupancy.get(fid):
|
|
||||||
region, transit = transform.room(occupancy[fid]), False
|
|
||||||
scale = .5 if riding else .75
|
scale = .5 if riding else .75
|
||||||
scale = max(math.floor(p*scale), 1) if p >= 2 else p*scale
|
scale = max(math.floor(p*scale), 1) if p >= 2 else p*scale
|
||||||
at = [center[0]-8*scale, center[1]+height*.5-3*p-17*scale]
|
at = [center[0]-8*scale, center[1]+height*.5-3*p-17*scale]
|
||||||
@@ -1650,13 +1700,23 @@ def compare_actor_geometry(state, report):
|
|||||||
frame=frame, variant=variant, flip=track.get('direction', 1) < 0,
|
frame=frame, variant=variant, flip=track.get('direction', 1) < 0,
|
||||||
staff=is_staff, state=states.index(person['state']), receiving=receiving,
|
staff=is_staff, state=states.index(person['state']), receiving=receiving,
|
||||||
carrying=carrying, riding=riding)
|
carrying=carrying, riding=riding)
|
||||||
members.append(dict(row=row, bounds=[region[0]+p, region[1]+p, region[2]-2*p, region[3]-2*p], transit=transit))
|
if relocation.get('no_lane'):
|
||||||
|
aggregate = next((a for a in stranded if a['at'] == relocation['badge_at']), None)
|
||||||
|
if aggregate is None:
|
||||||
|
aggregate = dict(members=[], drawn_keys=[], at=relocation['badge_at'], text='')
|
||||||
|
stranded.append(aggregate)
|
||||||
|
aggregate['members'].append(dict(key=key, id=key.split('/', 1)[1], state=row['state'], cell=cell, mode=mode))
|
||||||
|
aggregate['text'] = '+'+str(len(aggregate['members']))
|
||||||
|
else:
|
||||||
|
members.append(dict(row=row, bounds=[region[0]+p, region[1]+p, region[2]-2*p, region[3]-2*p], transit=transit))
|
||||||
fitted, aggregations = fitted_actor_layout(members, p, height)
|
fitted, aggregations = fitted_actor_layout(members, p, height)
|
||||||
|
aggregations = stranded + aggregations
|
||||||
close(report.get('actor_aggregations'), aggregations, 'actor aggregation/no-fit')
|
close(report.get('actor_aggregations'), aggregations, 'actor aggregation/no-fit')
|
||||||
require([r['key'] for r in report['actor_drawings']] == [k for k in floor_keys if k in fitted], 'actor drawing coverage/order')
|
require([r['key'] for r in report['actor_drawings']] == [k for k in floor_keys if k in fitted], 'actor drawing coverage/order')
|
||||||
actual = list(report['actor_drawings']) + [r for c in report['cabins'] for r in c['passengers']]
|
actual = list(report['actor_drawings']) + [r for c in report['cabins'] for r in c['passengers']]
|
||||||
require(len(actual) == len(fitted) and {r['key'] for r in actual} == set(fitted), 'missing or duplicate actor')
|
require(len(actual) == len(fitted) and {r['key'] for r in actual} == set(fitted), 'missing or duplicate actor')
|
||||||
for row in actual:
|
for row in actual:
|
||||||
|
require('region' not in row and 'boarded' not in row, 'unsupported actor projection flags')
|
||||||
for field, value in fitted[row['key']].items():
|
for field, value in fitted[row['key']].items():
|
||||||
close(row[field], value, 'actor fitted '+field)
|
close(row[field], value, 'actor fitted '+field)
|
||||||
exemptions = {m['row']['key'] for m in members if m['transit']}
|
exemptions = {m['row']['key'] for m in members if m['transit']}
|
||||||
|
|||||||
@@ -311,6 +311,8 @@ def collect(args):
|
|||||||
os.environ.update(P23_LAYOUT=layout, P23_OUTPUT=str(directory))
|
os.environ.update(P23_LAYOUT=layout, P23_OUTPUT=str(directory))
|
||||||
phase('layout-'+layout.lower(), 'tests/ui/test_p23_life_observation.gd')
|
phase('layout-'+layout.lower(), 'tests/ui/test_p23_life_observation.gd')
|
||||||
cases.append({'directory':directory.name, 'phase':phases[-1], 'files':r.inventory(directory)})
|
cases.append({'directory':directory.name, 'phase':phases[-1], 'files':r.inventory(directory)})
|
||||||
|
for layout in ('A', 'B'):
|
||||||
|
checker.inspect_stream(out/('layout-'+layout), root)
|
||||||
if args.input_mode == 'native_os':
|
if args.input_mode == 'native_os':
|
||||||
os_phases = collect_os(r, root, out, stage, engine, commit, refs, protected, outer)
|
os_phases = collect_os(r, root, out, stage, engine, commit, refs, protected, outer)
|
||||||
checker = module('p23_os_input_map', root/'tools/check_p23_life_observation_evidence.py')
|
checker = module('p23_os_input_map', root/'tools/check_p23_life_observation_evidence.py')
|
||||||
|
|||||||
@@ -1848,6 +1848,127 @@ class AnchoredGeometryTests(unittest.TestCase):
|
|||||||
check.compare_actor_geometry(self.snapshot, report)
|
check.compare_actor_geometry(self.snapshot, report)
|
||||||
|
|
||||||
|
|
||||||
|
class CabinLandingTests(unittest.TestCase):
|
||||||
|
"""Constructed raw topology; explicit expected draws, never OS evidence."""
|
||||||
|
setUp = AnchoredGeometryTests.setUp
|
||||||
|
person = AnchoredGeometryTests.person
|
||||||
|
drawing = AnchoredGeometryTests.drawing
|
||||||
|
|
||||||
|
def lift(self, floor=0):
|
||||||
|
self.runtime['elevators']['shafts'] = [dict(
|
||||||
|
config=dict(shaft_id='lift', stops=[[1,0,0], [1,1,0]], width=2, capacity=4),
|
||||||
|
current_floor=0, phase='PICKUP', boarding_floor=1, phase_origin_floor=0,
|
||||||
|
remaining_minutes=10*(1-floor), phase_total_minutes=10, operational=True, boarded=[])]
|
||||||
|
self.report['elevator_tracks'] = {'lift': dict(floor=floor, **{'from': floor}, at=0)}
|
||||||
|
# The transform is 64x96; the full two-cell cabin is 96x76.
|
||||||
|
y = check.math.copysign(check.math.floor(abs(174-96*floor)+.5), 174-96*floor)
|
||||||
|
self.report['cabins'] = [dict(shaft_id='lift', rect=[156,y,96,76], pixel=4,
|
||||||
|
operational=True, doors_open=False, badge_at=[156,y-14], passengers=[])]
|
||||||
|
synthetic_repair28_fields(self.report, self.snapshot)
|
||||||
|
|
||||||
|
def test_moving_guest_same_floor_and_old_position_negative(self):
|
||||||
|
self.lift()
|
||||||
|
self.person('guest/g', [1,0,0])
|
||||||
|
self.drawing('guest/g', [108,214])
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
bad = copy.deepcopy(self.report)
|
||||||
|
bad['actor_drawings'][0].update(center=[172,214], at=[148,199])
|
||||||
|
with self.assertRaisesRegex(ValueError, 'non-passenger inside cabin'):
|
||||||
|
check.compare_actor_geometry(self.snapshot, bad)
|
||||||
|
|
||||||
|
def test_transit_from_lift_to_corridor_is_relocated_before_jitter(self):
|
||||||
|
self.lift()
|
||||||
|
self.person('guest/g', [0,0,0])
|
||||||
|
self.report.update(paused=False, animation_time=.21)
|
||||||
|
self.report['actor_tracks']['guest/g']['from'] = [1,0,0]
|
||||||
|
row = self.drawing('guest/g', [108,214], 'walk')
|
||||||
|
row['frame'] += 2
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
bad = copy.deepcopy(self.report)
|
||||||
|
bad['actor_drawings'][0]['center'][0] += 1
|
||||||
|
with self.assertRaisesRegex(ValueError, 'actor fitted center'):
|
||||||
|
check.compare_actor_geometry(self.snapshot, bad)
|
||||||
|
|
||||||
|
def test_carrier_and_raised_patient_follow_relocated_region(self):
|
||||||
|
self.lift()
|
||||||
|
self.person('staff/carrier', [1,0,0], 'AVAILABLE')
|
||||||
|
self.person('staff/patient', [1,0,0], 'INCAPACITATED')
|
||||||
|
self.runtime['staff']['tasks'] = [dict(task_type='rescue_transport', state='MOVING',
|
||||||
|
assigned_staff_id='carrier', target_entity_id='patient', rescue_facility_id='')]
|
||||||
|
self.report['carried_patients'] = {'patient': 'carrier'}
|
||||||
|
self.drawing('staff/carrier', [108,214])
|
||||||
|
self.drawing('staff/patient', [108,166], 'collapsed')
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
self.assertTrue(all(not check.intersects(check.actor_identity_core(r), self.report['cabins'][0]['rect'])
|
||||||
|
for r in self.report['actor_drawings']))
|
||||||
|
|
||||||
|
def test_fractional_moving_cabin_overlaps_floor_above(self):
|
||||||
|
self.lift(.8)
|
||||||
|
self.person('guest/g', [1,1,0])
|
||||||
|
self.drawing('guest/g', [108,118])
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
|
||||||
|
def test_no_lane_aggregates_identity_and_never_drops_silently(self):
|
||||||
|
self.lift()
|
||||||
|
self.runtime['world']['walkable_cells'] = [[1,0,0], [2,0,0]]
|
||||||
|
self.person('guest/g', [1,0,0])
|
||||||
|
self.report['actor_aggregations'] = [dict(members=[dict(key='guest/g', id='g', state=3,
|
||||||
|
cell=[1,0,0], mode='idle')], drawn_keys=[], at=[60,150], text='+1')]
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
for mutation in ('badge', 'identity', 'hidden'):
|
||||||
|
bad = copy.deepcopy(self.report)
|
||||||
|
if mutation == 'badge': bad['actor_aggregations'][0]['at'][0] += 1
|
||||||
|
if mutation == 'identity': bad['actor_aggregations'][0]['members'] = []
|
||||||
|
if mutation == 'hidden':
|
||||||
|
bad['actor_aggregations'] = []
|
||||||
|
bad['queue_hidden'] = {'guest/g': True}
|
||||||
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
||||||
|
check.compare_actor_geometry(self.snapshot, bad)
|
||||||
|
self.person('guest/h', [1,0,0])
|
||||||
|
aggregate = self.report['actor_aggregations'][0]
|
||||||
|
aggregate['members'].append(dict(key='guest/h', id='h', state=3, cell=[1,0,0], mode='idle'))
|
||||||
|
aggregate['text'] = '+2'
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
|
||||||
|
def test_reserved_lane_prefers_free_then_shares_with_queue(self):
|
||||||
|
self.lift()
|
||||||
|
self.person('guest/q', [1,0,0], 'QUEUING')
|
||||||
|
self.person('guest/g', [1,0,0])
|
||||||
|
self.runtime['elevators']['trips'] = [dict(request_id='queue', actor_id='guest:q', state='QUEUED',
|
||||||
|
shaft_id='lift', connector_id='lift.link.0.1', enqueued_at=0,
|
||||||
|
**{'from': [1,0,0], 'to': [1,1,0]})]
|
||||||
|
self.report.update(queue_positions={'guest/q':[108,214]},
|
||||||
|
landing_badges=[dict(at=[60,150], text='待ち1人', cell=[1,0,0],
|
||||||
|
members=['guest/q'], visible_count=1, overflow=0)])
|
||||||
|
self.drawing('guest/q', [108,214], 'wait')
|
||||||
|
self.drawing('guest/g', [300,214])
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
self.runtime['world']['walkable_cells'].remove([3,0,0])
|
||||||
|
self.report['actor_drawings'][0].update(at=[100,199])
|
||||||
|
self.report['actor_drawings'][1].update(center=[108,214], at=[82,199])
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
|
||||||
|
def test_flags_drop_and_unnecessary_relocation_fail(self):
|
||||||
|
self.lift()
|
||||||
|
self.person('guest/g', [1,0,0])
|
||||||
|
self.drawing('guest/g', [108,214])
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
for mutation in ('riding', 'region', 'boarded', 'drop'):
|
||||||
|
bad = copy.deepcopy(self.report)
|
||||||
|
if mutation == 'riding': bad['actor_drawings'][0]['riding'] = True
|
||||||
|
if mutation == 'region':
|
||||||
|
bad['actor_drawings'][0]['region'] = 'landing'
|
||||||
|
if mutation == 'boarded': bad['boarded']['guest/g'] = True
|
||||||
|
if mutation == 'drop': bad['actor_drawings'] = []
|
||||||
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
||||||
|
check.compare_actor_geometry(self.snapshot, bad)
|
||||||
|
# A free corridor actor must remain at its actual cell, even beside a lift.
|
||||||
|
self.runtime['guests']['guests'][0]['current_cell'] = [3,0,0]
|
||||||
|
self.report['actor_tracks']['guest/g'].update(cell=[3,0,0], **{'from': [3,0,0]})
|
||||||
|
with self.assertRaisesRegex(ValueError, 'actor fitted'):
|
||||||
|
check.compare_actor_geometry(self.snapshot, self.report)
|
||||||
|
|
||||||
|
|
||||||
class ComparisonTests(unittest.TestCase):
|
class ComparisonTests(unittest.TestCase):
|
||||||
def pair(self):
|
def pair(self):
|
||||||
declaration = dict(metric='sum_destination_arrival_minutes_entire_cohort',
|
declaration = dict(metric='sum_destination_arrival_minutes_entire_cohort',
|
||||||
|
|||||||
Reference in New Issue
Block a user