Connect physical elevators and staff commutes to construction, saves, and cutaway UI
This commit is contained in:
@@ -380,6 +380,9 @@ func _test_v3_migration_and_v4_tamper_guards() -> void:
|
||||
var v3_runtime: Dictionary = v3[&"systems"][&"runtime"]
|
||||
v3_runtime[&"runtime_schema_version"] = 3
|
||||
v3_runtime.erase(&"kitchen_production")
|
||||
v3_runtime.erase(&"elevators")
|
||||
v3_runtime[&"path"][&"schema_version"] = 3
|
||||
v3_runtime[&"path"].erase(&"vertical_walk_blockers")
|
||||
v3_runtime.erase(&"operations")
|
||||
v3_runtime.erase(&"catalog_manifest")
|
||||
v3_runtime.erase(&"progression")
|
||||
@@ -396,10 +399,10 @@ func _test_v3_migration_and_v4_tamper_guards() -> void:
|
||||
_expect(int(_runtime.progression_service.metrics[&"guest_lifetime"]) == 2, "v3 migration derives lifetime Guests from active plus persistent records")
|
||||
_expect(_runtime.get_mvp_guest_definition_ids().size() == 32, "v3 migration registers the current 32 catalog definitions")
|
||||
var migrated: Dictionary = _runtime.create_snapshot()
|
||||
_expect(int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 7, "v3 migration emits canonical Runtime schema v7")
|
||||
_expect(int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 8, "v3 migration emits canonical Runtime schema v8")
|
||||
var digest := SimulationDigest.compute(migrated)
|
||||
_expect(_runtime.restore_snapshot(migrated), "canonical v7 snapshot restores")
|
||||
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "canonical v7 restore is exact")
|
||||
_expect(_runtime.restore_snapshot(migrated), "canonical v8 snapshot restores")
|
||||
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "canonical v8 restore is exact")
|
||||
|
||||
var progressed_legacy: Dictionary = v3.duplicate(true)
|
||||
progressed_legacy[&"game_state"][&"world_tree_rank"] = 2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends SceneTree
|
||||
|
||||
const FOURTEEN_DAYS_MINUTES := 14 * SimulationKernel.MINUTES_PER_DAY
|
||||
const RUNTIME_SNAPSHOT_VERSION := 7
|
||||
const RUNTIME_SNAPSHOT_VERSION := 8
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
@@ -15,7 +15,7 @@ func _run() -> void:
|
||||
print("MVP: production Operations acceptance")
|
||||
_test_waiting_choice_and_command_exactly_once()
|
||||
_test_twenty_four_hour_actual_tick_smoke()
|
||||
_test_full_partial_payroll_bankruptcy_and_v7_tamper()
|
||||
_test_full_partial_payroll_bankruptcy_and_v8_tamper()
|
||||
_test_fourteen_day_boundary_snapshot_determinism()
|
||||
await process_frame
|
||||
if _failures == 0:
|
||||
@@ -39,7 +39,7 @@ func _new_fixture(seed: int, enable_operations := true) -> Dictionary:
|
||||
int(initial_snapshot[&"systems"][&"runtime"].get(
|
||||
&"runtime_schema_version", -1
|
||||
)) == RUNTIME_SNAPSHOT_VERSION,
|
||||
"fixture emits Runtime snapshot schema v7"
|
||||
"fixture emits Runtime snapshot schema v8"
|
||||
)
|
||||
if enable_operations:
|
||||
var enabled: Dictionary = runtime.enable_production_operations()
|
||||
@@ -107,6 +107,15 @@ func _align_operations_fixture_clocks(runtime: Variant, minute: int) -> void:
|
||||
runtime.defense_service.current_minute = minute
|
||||
runtime.construction_service.current_minute = minute
|
||||
runtime.excavation_service.current_minute = minute
|
||||
# This accelerated oracle owns no cars or rides; update its empty transport
|
||||
# aggregate through the public snapshot validator instead of a private clock.
|
||||
var transport_snapshot: Dictionary = runtime.elevators.create_snapshot()
|
||||
if not (transport_snapshot[&"shafts"] as Array).is_empty() or not (transport_snapshot[&"trips"] as Array).is_empty():
|
||||
_expect(false, "accelerated Operations oracle cannot skip live elevator movement")
|
||||
return
|
||||
transport_snapshot[&"current_minute"] = minute
|
||||
if not runtime.elevators.restore_snapshot(transport_snapshot):
|
||||
_expect(false, "empty transport clock aligns through validated snapshot restore")
|
||||
|
||||
|
||||
func _operations_slice_is_healthy(fixture: Dictionary) -> bool:
|
||||
@@ -208,7 +217,7 @@ func _test_waiting_choice_and_command_exactly_once() -> void:
|
||||
var command_snapshot: Dictionary = runtime.create_snapshot()
|
||||
_expect(
|
||||
runtime.restore_snapshot(command_snapshot),
|
||||
"command history survives an exact v7 snapshot round trip"
|
||||
"command history survives an exact v8 snapshot round trip"
|
||||
)
|
||||
var restored_retry: CommandResult = runtime.submit_command(
|
||||
&"change_resources",
|
||||
@@ -248,12 +257,12 @@ func _test_twenty_four_hour_actual_tick_smoke() -> void:
|
||||
_expect(
|
||||
runtime.restore_snapshot(snapshot)
|
||||
and runtime.latest_digest == SimulationDigest.compute(snapshot),
|
||||
"24-hour actual-tick v7 snapshot round trip is exact"
|
||||
"24-hour actual-tick v8 snapshot round trip is exact"
|
||||
)
|
||||
_free_fixture(fixture)
|
||||
|
||||
|
||||
func _test_full_partial_payroll_bankruptcy_and_v7_tamper() -> void:
|
||||
func _test_full_partial_payroll_bankruptcy_and_v8_tamper() -> void:
|
||||
var fixture := _new_fixture(73_002, false)
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
var clock: Variant = fixture[&"clock"]
|
||||
@@ -340,7 +349,7 @@ func _test_full_partial_payroll_bankruptcy_and_v7_tamper() -> void:
|
||||
var terminal_digest := SimulationDigest.compute(terminal_snapshot)
|
||||
_expect(
|
||||
runtime.restore_snapshot(terminal_snapshot),
|
||||
"BANKRUPT terminal v7 snapshot restores without clearing terminal state"
|
||||
"BANKRUPT terminal v8 snapshot restores without clearing terminal state"
|
||||
)
|
||||
clock.advance_minutes(180)
|
||||
_expect(
|
||||
@@ -353,32 +362,32 @@ func _test_full_partial_payroll_bankruptcy_and_v7_tamper() -> void:
|
||||
"BANKRUPT cannot be acknowledged away"
|
||||
)
|
||||
|
||||
_assert_v7_tamper_rejected(
|
||||
_assert_v8_tamper_rejected(
|
||||
runtime,
|
||||
terminal_snapshot,
|
||||
func(tampered: Dictionary) -> void:
|
||||
tampered[&"systems"][&"runtime"][&"operations"][&"personal_action_points"] = 99,
|
||||
"v7 rejects an out-of-range Operations action-point total"
|
||||
"v8 rejects an out-of-range Operations action-point total"
|
||||
)
|
||||
_assert_v7_tamper_rejected(
|
||||
_assert_v8_tamper_rejected(
|
||||
runtime,
|
||||
terminal_snapshot,
|
||||
func(tampered: Dictionary) -> void:
|
||||
tampered[&"systems"][&"runtime"][&"next_command_id"] = int(
|
||||
tampered[&"systems"][&"runtime"][&"next_command_id"]
|
||||
) + 1,
|
||||
"v7 rejects command high-water gaps"
|
||||
"v8 rejects command high-water gaps"
|
||||
)
|
||||
_assert_v7_tamper_rejected(
|
||||
_assert_v8_tamper_rejected(
|
||||
runtime,
|
||||
terminal_snapshot,
|
||||
func(tampered: Dictionary) -> void:
|
||||
var accepted: Array = tampered[&"systems"][&"runtime"][&"command_queue"][&"accepted"]
|
||||
if not accepted.is_empty():
|
||||
accepted[0][&"idempotency_key"] = &"tampered.accepted.key",
|
||||
"v7 rejects accepted-command/idempotency cross-link tampering"
|
||||
"v8 rejects accepted-command/idempotency cross-link tampering"
|
||||
)
|
||||
_assert_v7_tamper_rejected(
|
||||
_assert_v8_tamper_rejected(
|
||||
runtime,
|
||||
terminal_snapshot,
|
||||
func(tampered: Dictionary) -> void:
|
||||
@@ -386,26 +395,26 @@ func _test_full_partial_payroll_bankruptcy_and_v7_tamper() -> void:
|
||||
summaries[0][&"category_totals"][&"wages"] = int(
|
||||
summaries[0][&"category_totals"][&"wages"]
|
||||
) + 1,
|
||||
"v7 rejects DaySummary/payroll wage reconciliation tampering"
|
||||
"v8 rejects DaySummary/payroll wage reconciliation tampering"
|
||||
)
|
||||
_assert_v7_tamper_rejected(
|
||||
_assert_v8_tamper_rejected(
|
||||
runtime,
|
||||
terminal_snapshot,
|
||||
func(tampered: Dictionary) -> void:
|
||||
tampered[&"systems"][&"runtime"][&"unexpected_v7_field"] = true,
|
||||
"v7 rejects unknown Runtime fields"
|
||||
tampered[&"systems"][&"runtime"][&"unexpected_v8_field"] = true,
|
||||
"v8 rejects unknown Runtime fields"
|
||||
)
|
||||
_assert_v7_tamper_rejected(
|
||||
_assert_v8_tamper_rejected(
|
||||
runtime,
|
||||
terminal_snapshot,
|
||||
func(tampered: Dictionary) -> void:
|
||||
tampered[&"systems"][&"runtime"][&"operations"][&"coordinator"][&"unexpected_field"] = true,
|
||||
"v7 rejects unknown Coordinator fields"
|
||||
"v8 rejects unknown Coordinator fields"
|
||||
)
|
||||
_free_fixture(fixture)
|
||||
|
||||
|
||||
func _assert_v7_tamper_rejected(
|
||||
func _assert_v8_tamper_rejected(
|
||||
runtime: Variant,
|
||||
canonical: Dictionary,
|
||||
mutator: Callable,
|
||||
@@ -454,7 +463,7 @@ func _test_fourteen_day_boundary_snapshot_determinism() -> void:
|
||||
var resumed_clock: Variant = resumed[&"clock"]
|
||||
var resumed_runtime: Variant = resumed[&"runtime"]
|
||||
var restored: bool = resumed_runtime.restore_snapshot(midpoint)
|
||||
_expect(restored, "fresh Runtime restores the enabled v7 midpoint snapshot")
|
||||
_expect(restored, "fresh Runtime restores the enabled v8 midpoint snapshot")
|
||||
var resumed_advanced := 0
|
||||
if restored:
|
||||
resumed_advanced = _advance_operations_boundary_slice(
|
||||
|
||||
@@ -57,7 +57,7 @@ func _test_disabled_compatibility_and_enable() -> void:
|
||||
_expect(not disabled[&"enabled"], "Operations remain disabled for existing Runtime tests")
|
||||
var disabled_snapshot: Dictionary = _runtime.create_snapshot()
|
||||
var disabled_runtime: Dictionary = disabled_snapshot[&"systems"][&"runtime"]
|
||||
_expect(int(disabled_runtime[&"runtime_schema_version"]) == 7, "Runtime snapshot advances to schema v7")
|
||||
_expect(int(disabled_runtime[&"runtime_schema_version"]) == 8, "Runtime snapshot advances to schema v8")
|
||||
_expect(
|
||||
disabled_runtime[&"operations"] == {
|
||||
&"wrapper_version": 1,
|
||||
@@ -361,6 +361,9 @@ func _test_daily_payroll_financials_and_crosslink_tamper() -> void:
|
||||
var downgraded: Dictionary = canonical.duplicate(true)
|
||||
downgraded[&"systems"][&"runtime"][&"runtime_schema_version"] = 4
|
||||
downgraded[&"systems"][&"runtime"].erase(&"kitchen_production")
|
||||
downgraded[&"systems"][&"runtime"].erase(&"elevators")
|
||||
downgraded[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
|
||||
downgraded[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
|
||||
_expect(
|
||||
_rejects_atomically(downgraded),
|
||||
"relabelled enabled v5 cannot discard Operations history through v4 migration"
|
||||
|
||||
@@ -93,7 +93,7 @@ func _run() -> void:
|
||||
_test_corridors_and_primary_world_coexistence()
|
||||
_test_staff_door_access_and_stable_ids()
|
||||
_test_project_unproject_replay_and_defensive_queries()
|
||||
_test_vertical_topology_rejects_unchanged()
|
||||
_test_vertical_topology_projects_without_walk_bypass()
|
||||
_test_definition_and_instance_crosslink_validation()
|
||||
_test_project_and_unproject_failures_roll_back_exactly()
|
||||
_test_callback_reentry_tamper_and_quarantine_recovery()
|
||||
@@ -202,21 +202,17 @@ func _test_project_unproject_replay_and_defensive_queries() -> void:
|
||||
_expect(context.projector.projection_revision == 2, "project and unproject each advance exactly once")
|
||||
|
||||
|
||||
func _test_vertical_topology_rejects_unchanged() -> void:
|
||||
func _test_vertical_topology_projects_without_walk_bypass() -> void:
|
||||
var context := _context([Vector3i(0, 0, PathType.FRONT)])
|
||||
var before_projector: Dictionary = context.projector.create_snapshot()
|
||||
var before_source: Dictionary = context.source.create_snapshot()
|
||||
var before_adapter: Dictionary = context.adapter.create_snapshot()
|
||||
var before_path: Dictionary = context.path.create_snapshot()
|
||||
var guest_instance := _instance(GUEST_ELEVATOR, &"facility_00000005", Vector3i(4, 4, PathType.FRONT), 40, &"construction_00000005")
|
||||
var guest = context.projector.project_facility(&"guest_elevator", GUEST_ELEVATOR, guest_instance)
|
||||
_expect(guest.code == CommandResultType.Code.VALIDATION_FAILED and guest.details[&"reason"] == context.projector.REASON_UNSUPPORTED_VERTICAL_TOPOLOGY, "guest elevator explicitly reports unsupported vertical topology")
|
||||
_expect(guest.is_success(), "guest elevator projects completed shaft topology")
|
||||
var service_instance := _instance(SERVICE_ELEVATOR, &"facility_00000006", Vector3i(8, 4, PathType.BACK), 41, &"construction_00000006")
|
||||
var service = context.projector.project_facility(&"service_elevator", SERVICE_ELEVATOR, service_instance)
|
||||
_expect(service.code == CommandResultType.Code.VALIDATION_FAILED and service.details[&"reason"] == context.projector.REASON_UNSUPPORTED_VERTICAL_TOPOLOGY, "service elevator explicitly reports unsupported vertical topology")
|
||||
_expect(context.projector.create_snapshot() == before_projector, "unsupported vertical facilities leave projector state unchanged")
|
||||
_expect(context.source.create_snapshot() == before_source and context.adapter.create_snapshot() == before_adapter and context.path.create_snapshot() == before_path, "unsupported vertical facilities leave all authoritative dependencies unchanged")
|
||||
_expect(context.projector.get_processed_request_ids().is_empty(), "unsupported vertical rejection does not consume request IDs")
|
||||
_expect(service.is_success(), "service elevator projects completed shaft topology")
|
||||
_expect(context.path.find_path(Vector3i(4, 4, 0), Vector3i(4, 5, 0)).is_empty(), "guest shaft cannot be climbed without its separately owned cabin connector")
|
||||
_expect(context.path.find_path(Vector3i(8, 4, 1), Vector3i(8, 5, 1)).is_empty(), "service shaft cannot be climbed by ordinary walk edges")
|
||||
_expect(context.path.get_vertical_walk_blockers().size() == 2 and context.projector.validate_invariants()[&"ok"], "both completed elevator footprints retain authoritative owned shaft restrictions")
|
||||
|
||||
|
||||
func _test_definition_and_instance_crosslink_validation() -> void:
|
||||
|
||||
@@ -384,13 +384,13 @@ func _test_snapshot_roundtrip_and_atomic_rejection() -> void:
|
||||
)
|
||||
original.record_actor_progress(&"off_graph_actor", Vector3i(4, 4, 0), 12)
|
||||
var snapshot := original.create_snapshot()
|
||||
_expect(int(snapshot[&"schema_version"]) == 3, "request-safe traffic snapshot uses schema version 3")
|
||||
_expect(int(snapshot[&"schema_version"]) == 4, "shaft-safe traffic snapshot uses schema version 4")
|
||||
_expect(snapshot.has(&"cell_traffic") and snapshot.has(&"actor_cells"), "snapshot persists traffic and occupancy projection")
|
||||
_expect(not snapshot.has(&"graph") and not snapshot.has(&"cache"), "snapshot still excludes derived graph and cache")
|
||||
var restored = PathServiceScript.new()
|
||||
_expect(restored.restore_snapshot(snapshot), "v3 snapshot restores before authoritative world graph")
|
||||
_expect(restored.restore_snapshot(snapshot), "v4 snapshot restores before authoritative world graph")
|
||||
_expect(restored.rebuild_graph([from_cell, to_cell]), "restored traffic validates against authoritative cells")
|
||||
_expect(restored.create_snapshot() == snapshot, "v3 snapshot roundtrip is exact")
|
||||
_expect(restored.create_snapshot() == snapshot, "v4 snapshot roundtrip is exact")
|
||||
_expect(restored.get_current_density(from_cell) == 1, "restored density is derived consistently from actors")
|
||||
_expect(restored.elevator_queue_size(&"lift_save") == 1, "restored elevator retains its request queue")
|
||||
|
||||
@@ -407,6 +407,7 @@ func _test_snapshot_roundtrip_and_atomic_rejection() -> void:
|
||||
v2_snapshot[&"schema_version"] = 2
|
||||
v2_snapshot.erase(&"known_elevator_request_ids")
|
||||
v2_snapshot.erase(&"active_elevator_units")
|
||||
v2_snapshot.erase(&"vertical_walk_blockers")
|
||||
var v2_restored = PathServiceScript.new()
|
||||
_expect(v2_restored.restore_snapshot(v2_snapshot), "schema v2 snapshot remains readable")
|
||||
_expect(v2_restored.rebuild_graph([from_cell, to_cell]), "schema v2 traffic validates against authoritative cells")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extends SceneTree
|
||||
|
||||
const RUNTIME_SNAPSHOT_VERSION := 7
|
||||
const RUNTIME_SNAPSHOT_VERSION := 8
|
||||
const INFRASTRUCTURE_SNAPSHOT_KEYS: Array[StringName] = [
|
||||
&"excavation",
|
||||
&"spatial_subslots",
|
||||
@@ -59,7 +59,7 @@ func _run() -> void:
|
||||
_test_completed_proxy_projection()
|
||||
_test_proxy_completion_raw_signal_commit_order()
|
||||
_test_proxy_completion_projection_failure_is_atomic()
|
||||
_test_vertical_preflight_rejection()
|
||||
_test_completed_elevator_topology()
|
||||
_test_demolition_projection_atomicity()
|
||||
_test_current_snapshot_strictness()
|
||||
_test_v5_proxy_migration_determinism()
|
||||
@@ -113,14 +113,14 @@ func _test_runtime_surface(fixture: Dictionary) -> bool:
|
||||
var snapshot: Dictionary = runtime.create_snapshot()
|
||||
var runtime_data: Dictionary = snapshot[&"systems"][&"runtime"]
|
||||
var current_version := int(runtime_data.get(&"runtime_schema_version", -1)) == RUNTIME_SNAPSHOT_VERSION
|
||||
_expect(current_version, "Runtime emits schema v7 with infrastructure state")
|
||||
_expect(current_version, "Runtime emits schema v8 with infrastructure state")
|
||||
ready = ready and current_version
|
||||
for key: StringName in INFRASTRUCTURE_SNAPSHOT_KEYS:
|
||||
var valid_dictionary := runtime_data.get(key, null) is Dictionary
|
||||
_expect(valid_dictionary, "current Runtime stores strict %s state" % key)
|
||||
ready = ready and valid_dictionary
|
||||
if not ready:
|
||||
print(" PENDING: Runtime v6 infrastructure surface is not connected yet; behavioral cases skipped.")
|
||||
print(" PENDING: Runtime v8 infrastructure surface is not connected yet; behavioral cases skipped.")
|
||||
return ready
|
||||
|
||||
|
||||
@@ -593,45 +593,23 @@ func _test_proxy_completion_projection_failure_is_atomic() -> void:
|
||||
_free_fixture(fixture)
|
||||
|
||||
|
||||
func _test_vertical_preflight_rejection() -> void:
|
||||
func _test_completed_elevator_topology() -> void:
|
||||
var fixture := _new_fixture(81_040)
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
_expect(_promote_rank_two(fixture, &"infra.vertical.rank"), "vertical fixture reaches Rank 2")
|
||||
var before := _infrastructure_state(fixture)
|
||||
var guest = _submit(
|
||||
runtime,
|
||||
&"build_facility",
|
||||
{
|
||||
&"definition_id": &"guest_elevator",
|
||||
&"origin": Vector3i(-6, 1, PathType.FRONT),
|
||||
&"rotation": 0,
|
||||
},
|
||||
&"infra.vertical.guest"
|
||||
)
|
||||
var service = _submit(
|
||||
runtime,
|
||||
&"build_facility",
|
||||
{
|
||||
&"definition_id": &"service_elevator",
|
||||
&"origin": Vector3i(5, 1, PathType.BACK),
|
||||
&"rotation": 0,
|
||||
},
|
||||
&"infra.vertical.service"
|
||||
)
|
||||
_expect(
|
||||
not guest.is_success()
|
||||
and guest.details.get(&"reason", &"") == &"UNSUPPORTED_VERTICAL_TOPOLOGY",
|
||||
"guest elevator is rejected at build-command preflight"
|
||||
)
|
||||
_expect(
|
||||
not service.is_success()
|
||||
and service.details.get(&"reason", &"") == &"UNSUPPORTED_VERTICAL_TOPOLOGY",
|
||||
"service elevator is rejected with the same stable reason"
|
||||
)
|
||||
_expect(
|
||||
_infrastructure_state(fixture) == before,
|
||||
"unsupported vertical preflight spends nothing and mutates no service"
|
||||
)
|
||||
var cash_before: int = runtime.economy_service.get_balance(&"PLAYER_CASH")
|
||||
for request: Array in [[&"guest_elevator", Vector3i(-6, 0, PathType.FRONT)], [&"service_elevator", Vector3i(5, 0, PathType.BACK)]]:
|
||||
var result = _submit(runtime, &"build_facility", {&"definition_id": request[0], &"origin": request[1], &"rotation": 0}, StringName("infra.vertical." + String(request[0])))
|
||||
_expect(result.is_success(), "%s is accepted through paid build-command preflight" % request[0])
|
||||
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH") == cash_before - 3700, "accepted lift construction spends actual definition costs")
|
||||
_expect(runtime.facility_infrastructure_projector.get_projection_ids().is_empty(), "unfinished lift construction does not create usable shaft topology")
|
||||
fixture[&"clock"].advance_minutes(360)
|
||||
_expect(runtime.facility_infrastructure_projector.get_projection_ids().size() == 2 and runtime.elevators.get_report()[&"shafts"].size() == 2, "completed guest and service modules project separate cabins")
|
||||
for shaft: Dictionary in runtime.elevators.get_report()[&"shafts"]:
|
||||
var result = _submit(runtime, &"set_elevator_operation", {&"shaft_id": shaft[&"shaft_id"], &"operator_open": false}, StringName("infra.vertical.stop." + String(shaft[&"shaft_id"])))
|
||||
_expect(result.is_success(), "completed lift can be stopped through Runtime command")
|
||||
_expect(runtime.path_service.find_path(shaft[&"stops"][0], shaft[&"stops"][1]).is_empty(), "stopped lift shaft never permits an ordinary vertical walk bypass")
|
||||
_expect(runtime.facility_infrastructure_projector.validate_invariants()[&"ok"], "completed elevator ROUTE and owned shaft restrictions crosslink exactly")
|
||||
_free_fixture(fixture)
|
||||
|
||||
|
||||
@@ -750,10 +728,10 @@ func _test_current_snapshot_strictness() -> void:
|
||||
_expect(built[&"ok"], "snapshot fixture stores one completed projection")
|
||||
var snapshot: Dictionary = runtime.create_snapshot()
|
||||
var expected_digest := SimulationDigest.compute(snapshot)
|
||||
_expect(runtime.restore_snapshot(snapshot), "valid Runtime v7 snapshot restores")
|
||||
_expect(runtime.restore_snapshot(snapshot), "valid Runtime v8 snapshot restores")
|
||||
_expect(
|
||||
runtime.create_snapshot() == snapshot and runtime.latest_digest == expected_digest,
|
||||
"Runtime v7 round trip is exact"
|
||||
"Runtime v8 round trip is exact"
|
||||
)
|
||||
|
||||
_assert_runtime_restore_rejected(
|
||||
@@ -761,7 +739,7 @@ func _test_current_snapshot_strictness() -> void:
|
||||
snapshot,
|
||||
func(candidate: Dictionary) -> void:
|
||||
candidate[&"systems"][&"runtime"][&"unknown_infrastructure"] = {},
|
||||
"unknown v7 Runtime field"
|
||||
"unknown v8 Runtime field"
|
||||
)
|
||||
for key: StringName in INFRASTRUCTURE_SNAPSHOT_KEYS:
|
||||
_assert_runtime_restore_rejected(
|
||||
@@ -838,9 +816,13 @@ func _test_v5_proxy_migration_determinism() -> void:
|
||||
var stripped_v6_events := _strip_v6_infrastructure_domain_history(legacy_runtime)
|
||||
legacy_runtime[&"runtime_schema_version"] = 5
|
||||
legacy_runtime.erase(&"kitchen_production")
|
||||
legacy_runtime.erase(&"elevators")
|
||||
legacy_runtime.erase(&"staff_commute")
|
||||
for key: StringName in INFRASTRUCTURE_SNAPSHOT_KEYS:
|
||||
legacy_runtime.erase(key)
|
||||
legacy_runtime[&"path"] = baseline[&"runtime"].path_service.create_snapshot()
|
||||
legacy_runtime[&"path"][&"schema_version"] = 3
|
||||
legacy_runtime[&"path"].erase(&"vertical_walk_blockers")
|
||||
_free_fixture(baseline)
|
||||
_expect(
|
||||
stripped_v6_events > 0 and _legacy_v5_infrastructure_shape_is_clean(legacy_runtime),
|
||||
@@ -855,8 +837,8 @@ func _test_v5_proxy_migration_determinism() -> void:
|
||||
_expect(first_runtime.restore_snapshot(legacy), "v5 proxy snapshot migrates through Runtime restore")
|
||||
var first_migrated: Dictionary = first_runtime.create_snapshot()
|
||||
_expect(
|
||||
int(first_migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 7,
|
||||
"v5 migration is immediately re-emitted as v7"
|
||||
int(first_migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 8,
|
||||
"v5 migration is immediately re-emitted as v8"
|
||||
)
|
||||
_expect(
|
||||
first_runtime.facility_infrastructure_projector.has_projection(instance_id)
|
||||
@@ -1057,7 +1039,7 @@ func _strip_v6_infrastructure_domain_history(runtime_data: Dictionary) -> int:
|
||||
|
||||
|
||||
func _legacy_v5_infrastructure_shape_is_clean(runtime_data: Dictionary) -> bool:
|
||||
if runtime_data.has(&"kitchen_production"):
|
||||
if runtime_data.has(&"kitchen_production") or runtime_data.has(&"elevators") or runtime_data.has(&"staff_commute"):
|
||||
return false
|
||||
for key: StringName in INFRASTRUCTURE_SNAPSHOT_KEYS:
|
||||
if runtime_data.has(key) or runtime_data.has(String(key)):
|
||||
|
||||
@@ -2,6 +2,7 @@ extends SceneTree
|
||||
|
||||
const POST_V2_RUNTIME_KEYS := [
|
||||
&"kitchen_production",
|
||||
&"elevators",
|
||||
&"movement_credits",
|
||||
&"catalog_manifest",
|
||||
&"progression",
|
||||
@@ -302,11 +303,11 @@ func _test_current_snapshot_crosslinks_and_legacy_v2() -> void:
|
||||
_clock.advance_one_minute()
|
||||
var snapshot: Dictionary = _runtime.create_snapshot()
|
||||
var runtime_data: Dictionary = snapshot[&"systems"][&"runtime"]
|
||||
_expect(int(runtime_data[&"runtime_schema_version"]) == 7, "Runtime snapshot advertises schema v7")
|
||||
_expect(int(runtime_data[&"runtime_schema_version"]) == 8, "Runtime snapshot advertises schema v8")
|
||||
_expect(not runtime_data[&"movement_credits"].is_empty(), "mid-move snapshot persists fixed-point movement credits")
|
||||
var digest := SimulationDigest.compute(snapshot)
|
||||
_expect(_runtime.restore_snapshot(snapshot), "valid mid-move v7 snapshot restores")
|
||||
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "mid-move v7 save/load is exact")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "valid mid-move v8 snapshot restores")
|
||||
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "mid-move v8 save/load is exact")
|
||||
|
||||
var corruptions: Array[Dictionary] = []
|
||||
var bad_actor := snapshot.duplicate(true)
|
||||
@@ -342,6 +343,8 @@ func _test_current_snapshot_crosslinks_and_legacy_v2() -> void:
|
||||
var legacy_data: Dictionary = legacy_v2[&"systems"][&"runtime"]
|
||||
legacy_data[&"runtime_schema_version"] = 2
|
||||
_strip_post_v2_runtime_state(legacy_data)
|
||||
legacy_data[&"path"][&"schema_version"] = 3
|
||||
legacy_data[&"path"].erase(&"vertical_walk_blockers")
|
||||
legacy_data[&"path"][&"actor_cells"] = []
|
||||
legacy_data[&"path"][&"actor_progress"] = []
|
||||
for traffic: Dictionary in legacy_data[&"path"][&"cell_traffic"]:
|
||||
@@ -350,16 +353,16 @@ func _test_current_snapshot_crosslinks_and_legacy_v2() -> void:
|
||||
traffic[&"temporary_block"] = true
|
||||
_expect(
|
||||
_is_true_runtime_v2_shape(legacy_data),
|
||||
"legacy fixture removes every v3-v7 aggregate and dedicated history instead of relabelling v7"
|
||||
"legacy fixture removes every v3-v8 aggregate and dedicated history instead of relabelling v8"
|
||||
)
|
||||
_expect(_runtime.restore_snapshot(legacy_v2), "legacy Runtime v2 deterministically rebuilds Path actors and credits")
|
||||
var migrated: Dictionary = _runtime.create_snapshot()
|
||||
_expect(
|
||||
int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 7
|
||||
int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 8
|
||||
and migrated[&"systems"][&"runtime"][&"path"][&"actor_cells"].size() == 2
|
||||
and migrated[&"systems"][&"runtime"][&"movement_credits"].size() == 2
|
||||
and bool(_runtime.path_service.get_cell_traffic(origin)[&"temporary_block"]),
|
||||
"legacy migration emits canonical v7 ownership while preserving blocked traffic state"
|
||||
"legacy migration emits canonical v8 ownership while preserving blocked traffic state"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ func _test_runtime_assignment_boundary() -> void:
|
||||
_expect(_runtime.submit_command(&"hire_staff_candidate", {&"candidate_id": id}).is_success(), "catalog employee is hired: %s" % id)
|
||||
var money_before: int = _runtime.economy_service.get_balance(_runtime.PLAYER_CASH_ACCOUNT)
|
||||
_expect(_assign(&"staff_akane", west, &"DAY").is_success(), "new command assigns hired receptionist")
|
||||
_expect(buildings[0].active_staff_ids == [&"staff_akane"] and buildings[0].state == FacilityInstance.State.OPEN, "paused assignment immediately opens the facility")
|
||||
_expect(buildings[0].active_staff_ids.is_empty() and _runtime.staff_service.get_staff(&"staff_akane").state == StaffRuntime.State.COMMUTING, "paused assignment waits for the receptionist to physically arrive")
|
||||
var old_cell: Vector3i = _runtime.staff_service.get_staff(&"staff_akane").current_cell
|
||||
var before: Dictionary = _runtime.staff_service.create_snapshot()
|
||||
for row: Array in [
|
||||
@@ -174,8 +174,10 @@ func _test_runtime_assignment_boundary() -> void:
|
||||
_expect(_runtime.staff_service.create_snapshot() == before, "route failure preserves the old assignment")
|
||||
_runtime.path_service.set_cell_temporary_block(old_cell, false)
|
||||
_clock.advance_minutes(20)
|
||||
_expect(buildings[0].active_staff_ids == [&"staff_akane"] and buildings[0].state == FacilityInstance.State.OPEN, "real commute arrival opens the first reception")
|
||||
old_cell = _runtime.staff_service.get_staff(&"staff_akane").current_cell
|
||||
_expect(_assign(&"staff_akane", east, &"DAY").is_success(), "employee can move to another compatible facility")
|
||||
_expect(buildings[0].active_staff_ids.is_empty() and buildings[1].active_staff_ids == [&"staff_akane"], "reassignment removes the old service staffing and activates the new facility")
|
||||
_expect(buildings[0].active_staff_ids.is_empty() and buildings[1].active_staff_ids.is_empty(), "reassignment removes old staffing while the new commute is in progress")
|
||||
_expect(_runtime.staff_service.get_staff(&"staff_akane").current_cell == old_cell, "Runtime assignment leaves Path position intact")
|
||||
_expect(_runtime.economy_service.get_balance(_runtime.PLAYER_CASH_ACCOUNT) == money_before, "assignment and shift changes charge no second signing fee")
|
||||
var report: Dictionary = _runtime.get_staff_management_report()
|
||||
@@ -206,7 +208,7 @@ func _test_runtime_assignment_boundary() -> void:
|
||||
var applied: Dictionary = _runtime.staff_service.get_work_assignment(&"staff_akane")
|
||||
var applied_shift: ShiftInstance = _runtime.staff_service.get_shift(_runtime.staff_service.get_staff(&"staff_akane").shift_id)
|
||||
_expect(int(applied[&"changed_at"]) == queued_at + 1 and int(applied[&"effective_from"]) == queued_at + 1, "queued assignment uses the execution minute, not the previous Staff tick")
|
||||
_expect(applied_shift.worked_minutes == 1, "command and AI phases record the execution minute only once")
|
||||
_expect(applied_shift.worked_minutes == 0, "queued reassignment cannot record work before arriving at its new workplace")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "queued assignment leaves a coherent save at the end of its tick")
|
||||
_test_active_service_reassignment(west, east)
|
||||
_test_busy_staff_and_capacity(west)
|
||||
@@ -214,6 +216,7 @@ func _test_runtime_assignment_boundary() -> void:
|
||||
|
||||
func _test_active_service_reassignment(west: StringName, east: StringName) -> void:
|
||||
_expect(_assign(&"staff_akane", east, &"DAY").is_success(), "service test keeps receptionist on DAY")
|
||||
_wait_for_attendance(&"staff_akane", east)
|
||||
var spawned = _runtime.submit_command(&"spawn_guest", {
|
||||
&"guest_id": &"reassignment_guest", &"guest_type_id": &"traveler", &"gold": 100,
|
||||
&"options": {&"state": GuestRuntime.State.DECIDING, &"current_cell": Vector3i.ZERO},
|
||||
@@ -236,6 +239,7 @@ func _test_active_service_reassignment(west: StringName, east: StringName) -> vo
|
||||
_expect(service.state == ServiceInstance.State.WAITING_FOR_STAFF, "existing service enters the normal staff-replacement grace period")
|
||||
_expect(_runtime.economy_service.get_balance(_runtime.PLAYER_CASH_ACCOUNT) == balance_before, "waiting service posts no phantom revenue")
|
||||
_expect(_assign(&"staff_akane", east, &"DAY").is_success(), "replacement can return during grace period")
|
||||
_wait_for_attendance(&"staff_akane", east)
|
||||
_clock.advance_one_minute()
|
||||
_expect(service.state == ServiceInstance.State.ACTIVE, "staff return resumes the same service instead of duplicating it")
|
||||
_expect(_assign(&"staff_akane", &"", &"DAY").is_success(), "employee can be unassigned during service")
|
||||
@@ -247,6 +251,7 @@ func _test_active_service_reassignment(west: StringName, east: StringName) -> vo
|
||||
|
||||
func _test_busy_staff_and_capacity(west: StringName) -> void:
|
||||
_expect(_assign(&"staff_akane", west, &"DAY").is_success(), "busy-task test starts with a working employee")
|
||||
_wait_for_attendance(&"staff_akane", west)
|
||||
var task = _runtime.submit_command(&"create_staff_task", {
|
||||
&"task_id": &"assignment_guard_task", &"task_type": &"reception", &"priority": 99,
|
||||
&"options": {&"target_cell": Vector3i.ZERO, &"required_role_tags": [&"receptionist"],
|
||||
@@ -270,6 +275,14 @@ func _test_busy_staff_and_capacity(west: StringName) -> void:
|
||||
_expect(_assign(&"capacity_overflow", west, &"NIGHT").is_success(), "opposite shift has its own staffing capacity")
|
||||
|
||||
|
||||
func _wait_for_attendance(staff_id: StringName, facility_id: StringName) -> void:
|
||||
for minute in range(40):
|
||||
if _runtime.facility_service.get_instance(facility_id).active_staff_ids.has(staff_id):
|
||||
return
|
||||
_clock.advance_one_minute()
|
||||
_expect(false, "employee reaches the assigned workplace through the real route")
|
||||
|
||||
|
||||
func _assign(staff_id: StringName, facility_id: StringName, template_id: StringName) -> CommandResult:
|
||||
return _runtime.submit_command(&"set_staff_work_assignment", {&"staff_id": staff_id, &"facility_id": facility_id, &"template_id": template_id})
|
||||
|
||||
|
||||
@@ -794,6 +794,9 @@ func _test_snapshot_replay_corruption_and_legacy_migration() -> void:
|
||||
var legacy: Dictionary = _runtime.create_snapshot()
|
||||
legacy[&"systems"][&"runtime"].erase(&"runtime_schema_version")
|
||||
legacy[&"systems"][&"runtime"].erase(&"kitchen_production")
|
||||
legacy[&"systems"][&"runtime"].erase(&"elevators")
|
||||
legacy[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
|
||||
legacy[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
|
||||
legacy[&"systems"][&"runtime"].erase(&"operations")
|
||||
legacy[&"systems"][&"runtime"].erase(&"defense")
|
||||
legacy[&"systems"][&"runtime"][&"rng_streams"].erase("defense")
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
extends SceneTree
|
||||
|
||||
const CommuteType = preload("res://scripts/staff/staff_commute_service.gd")
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _fixtures: Array = []
|
||||
|
||||
|
||||
## Movement is injected at Runtime's narrow boundary. Real Staff, Shift, Task,
|
||||
## Facility and staff-only Path rights run unchanged in this focused suite.
|
||||
class CommuteRuntime extends RefCounted:
|
||||
var staff_service := StaffService.new()
|
||||
var facility_service := FacilityService.new()
|
||||
var path_service := PathService.new()
|
||||
var commute = CommuteType.new()
|
||||
var movements: Array[Dictionary] = []
|
||||
var last_moved: Dictionary = {}
|
||||
var fail_movement := false
|
||||
var in_transit := false
|
||||
|
||||
func configure() -> void:
|
||||
var cells: Array[Vector3i] = []
|
||||
for layer: int in [0, 1]:
|
||||
for x in range(13): cells.append(Vector3i(x, 0, layer))
|
||||
path_service.rebuild_graph(cells)
|
||||
path_service.configure_connector(&"staff_door", Vector3i.ZERO, Vector3i(0, 0, 1), 100, true, &"staff")
|
||||
for row: Array in [[&"west", Vector3i(5, 0, 0)], [&"east", Vector3i(10, 0, 0)], [&"back", Vector3i(5, 0, 1)]]:
|
||||
facility_service.create_instance({&"instance_id": row[0], &"definition_id": &"office", &"origin": row[1], &"occupied_cells": [row[1]], &"staff_min": 1, &"staff_max": 2})
|
||||
commute.configure(self)
|
||||
|
||||
func advance_staff_commute(staff_id: StringName, target: Vector3i, minute: int, reason: StringName = &"") -> Dictionary:
|
||||
var worker := staff_service.get_staff(staff_id)
|
||||
if not reason.is_empty() or int(last_moved.get(staff_id, -1)) >= minute or fail_movement:
|
||||
return {&"moved": false}
|
||||
var path := path_service.find_path(worker.current_cell, target, [0, 1], [&"staff"])
|
||||
if path.size() <= 1: return {&"moved": false}
|
||||
var previous := worker.current_cell
|
||||
worker.current_cell = path[1]
|
||||
last_moved[staff_id] = minute
|
||||
movements.append({&"minute": minute, &"staff_id": staff_id, &"from": previous, &"to": worker.current_cell})
|
||||
return {&"moved": true}
|
||||
|
||||
func is_staff_in_transit(_staff_id: StringName) -> bool:
|
||||
return in_transit
|
||||
|
||||
func advance(minute: int) -> void:
|
||||
staff_service.advance_one_minute(minute)
|
||||
commute.tick(minute)
|
||||
for facility: FacilityInstance in facility_service.get_instances():
|
||||
var active: Array[StringName] = []
|
||||
for worker: StaffRuntime in staff_service.get_staff_members():
|
||||
if commute.is_present(worker, facility): active.append(worker.staff_id)
|
||||
facility_service.set_operational_status(facility.instance_id, {&"active_staff_ids": active}, minute)
|
||||
|
||||
func dispose() -> void:
|
||||
staff_service.configure_attendance_driver()
|
||||
commute._runtime = null
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
print("P2: physical attendance, recurring commutes and saved position replay")
|
||||
_test_legacy_contract()
|
||||
_test_walk_and_arrival_staffing()
|
||||
_test_early_arrival_and_shift_end()
|
||||
_test_late_route_repair_and_replay()
|
||||
_test_reassignment_and_return_to_post()
|
||||
_test_reassignment_after_late_arrival_counts_boundary_once()
|
||||
_test_porter_dispatch_after_check_in()
|
||||
_test_back_layer_permissions_and_unavailable()
|
||||
_test_night_shift_and_missing_workplace()
|
||||
_test_boarded_reassignment_does_not_staff_origin()
|
||||
for fixture: CommuteRuntime in _fixtures: fixture.dispose()
|
||||
print("Staff commute: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(_failures)
|
||||
|
||||
|
||||
func _fixture(role: StringName = &"receptionist", minute := 360) -> CommuteRuntime:
|
||||
var fixture := CommuteRuntime.new()
|
||||
fixture.configure()
|
||||
fixture.staff_service.create_staff(&"worker", role)
|
||||
fixture.staff_service.advance_one_minute(minute)
|
||||
_fixtures.append(fixture)
|
||||
return fixture
|
||||
|
||||
|
||||
func _plan(fixture: CommuteRuntime, facility: StringName = &"west", template: StringName = &"DAY") -> StaffRuntime:
|
||||
_expect(fixture.staff_service.set_work_assignment(&"worker", facility, template).is_success(), "repeating work plan is accepted")
|
||||
return fixture.staff_service.get_staff(&"worker")
|
||||
|
||||
|
||||
func _test_legacy_contract() -> void:
|
||||
var legacy := StaffService.new()
|
||||
legacy.create_staff(&"legacy", &"receptionist")
|
||||
legacy.advance_one_minute(360)
|
||||
legacy.set_work_assignment(&"legacy", &"west", &"DAY")
|
||||
_expect(legacy.get_staff(&"legacy").state == StaffRuntime.State.AVAILABLE, "component-only recurring plans keep their original shift contract")
|
||||
var fixture := _fixture()
|
||||
fixture.staff_service.create_standard_shift(&"worker", &"DAY", 0, &"west", &"manual")
|
||||
var worker := fixture.staff_service.get_staff(&"worker")
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE and not fixture.staff_service.requires_attendance(&"worker"), "manual legacy shifts remain available without a persistent plan")
|
||||
_expect(fixture.commute.is_present(worker, fixture.facility_service.get_instance(&"west")), "legacy manual-shift staffing keeps compatibility")
|
||||
|
||||
|
||||
func _test_walk_and_arrival_staffing() -> void:
|
||||
var fixture := _fixture()
|
||||
var worker := _plan(fixture)
|
||||
var shift := fixture.staff_service.get_shift(worker.shift_id)
|
||||
var west := fixture.facility_service.get_instance(&"west")
|
||||
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i.ZERO, "paused assignment starts a real commute without teleporting")
|
||||
_expect(shift.worked_minutes == 0 and shift.actual_started_at == -1, "assignment time is not falsely counted as work")
|
||||
_expect(not fixture.commute.is_present(worker, west), "a remote assignment cannot staff a facility")
|
||||
for minute in range(361, 365):
|
||||
var previous := worker.current_cell
|
||||
fixture.advance(minute)
|
||||
_expect(worker.current_cell.distance_to(previous) <= 1.0, "commute advances at most one route edge per minute")
|
||||
_expect(worker.current_cell == Vector3i(4, 0, 0), "worker reaches the actual facility access cell")
|
||||
_expect(worker.state == StaffRuntime.State.COMMUTING and west.active_staff_ids.is_empty(), "movement arrival activates at the next Staff AI boundary")
|
||||
fixture.advance(365)
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE and west.active_staff_ids == [&"worker"], "only physical arrival contributes the facility staff capacity")
|
||||
_expect(shift.actual_started_at == 365 and shift.worked_minutes == 1, "first recorded work minute is the arrival activation minute")
|
||||
var snapshot := fixture.staff_service.create_snapshot()
|
||||
fixture.commute.get_report()
|
||||
_expect(fixture.staff_service.create_snapshot() == snapshot, "attendance report reads do not mutate saved Staff state")
|
||||
_expect(fixture.staff_service.validate_invariants()[&"ok"], "physical commute preserves Staff invariants")
|
||||
|
||||
|
||||
func _test_early_arrival_and_shift_end() -> void:
|
||||
var fixture := _fixture(&"receptionist", 330)
|
||||
var worker := _plan(fixture)
|
||||
var shift := fixture.staff_service.get_shift(worker.shift_id)
|
||||
for minute in range(331, 360): fixture.advance(minute)
|
||||
_expect(worker.current_cell == Vector3i(4, 0, 0) and worker.state == StaffRuntime.State.COMMUTING, "early arrival waits at the workplace through the commute lead window")
|
||||
_expect(shift.actual_started_at == -1 and shift.worked_minutes == 0, "early arrival does not start paid duty before 06:00")
|
||||
fixture.advance(360)
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE and shift.actual_started_at == 360, "an employee already at the workplace starts at exactly 06:00")
|
||||
fixture.advance(1080)
|
||||
_expect(worker.state == StaffRuntime.State.OFF_DUTY and worker.shift_id.is_empty() and shift.actual_ended_at == 1080, "18:00 closes the work shift and removes attendance")
|
||||
_expect(worker.current_cell == Vector3i(4, 0, 0), "shift end keeps the real position rather than teleporting home")
|
||||
fixture.advance(1770)
|
||||
_expect(worker.state == StaffRuntime.State.COMMUTING, "next day's recurring commute begins automatically at 05:30")
|
||||
fixture.advance(1800)
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE, "next day's saved plan activates only at its new shift boundary")
|
||||
|
||||
|
||||
func _test_late_route_repair_and_replay() -> void:
|
||||
var fixture := _fixture()
|
||||
fixture.path_service.set_cell_temporary_block(Vector3i(2, 0, 0), true)
|
||||
var worker := _plan(fixture)
|
||||
for minute in range(361, 379): fixture.advance(minute)
|
||||
var shift := fixture.staff_service.get_shift(worker.shift_id)
|
||||
_expect(worker.current_cell == Vector3i.ZERO and worker.state == StaffRuntime.State.COMMUTING, "a blocked route never teleports or invents attendance")
|
||||
_expect(shift.late_minutes == 18 and shift.late_reason == CommuteType.ROUTE_BLOCKED, "lateness records the real route blockage after the grace threshold")
|
||||
_expect(shift.worked_minutes == 0 and shift.actual_started_at == -1, "unreachable staff do not accumulate fictional worked minutes")
|
||||
var checkpoint := fixture.staff_service.create_snapshot()
|
||||
var restored := _fixture()
|
||||
_expect(restored.staff_service.restore_snapshot(checkpoint), "a blocked commute snapshot restores using existing Staff and Shift data")
|
||||
restored.path_service.set_cell_temporary_block(Vector3i(2, 0, 0), true)
|
||||
_expect(restored.staff_service.create_snapshot() == checkpoint, "restored blocked attendance is exact without new service state")
|
||||
fixture.path_service.set_cell_temporary_block(Vector3i(2, 0, 0), false)
|
||||
restored.path_service.set_cell_temporary_block(Vector3i(2, 0, 0), false)
|
||||
for minute in range(379, 390):
|
||||
fixture.advance(minute)
|
||||
restored.advance(minute)
|
||||
_expect(fixture.staff_service.create_snapshot() == restored.staff_service.create_snapshot(), "route repair replays to identical work, lateness and positions after restore")
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE and worker.absence_reason.is_empty(), "repairing the route allows physical arrival and clears the active absence")
|
||||
_expect(shift.actual_started_at == 383 and shift.late_minutes == 22, "arrival keeps historical late minutes while beginning real duty")
|
||||
var midwalk := _fixture()
|
||||
var walker := _plan(midwalk)
|
||||
midwalk.advance(361)
|
||||
midwalk.advance(362)
|
||||
var moving := midwalk.staff_service.create_snapshot()
|
||||
var replay := _fixture()
|
||||
_expect(replay.staff_service.restore_snapshot(moving), "mid-walk save restores the exact current cell")
|
||||
for minute in range(363, 368):
|
||||
midwalk.advance(minute)
|
||||
replay.advance(minute)
|
||||
_expect(midwalk.staff_service.create_snapshot() == replay.staff_service.create_snapshot() and walker.current_cell == Vector3i(4, 0, 0), "mid-walk replay neither skips nor repeats route cells")
|
||||
|
||||
|
||||
func _test_reassignment_and_return_to_post() -> void:
|
||||
var fixture := _fixture()
|
||||
var worker := _plan(fixture)
|
||||
for minute in range(361, 367): fixture.advance(minute)
|
||||
var first := fixture.staff_service.get_shift(worker.shift_id)
|
||||
_expect(fixture.staff_service.set_work_assignment(&"worker", &"east", &"DAY").is_success(), "fixed staff can be reassigned after arriving")
|
||||
first = fixture.staff_service.get_shift(first.shift_instance_id)
|
||||
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i(4, 0, 0), "reassignment walks from the current location")
|
||||
_expect(first.assigned_facility_id == &"west" and first.actual_ended_at == 366, "historical duty remains attached to the old workplace")
|
||||
for minute in range(367, 374): fixture.advance(minute)
|
||||
var shift := fixture.staff_service.get_shift(worker.shift_id)
|
||||
_expect(worker.current_cell == Vector3i(9, 0, 0) and worker.state == StaffRuntime.State.AVAILABLE, "reassignment opens the new workplace after arrival")
|
||||
# Completion of a portable task leaves its employee at the work target.
|
||||
worker.current_cell = Vector3i(6, 0, 0)
|
||||
worker.transition_to(StaffRuntime.State.AVAILABLE, &"TASK_COMPLETED", 374)
|
||||
fixture.advance(374)
|
||||
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i(7, 0, 0), "fixed roles return to their post after work elsewhere")
|
||||
fixture.advance(375)
|
||||
fixture.advance(376)
|
||||
fixture.advance(377)
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE and worker.current_cell == Vector3i(9, 0, 0), "desk service resumes after returning physically")
|
||||
_expect(shift.late_minutes == 0, "returning from a completed task does not rewrite arrival lateness")
|
||||
_expect(fixture.staff_service.set_work_assignment(&"worker", &"", &"DAY").is_success(), "unassignment cancels the repeating commute")
|
||||
var cell := worker.current_cell
|
||||
fixture.advance(378)
|
||||
_expect(worker.state == StaffRuntime.State.OFF_DUTY and worker.current_cell == cell, "unassigned staff keep position and stop commuting")
|
||||
|
||||
|
||||
func _test_reassignment_after_late_arrival_counts_boundary_once() -> void:
|
||||
var fixture := _fixture()
|
||||
var worker := _plan(fixture)
|
||||
for minute in range(361, 366): fixture.advance(minute)
|
||||
var old_id := worker.shift_id
|
||||
fixture.facility_service.create_instance({&"instance_id": &"nearby", &"definition_id": &"office", &"origin": Vector3i(3, 0, 0), &"occupied_cells": [Vector3i(3, 0, 0)]})
|
||||
_expect(fixture.staff_service.get_shift(old_id).worked_minutes == 1, "late arrival has only one actual duty minute before reassignment")
|
||||
_expect(fixture.staff_service.set_work_assignment(&"worker", &"nearby", &"DAY").is_success(), "paused reassignment may choose another workplace sharing this access cell")
|
||||
var old := fixture.staff_service.get_shift(old_id)
|
||||
var replacement := fixture.staff_service.get_shift(worker.shift_id)
|
||||
_expect(old.worked_minutes == 0 and old.actual_started_at == -1 and replacement.worked_minutes == 1, "reassignment transfers the current duty minute exactly once despite earlier commute gaps")
|
||||
_expect(fixture.staff_service.restore_snapshot(fixture.staff_service.create_snapshot()), "gap-aware historical shift split restores without extra saved attendance state")
|
||||
|
||||
|
||||
func _test_porter_dispatch_after_check_in() -> void:
|
||||
var fixture := _fixture(&"porter")
|
||||
var worker := _plan(fixture)
|
||||
_expect(not worker.is_available_for_dispatch(), "new porter cannot take a delivery before checking in")
|
||||
for minute in range(361, 367): fixture.advance(minute)
|
||||
worker.current_cell = Vector3i(10, 0, 0)
|
||||
worker.transition_to(StaffRuntime.State.AVAILABLE, &"TASK_COMPLETED", 367)
|
||||
fixture.advance(367)
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE and worker.current_cell == Vector3i(10, 0, 0), "checked-in porter can accept another job from a delivery destination")
|
||||
_expect(not fixture.commute.is_present(worker, fixture.facility_service.get_instance(&"west")), "porter away from the warehouse does not falsely staff the warehouse")
|
||||
var restored := _fixture(&"porter")
|
||||
_expect(restored.staff_service.restore_snapshot(fixture.staff_service.create_snapshot()), "the porter's once-per-shift check-in survives save restoration")
|
||||
restored.advance(368)
|
||||
_expect(restored.staff_service.get_staff(&"worker").state == StaffRuntime.State.AVAILABLE, "loaded porter duty does not force an unnecessary return commute")
|
||||
fixture.advance(1080)
|
||||
fixture.advance(1770)
|
||||
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i(9, 0, 0), "the following shift requires a fresh real warehouse check-in")
|
||||
|
||||
|
||||
func _test_back_layer_permissions_and_unavailable() -> void:
|
||||
var fixture := _fixture()
|
||||
var worker := _plan(fixture, &"back")
|
||||
var access: Dictionary = fixture.commute.find_facility_access_path(Vector3i.ZERO, fixture.facility_service.get_instance(&"back"))
|
||||
_expect(access[&"reachable"] and access[&"cell"].z == 1, "commute can reach BACK facilities through an authorized staff door")
|
||||
_expect(fixture.path_service.find_path(Vector3i.ZERO, Vector3i(4, 0, 1), [0, 1], []).is_empty(), "the same staff door remains inaccessible without staff rights")
|
||||
fixture.advance(361)
|
||||
_expect(worker.current_cell == Vector3i(0, 0, 1), "cross-layer commute uses the actual connector endpoint")
|
||||
worker.health = 0
|
||||
worker.transition_to(StaffRuntime.State.INCAPACITATED, &"TEST", 362)
|
||||
fixture.advance(362)
|
||||
_expect(worker.current_cell == Vector3i(0, 0, 1) and worker.state == StaffRuntime.State.INCAPACITATED, "unavailable workers do not walk or get promoted to available")
|
||||
worker.health = 100
|
||||
worker.transition_to(StaffRuntime.State.COMMUTING, &"RECOVERED", 363)
|
||||
fixture.fail_movement = true
|
||||
fixture.advance(363)
|
||||
_expect(worker.current_cell == Vector3i(0, 0, 1), "a refused Runtime movement leaves the commute position unchanged")
|
||||
fixture.fail_movement = false
|
||||
for minute in range(364, 371): fixture.advance(minute)
|
||||
_expect(fixture.commute.is_present(worker, fixture.facility_service.get_instance(&"back")), "recovered staff finish the real BACK commute")
|
||||
_expect(not CommuteType.is_at_facility(worker, fixture.facility_service.get_instance(&"west")), "identical screen coordinates on different layers never count as attendance")
|
||||
var upper := FacilityInstance.new().configure(&"upper", &"office", {&"origin": Vector3i(4, 1, 1), &"occupied_cells": [Vector3i(4, 1, 1)]})
|
||||
_expect(not CommuteType.is_at_facility(worker, upper), "standing below an upper room cannot bypass the route to its floor")
|
||||
_expect(not fixture.commute.find_facility_access_path(worker.current_cell, upper)[&"reachable"], "a corridor underneath a room is not a valid workplace entrance")
|
||||
|
||||
|
||||
func _test_night_shift_and_missing_workplace() -> void:
|
||||
var fixture := _fixture(&"receptionist", 1410)
|
||||
var worker := _plan(fixture, &"west", &"NIGHT")
|
||||
for minute in range(1411, 1417): fixture.advance(minute)
|
||||
var shift := fixture.staff_service.get_shift(worker.shift_id)
|
||||
fixture.advance(1440)
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE and worker.shift_id == shift.shift_instance_id, "midnight retains the same physically attended NIGHT shift")
|
||||
fixture.advance(1800)
|
||||
_expect(worker.state == StaffRuntime.State.OFF_DUTY and shift.actual_ended_at == 1800, "NIGHT attendance ends at next-day 06:00")
|
||||
var missing := _fixture()
|
||||
var absent := _plan(missing, &"missing")
|
||||
missing.advance(361)
|
||||
_expect(absent.state == StaffRuntime.State.COMMUTING and absent.absence_reason == CommuteType.WORKPLACE_MISSING, "missing workplace remains a visible waiting condition")
|
||||
_expect(missing.staff_service.get_shift(absent.shift_id).worked_minutes == 0, "a missing workplace creates no phantom duty")
|
||||
|
||||
|
||||
func _test_boarded_reassignment_does_not_staff_origin() -> void:
|
||||
var fixture := _fixture()
|
||||
var worker := _plan(fixture)
|
||||
fixture.facility_service.create_instance({&"instance_id": &"boarding_post", &"definition_id": &"office", &"origin": Vector3i.ZERO, &"occupied_cells": [Vector3i.ZERO]})
|
||||
fixture.in_transit = true
|
||||
_expect(fixture.staff_service.set_work_assignment(&"worker", &"boarding_post", &"DAY").is_success(), "a boarded commute can accept a new destination plan")
|
||||
var shift := fixture.staff_service.get_shift(worker.shift_id)
|
||||
fixture.advance(361)
|
||||
_expect(worker.state == StaffRuntime.State.COMMUTING and shift.worked_minutes == 0, "the cabin's projected boarding cell cannot turn into attendance at the origin")
|
||||
_expect(not fixture.commute.is_present(worker, fixture.facility_service.get_instance(&"boarding_post")), "a passenger never supplies staff capacity while still in transit")
|
||||
_expect(fixture.commute.get_report()[&"staff"][0][&"reason"] == &"ELEVATOR_WAIT", "commute report distinguishes the owned ride from arrival at its projected origin")
|
||||
_expect(worker.current_cell == Vector3i.ZERO and fixture.movements.is_empty(), "the commute driver waits for the owned ride rather than moving its projected anchor")
|
||||
fixture.in_transit = false
|
||||
worker.current_cell = Vector3i(3, 0, 0)
|
||||
fixture.advance(362)
|
||||
_expect(worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i(2, 0, 0), "after safe disembarkation the employee walks toward the latest assignment")
|
||||
fixture.advance(363)
|
||||
fixture.advance(364)
|
||||
_expect(worker.state == StaffRuntime.State.AVAILABLE and shift.actual_started_at == 364, "new-post duty begins only after the completed ride and actual return")
|
||||
fixture.in_transit = true
|
||||
worker.current_task_id = &"owned_work"
|
||||
_expect(fixture.commute.can_attend_shift(worker, shift, 365), "an already checked-in employee's owned portable work remains duty while riding")
|
||||
worker.current_task_id = &""
|
||||
_expect(not fixture.commute.can_attend_shift(worker, shift, 365), "an idle passenger cannot become dispatchable based on its old checked-in position")
|
||||
|
||||
|
||||
func _expect(condition: bool, message: String) -> void:
|
||||
_checks += 1
|
||||
if not condition:
|
||||
_failures += 1
|
||||
push_error("FAIL: %s" % message)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cpp5niwsp4ayo
|
||||
@@ -52,16 +52,21 @@ func _run() -> void:
|
||||
return
|
||||
_expect(_command(&"hire_staff_candidate", {&"candidate_id": PORTER}), "hire the catalogue porter")
|
||||
_expect(_command(&"set_staff_work_assignment", {&"staff_id": PORTER, &"facility_id": _source, &"template_id": &"DAY"}), "assign porter to a repeating real shift")
|
||||
_expect(_worker().state == StaffRuntime.State.COMMUTING, "porter assignment begins a real warehouse commute")
|
||||
for minute in range(40):
|
||||
if _worker().state == StaffRuntime.State.AVAILABLE: break
|
||||
_clock.advance_minutes(1)
|
||||
_expect(_worker().state == StaffRuntime.State.AVAILABLE and StaffCommuteService.is_at_facility(_worker(), _runtime.facility_service.get_instance(_source)), "porter checks in physically before taking a supply job")
|
||||
_expect(_command(&"purchase_supplies", {&"warehouse_id": _source, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 8}), "purchase the source stock through the ledger")
|
||||
_expect(_command(&"create_logistics_request", {&"request_id": REQUEST, &"source_inventory_id": _inventory(_source), &"destination_inventory_id": _inventory(_destination), &"item_requirements": {SupplyCatalog.RAW_FOOD: 8}}), "create the reserved supply request")
|
||||
_expect(_quantity(_inventory(_destination)) == 0, "request creation does not deliver anything")
|
||||
_test_manual_dispatch_boundary()
|
||||
_reserved = _runtime.create_snapshot()
|
||||
var check_in_cell := _worker().current_cell
|
||||
_clock.advance_minutes(1)
|
||||
_expect(_request().assigned_carrier_id == PORTER and _worker().current_cell.distance_to(Vector3i.ZERO) <= 1.0, "dispatcher selects the actual employee without teleporting")
|
||||
var start := _worker().current_cell
|
||||
_expect(_request().assigned_carrier_id == PORTER and _worker().current_cell.distance_to(check_in_cell) <= 1.0, "dispatcher selects the checked-in employee without teleporting")
|
||||
_wait_for(LogisticsRequest.State.DELIVERING, 40)
|
||||
_expect(_request().state == LogisticsRequest.State.DELIVERING and _worker().current_cell != start, "porter walks to the source before pickup")
|
||||
_expect(_request().state == LogisticsRequest.State.DELIVERING and _worker().current_cell.distance_to(check_in_cell) <= 1.0, "physical pickup starts delivery from the checked-in warehouse access without teleporting")
|
||||
if _request().state != LogisticsRequest.State.DELIVERING:
|
||||
_finish()
|
||||
return
|
||||
@@ -69,6 +74,7 @@ func _run() -> void:
|
||||
_expect(_crosslinks(), "loaded Request, Task, reservations and cargo agree")
|
||||
_loaded = _runtime.create_snapshot()
|
||||
_expect(_runtime.restore_snapshot(_loaded), "mid-delivery Runtime snapshot restores")
|
||||
_test_same_floor_access()
|
||||
_test_ownership_and_replay()
|
||||
_test_capacity_wait()
|
||||
_test_route_recovery()
|
||||
@@ -84,6 +90,13 @@ func _run() -> void:
|
||||
_finish()
|
||||
|
||||
|
||||
func _test_same_floor_access() -> void:
|
||||
var upper := FacilityInstance.new().configure(&"upper", &"warehouse", {&"origin": Vector3i(2, 1, 1), &"occupied_cells": [Vector3i(2, 1, 1)]})
|
||||
_expect(not SupplyLogisticsBridge._cell_touches_facility(Vector3i(2, 0, 1), upper), "goods cannot be picked up from the floor below a warehouse")
|
||||
_expect(SupplyLogisticsBridge._cell_touches_facility(Vector3i(1, 1, 1), upper), "same-floor doorway remains a valid transfer boundary")
|
||||
_expect(not _runtime.supply_logistics.find_facility_access_path(Vector3i(2, 0, 1), upper).get(&"reachable", false), "source access routing cannot substitute a corridor below the destination floor")
|
||||
|
||||
|
||||
func _test_ownership_and_replay() -> void:
|
||||
var owned := _owned_snapshot()
|
||||
var manual: CommandResult = _runtime.submit_command(&"advance_logistics_request", {&"request_id": REQUEST})
|
||||
|
||||
@@ -9,7 +9,7 @@ func _initialize() -> void:
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
print("P1: Runtime v7 supply save migration")
|
||||
print("P1: Runtime v8 supply save migration")
|
||||
var source := _fixture(7301)
|
||||
var first := _fixture(7302)
|
||||
var second := _fixture(7303)
|
||||
@@ -19,28 +19,31 @@ func _run() -> void:
|
||||
source[&"clock"].advance_minutes(3)
|
||||
var canonical: Dictionary = source_runtime.create_snapshot()
|
||||
var canonical_data: Dictionary = canonical[&"systems"][&"runtime"]
|
||||
_expect(canonical_data[&"runtime_schema_version"] == 7, "current Runtime emits schema v7")
|
||||
_expect(canonical_data[&"runtime_schema_version"] == 8, "current Runtime emits schema v8")
|
||||
_expect(canonical_data[&"kitchen_production"][&"current_minute"] == source[&"clock"].absolute_minute, "production clock follows Runtime clock without existing kitchens")
|
||||
var legacy := canonical.duplicate(true)
|
||||
var legacy_data: Dictionary = legacy[&"systems"][&"runtime"]
|
||||
legacy_data[&"runtime_schema_version"] = 6
|
||||
legacy_data.erase(&"kitchen_production")
|
||||
legacy_data.erase(&"elevators")
|
||||
legacy_data[&"path"][&"schema_version"] = 3
|
||||
legacy_data[&"path"].erase(&"vertical_walk_blockers")
|
||||
_expect(legacy_data.has(&"facility_infrastructure") and legacy_data.has(&"route_projection"), "v6 fixture retains infrastructure introduced in v6")
|
||||
_expect(first_runtime.restore_snapshot(legacy), "genuine v6 state restores into v7")
|
||||
_expect(first_runtime.restore_snapshot(legacy), "genuine v6 state restores into v8")
|
||||
var migrated: Dictionary = first_runtime.create_snapshot()
|
||||
_expect(migrated[&"systems"][&"runtime"][&"runtime_schema_version"] == 7, "v6 migration re-emits current schema")
|
||||
_expect(migrated[&"systems"][&"runtime"][&"runtime_schema_version"] == 8, "v6 migration re-emits current schema")
|
||||
var kitchen: Dictionary = migrated[&"systems"][&"runtime"][&"kitchen_production"]
|
||||
_expect(kitchen[&"plans"].is_empty() and kitchen[&"active_batches"].is_empty(), "legacy migration does not infer kitchen production plans or batches")
|
||||
_expect(kitchen[&"current_minute"] == source[&"clock"].absolute_minute, "legacy production clock binds to the restored Runtime minute")
|
||||
_expect(migrated == canonical, "v6 migration preserves non-supply state exactly")
|
||||
_expect(second_runtime.restore_snapshot(legacy) and second_runtime.create_snapshot() == migrated, "v6 migration is deterministic across Runtime instances")
|
||||
_expect(first_runtime.restore_snapshot(migrated) and first_runtime.create_snapshot() == migrated, "canonical v7 round trip is exact")
|
||||
_expect(first_runtime.restore_snapshot(migrated) and first_runtime.create_snapshot() == migrated, "canonical v8 round trip is exact")
|
||||
var missing_supply := canonical.duplicate(true)
|
||||
missing_supply[&"systems"][&"runtime"].erase(&"kitchen_production")
|
||||
_expect(_rejects_atomically(first_runtime, missing_supply), "v7 cannot omit its supply aggregate")
|
||||
_expect(_rejects_atomically(first_runtime, missing_supply), "v8 cannot omit its supply aggregate")
|
||||
var relabelled := canonical.duplicate(true)
|
||||
relabelled[&"systems"][&"runtime"][&"runtime_schema_version"] = 6
|
||||
_expect(_rejects_atomically(first_runtime, relabelled), "relabelled v7 with retained kitchen state cannot masquerade as v6")
|
||||
_expect(_rejects_atomically(first_runtime, relabelled), "relabelled v8 with retained kitchen state cannot masquerade as v6")
|
||||
var bad_clock := canonical.duplicate(true)
|
||||
bad_clock[&"systems"][&"runtime"][&"kitchen_production"][&"current_minute"] -= 1
|
||||
_expect(_rejects_atomically(first_runtime, bad_clock), "production and Runtime clock disagreement is rejected")
|
||||
@@ -57,6 +60,9 @@ func _run() -> void:
|
||||
var orphaned: Dictionary = source_runtime.create_snapshot()
|
||||
orphaned[&"systems"][&"runtime"][&"runtime_schema_version"] = 6
|
||||
orphaned[&"systems"][&"runtime"].erase(&"kitchen_production")
|
||||
orphaned[&"systems"][&"runtime"].erase(&"elevators")
|
||||
orphaned[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
|
||||
orphaned[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
|
||||
_expect(_rejects_atomically(first_runtime, orphaned), "legacy label cannot hide input holds for a discarded production batch")
|
||||
for fixture: Dictionary in [source, first, second]:
|
||||
fixture[&"runtime"].free()
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _path: PathService
|
||||
var _elevators: ElevatorService
|
||||
var _config: Dictionary
|
||||
var _minute := 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var clock := root.get_node_or_null("GameClock")
|
||||
if clock != null: clock.set_paused(true)
|
||||
_test_grouping()
|
||||
_test_capacity_and_time()
|
||||
_test_access_and_shared_cabin()
|
||||
_test_restore_and_emergency()
|
||||
_test_mutations_and_cancellation()
|
||||
_test_snapshot_rejections()
|
||||
print("Elevator service: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(0 if _failures == 0 else 1)
|
||||
|
||||
|
||||
func _setup(layer := 0, floors := 4) -> void:
|
||||
_path = PathService.new()
|
||||
var cells: Array[Vector3i] = []
|
||||
for y: int in range(floors + 2):
|
||||
cells.append(Vector3i(0, y, layer))
|
||||
_path.rebuild_graph(cells)
|
||||
_elevators = ElevatorService.new()
|
||||
_elevators.configure(_path)
|
||||
_config = {&"shaft_id": StringName("transport.shaft.%d.0.0.%d" % [layer, 2 if layer == 0 else 3]),
|
||||
&"module_ids": [&"module.0", &"module.1"], &"stops": [], &"width": 2 if layer == 0 else 3,
|
||||
&"capacity": 4 if layer == 0 else 8, &"travel_minutes_per_floor": 10 if layer == 0 else 5,
|
||||
&"access_id": &"" if layer == 0 else &"staff"}
|
||||
for y: int in range(floors): (_config[&"stops"] as Array).append(Vector3i(0, y, layer))
|
||||
_expect(_elevators.synchronize([_config], 0)[&"ok"], "shaft initializes")
|
||||
_minute = 0
|
||||
|
||||
|
||||
func _request(actor: StringName, from: int, to: int, layer := 0) -> Dictionary:
|
||||
var access: Array[StringName] = []
|
||||
if layer == 1: access.append(&"staff")
|
||||
return _elevators.request_trip(actor, Vector3i(0, from, layer), Vector3i(0, to, layer), _minute, access)
|
||||
|
||||
|
||||
func _tick(count := 1) -> Array[Dictionary]:
|
||||
var arrivals: Array[Dictionary] = []
|
||||
for i: int in range(count):
|
||||
_minute += 1
|
||||
arrivals.append_array(_elevators.advance_one_minute(_minute))
|
||||
return arrivals
|
||||
|
||||
|
||||
func _car() -> Dictionary:
|
||||
return _elevators.get_report()[&"shafts"][0]
|
||||
|
||||
|
||||
func _test_grouping() -> void:
|
||||
var registry := DefinitionRegistry.new()
|
||||
registry.rebuild()
|
||||
var records: Array = []
|
||||
for row: Array in [[&"g2", &"guest_elevator", Vector3i(0, 2, 0)], [&"g0", &"guest_elevator", Vector3i(0, 0, 0)], [&"g6", &"guest_elevator", Vector3i(0, 6, 0)], [&"b0", &"service_elevator", Vector3i(0, 0, 1)], [&"other", &"reception", Vector3i(6, 0, 0)]]:
|
||||
records.append({&"instance_id": row[0], &"definition_id": row[1], &"origin": row[2]})
|
||||
var configs := ElevatorService.configurations_from_facilities(records, registry)
|
||||
_expect(configs.size() == 3, "adjacent modules form one shaft, gaps and layers form separate shafts")
|
||||
_expect(configs[0][&"stops"] == [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0), Vector3i(0, 3, 0)], "each occupied integer floor gets a landing")
|
||||
_expect(configs[0][&"module_ids"] == [&"g0", &"g2"], "construction ordering does not change module/stop ordering")
|
||||
_expect(configs[2][&"capacity"] == 8 and configs[2][&"travel_minutes_per_floor"] == 5 and configs[2][&"access_id"] == &"staff", "BACK shaft has eight seats, five minutes per floor and staff access")
|
||||
records.reverse()
|
||||
_expect(ElevatorService.configurations_from_facilities(records, registry) == configs, "configuration generation is deterministic")
|
||||
|
||||
|
||||
func _test_capacity_and_time() -> void:
|
||||
_setup()
|
||||
_expect(_path.create_snapshot()[&"connectors"].size() == 6, "four stops expose six bidirectional direct links")
|
||||
for i: int in range(5): _expect(_request(StringName("guest/%d" % i), 0, 3)[&"ok"], "guest queues without moving")
|
||||
_expect(_car()[&"passenger_count"] == 0 and _car()[&"waiting_count"] == 5, "requests do not board before simulation advances")
|
||||
_expect(_tick().is_empty() and _car()[&"passenger_count"] == 4 and _car()[&"waiting_count"] == 1, "shared cabin boards at most four FRONT actors")
|
||||
_expect(_car()[&"phase"] == &"BOARDING" and _car()[&"remaining_minutes"] == 6, "doors take two minutes plus one per boarding actor")
|
||||
_expect(_tick(6).is_empty() and _car()[&"phase"] == &"TRAVEL", "boarding duration elapses before cabin travels")
|
||||
_expect(_tick(15).is_empty() and is_equal_approx(_car()[&"visual_floor"], 1.5), "cabin interpolates actual half journey without arriving")
|
||||
_expect(_tick(15).is_empty() and _car()[&"phase"] == &"ALIGHTING", "three FRONT floors take thirty minutes before alighting")
|
||||
_expect(_tick(5).is_empty(), "passengers remain owned while doors are unloading")
|
||||
var arrivals := _tick()
|
||||
_expect(arrivals.size() == 4 and _minute == 43, "first four arrivals happen only after both door intervals and travel")
|
||||
for arrival: Dictionary in arrivals:
|
||||
_expect(arrival[&"cell"] == Vector3i(0, 3, 0) and not arrival[&"emergency"] and _path.get_active_elevator_request(arrival[&"actor_id"]).is_empty(), "arrival transfers actor once and releases Path ownership")
|
||||
_expect(_car()[&"phase"] == &"PICKUP" and _car()[&"waiting_count"] == 1, "remaining guest waits for same cabin to return")
|
||||
arrivals = _tick(66)
|
||||
_expect(arrivals.size() == 1 and arrivals[0][&"actor_id"] == &"guest/4" and _car()[&"phase"] == &"IDLE", "fifth guest waits for real return and completes without starvation")
|
||||
_expect(_tick(10).is_empty(), "completed request never arrives twice")
|
||||
var read := _elevators.get_report()
|
||||
read[&"shafts"][0][&"boarded"].append(&"tamper")
|
||||
_expect(_car()[&"boarded"].is_empty(), "report returns defensive copies")
|
||||
|
||||
|
||||
func _test_access_and_shared_cabin() -> void:
|
||||
_setup(1)
|
||||
_expect(not _elevators.request_trip(&"guest/no_access", Vector3i(0, 0, 1), Vector3i(0, 3, 1), 0)[&"ok"], "BACK elevator rejects actor without staff access")
|
||||
for i: int in range(9): _expect(_request(StringName("staff/%d" % i), 0, 3, 1)[&"ok"], "authorized staff queues")
|
||||
_tick()
|
||||
_expect(_car()[&"passenger_count"] == 8 and _car()[&"waiting_count"] == 1, "BACK cabin shares eight seats across requests")
|
||||
_tick(10)
|
||||
_expect(_car()[&"phase"] == &"TRAVEL" and _car()[&"remaining_minutes"] == 15, "BACK shaft uses five minutes per floor")
|
||||
_tick(15)
|
||||
_expect(_car()[&"phase"] == &"ALIGHTING", "BACK travel duration is physical")
|
||||
_expect(_tick(10).size() == 8, "BACK eight passengers alight together after paid time")
|
||||
_setup()
|
||||
_request(&"a", 0, 3)
|
||||
_request(&"b", 1, 2)
|
||||
_tick()
|
||||
_expect(_car()[&"passenger_count"] == 1 and _car()[&"waiting_count"] == 1, "different floor pair cannot create a second cabin")
|
||||
_expect(_elevators.get_actor_trip(&"b")[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED, "other connector actor remains waiting while shared car moves")
|
||||
_expect(_elevators.owns_connector(_elevators.get_actor_trip(&"a")[&"connector_id"]), "transport identifies its owned path step")
|
||||
_expect(not _elevators.owns_connector(&"door.other"), "unrelated door connector is not transport-owned")
|
||||
var all := _tick(100)
|
||||
_expect(all.size() == 2 and all[0][&"actor_id"] == &"a" and all[1][&"actor_id"] == &"b", "FIFO serves different floor pair after first journey")
|
||||
|
||||
|
||||
func _test_restore_and_emergency() -> void:
|
||||
_setup()
|
||||
_request(&"rider", 0, 3)
|
||||
_tick(20)
|
||||
_expect(_car()[&"phase"] == &"TRAVEL" and _car()[&"visual_floor"] > 1.0, "midride fixture is physically between floors")
|
||||
var state := _elevators.create_snapshot()
|
||||
var path_state := _path.create_snapshot()
|
||||
var future := _tick(40)
|
||||
var final_state := _elevators.create_snapshot()
|
||||
_expect(_path.restore_snapshot(path_state), "Path queue/boarded state restores")
|
||||
_expect(_elevators.restore_snapshot(state), "midride save restores against matching Path ownership: %s" % _elevators.last_error)
|
||||
_minute = state[&"current_minute"]
|
||||
_expect(_tick(40) == future and _elevators.create_snapshot() == final_state, "restored trip produces identical arrivals and final snapshot")
|
||||
_setup()
|
||||
_request(&"aboard", 0, 3)
|
||||
_request(&"waiting", 2, 3)
|
||||
_tick(20)
|
||||
var released := _elevators.set_powered(_config[&"shaft_id"], false, _minute)
|
||||
_expect(released.size() == 2, "power failure releases both waiting and boarded owners")
|
||||
_expect(released[0][&"actor_id"] == &"aboard" and released[0][&"cell"] == Vector3i(0, 2, 0) and released[0][&"emergency"], "power failure unloads boarded actor at nearest valid floor")
|
||||
_expect(released[1][&"actor_id"] == &"waiting" and released[1][&"cell"] == Vector3i(0, 2, 0) and released[1][&"cancelled"], "waiting cancellation stays on original landing for replan")
|
||||
_expect(_car()[&"operator_open"] and not _car()[&"powered"] and not _car()[&"operational"], "power loss does not alter player operation policy")
|
||||
_expect(_path.get_active_elevator_request(&"aboard").is_empty() and _elevators.get_actor_trip(&"waiting").is_empty(), "emergency leaves no orphan owner")
|
||||
_expect(not _request(&"stopped", 0, 3)[&"ok"], "stopped elevator rejects new boarding")
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "stopped elevator restores with Path outage state")
|
||||
_elevators.set_operator_open(_config[&"shaft_id"], false, _minute)
|
||||
_elevators.set_powered(_config[&"shaft_id"], true, _minute)
|
||||
_expect(not _car()[&"operator_open"] and _car()[&"powered"] and not _car()[&"operational"], "power restoration preserves player-requested stop")
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "player-stopped powered elevator state restores")
|
||||
_elevators.set_operator_open(_config[&"shaft_id"], true, _minute)
|
||||
_expect(_car()[&"operational"] and _request(&"resumed", 2, 0)[&"ok"], "player reopens restored elevator")
|
||||
_expect(_tick(27).size() == 1, "reopened cabin resumes from emergency landing with real travel")
|
||||
_setup()
|
||||
_request(&"blocked_destination", 0, 3)
|
||||
_tick(20)
|
||||
_path.configure_cell_traffic(Vector3i(0, 3, 0), 4, 0, true)
|
||||
released = _tick()
|
||||
_expect(released.size() == 1 and released[0][&"emergency"] and released[0][&"cell"] != Vector3i(0, 3, 0), "blocked destination causes emergency at a valid landing")
|
||||
_setup()
|
||||
_request(&"held", 0, 3)
|
||||
_request(&"waiting_held", 2, 3)
|
||||
_tick(15)
|
||||
for cell: Vector3i in _config[&"stops"]: _path.configure_cell_traffic(cell, 4, 0, true)
|
||||
released = _elevators.set_operator_open(_config[&"shaft_id"], false, _minute)
|
||||
_expect(released.size() == 1 and released[0][&"actor_id"] == &"waiting_held" and released[0][&"cancelled"], "all-landings closure still cancels waiting actors at their original position")
|
||||
_expect(_car()[&"passenger_count"] == 1 and not _car()[&"operational"] and not _path.get_active_elevator_request(&"held").is_empty(), "no valid landing holds boarded actor safely with ownership")
|
||||
var held_remaining: int = _car()[&"remaining_minutes"]
|
||||
_tick(20)
|
||||
_expect(_car()[&"remaining_minutes"] == held_remaining and _elevators.restore_snapshot(_elevators.create_snapshot()), "held cabin survives elapsed time and save restore without advancing or losing its rider")
|
||||
for cell: Vector3i in _config[&"stops"]: _path.configure_cell_traffic(cell)
|
||||
_elevators.set_operator_open(_config[&"shaft_id"], true, _minute)
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "repaired cabin resumes with an adjusted physical phase clock")
|
||||
_expect(_tick(40).size() == 1, "held actor arrives exactly once after landing repair")
|
||||
|
||||
|
||||
func _test_mutations_and_cancellation() -> void:
|
||||
_setup()
|
||||
_request(&"waiter", 0, 3)
|
||||
var before := _elevators.create_snapshot()
|
||||
_expect(not _elevators.synchronize([], _minute)[&"ok"] and _elevators.create_snapshot() == before, "queued rider prevents destructive shaft modification atomically")
|
||||
var larger := _config.duplicate(true)
|
||||
larger[&"module_ids"].append(&"module.2")
|
||||
larger[&"stops"].append(Vector3i(0, 4, 0))
|
||||
larger[&"stops"].append(Vector3i(0, 5, 0))
|
||||
_expect(not _elevators.synchronize([larger], _minute)[&"ok"], "extension waits until cabin and queues are idle")
|
||||
_expect(_elevators.cancel_actor(&"waiter") and _path.get_active_elevator_request(&"waiter").is_empty(), "waiting actor removal cancels Path queue ownership")
|
||||
_expect(_elevators.synchronize([larger], _minute)[&"ok"] and _car()[&"stops"].size() == 6, "idle upward extension joins existing physical cabin")
|
||||
_expect(_request(&"despawn", 0, 3)[&"ok"], "new request uses fresh request ID after cancellation")
|
||||
_tick(2)
|
||||
_expect(_elevators.cancel_actor(&"despawn") and _path.get_active_elevator_request(&"despawn").is_empty() and _car()[&"passenger_count"] == 0, "boarded actor despawn removes only seat and ownership")
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "saved doors remain valid after only passenger despawns")
|
||||
_expect(_tick(50).is_empty() and _car()[&"phase"] == &"IDLE", "empty cabin safely finishes its motion without resurrecting despawned actor")
|
||||
_expect(_elevators.synchronize([], _minute)[&"ok"] and _path.create_snapshot()[&"connectors"].is_empty(), "idle demolition removes all owned connectors")
|
||||
_setup()
|
||||
_request(&"duplicate", 0, 3)
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "queued snapshot restores with matching floor, request, and Path unit")
|
||||
var replay := _request(&"duplicate", 0, 3)
|
||||
_expect(replay[&"ok"] and replay[&"replay"] and _car()[&"waiting_count"] == 1, "same actor route request replays without duplicate queue")
|
||||
_expect(not _request(&"duplicate", 0, 2)[&"ok"], "actor cannot own another simultaneous destination")
|
||||
_expect(_elevators.advance_one_minute(8).is_empty() and _elevators.current_minute == 0, "skipped simulation minutes are rejected")
|
||||
_expect(_tick().is_empty() and _elevators.advance_one_minute(1).is_empty() and _car()[&"remaining_minutes"] == 3, "duplicate minute cannot advance cabin twice")
|
||||
|
||||
|
||||
func _test_snapshot_rejections() -> void:
|
||||
_setup()
|
||||
_request(&"saved", 0, 3)
|
||||
_tick(10)
|
||||
var saved := _elevators.create_snapshot()
|
||||
_expect(_elevators.restore_snapshot(saved), "valid active snapshot restores")
|
||||
var bad := saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"config"][&"capacity"] = 5
|
||||
_reject(bad, saved, "tampered FRONT capacity")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"config"][&"travel_minutes_per_floor"] = 1
|
||||
_reject(bad, saved, "tampered floor speed")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"remaining_minutes"] -= 1
|
||||
_reject(bad, saved, "tampered cabin countdown")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"trips"][0][&"to"] = Vector3i(0, 2, 0)
|
||||
_reject(bad, saved, "tampered destination")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"boarded"] = []
|
||||
_reject(bad, saved, "missing passenger seat")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"trips"] = []
|
||||
_reject(bad, saved, "missing active trip")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"request_sequence"] = 0
|
||||
_reject(bad, saved, "active request sequence reuse")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"powered"] = false
|
||||
_reject(bad, saved, "inconsistent power and operation")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"unexpected"] = true
|
||||
_reject(bad, saved, "unknown snapshot field")
|
||||
_tick(50)
|
||||
saved = _elevators.create_snapshot()
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"request_sequence"] = 0
|
||||
_reject(bad, saved, "retired request sequence reuse")
|
||||
_expect(_elevators.restore_snapshot(saved), "completed trip history permits valid restore")
|
||||
|
||||
|
||||
func _reject(bad: Dictionary, before: Dictionary, label: String) -> void:
|
||||
_expect(not _elevators.restore_snapshot(bad) and _elevators.create_snapshot() == before, "%s is rejected atomically" % label)
|
||||
|
||||
|
||||
func _expect(condition: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if condition: print("PASS: %s" % label)
|
||||
else:
|
||||
_failures += 1
|
||||
push_error("FAIL: %s" % label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://pe3usnvol833
|
||||
@@ -0,0 +1,147 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _runtime: Variant
|
||||
var _clock: Variant
|
||||
var _state: Variant
|
||||
var _serial := 0
|
||||
var _main: Control
|
||||
var _saw_trip := false
|
||||
var _saw_boarded := false
|
||||
var _saw_arrival := false
|
||||
var _saved := false
|
||||
var _preview_snapshot: Dictionary = {}
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
_runtime = root.get_node("SimulationRuntime")
|
||||
_clock = root.get_node("GameClock")
|
||||
_state = root.get_node("GameState")
|
||||
_clock.reset(1, 6, 0)
|
||||
_state.reset()
|
||||
_runtime.initialize(_clock, _state, 424242)
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
_clock.set_paused(true)
|
||||
root.size = Vector2i(1440, 810)
|
||||
_main = load("res://scenes/main/main.tscn").instantiate() as Control
|
||||
_main.set("manual_save_path", "user://tests/transport_%d/manual" % OS.get_process_id())
|
||||
root.add_child(_main)
|
||||
await process_frame
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
_expect(_state.get_resource(&"gold") == 5000 and _state.get_resource(&"materials") == 50, "ordinary starting budget")
|
||||
if not _build(&"guest_elevator", Vector3i(0, 0, 0)) or not _build(&"reception", Vector3i(2, 0, 0)):
|
||||
_finish(); return
|
||||
_clock.advance_minutes(300)
|
||||
_expect(_runtime.elevators.get_report()[&"shafts"].size() == 1, "completed construction registers physical cabin")
|
||||
if _runtime.elevators.get_report()[&"shafts"].is_empty(): _finish(); return
|
||||
if not _build(&"guest_elevator", Vector3i(0, 2, 0)) or not _build(&"standard_room", Vector3i(2, 2, 0)):
|
||||
_finish(); return
|
||||
_clock.advance_minutes(300)
|
||||
if not _build(&"standard_room", Vector3i(2, 3, 0)):
|
||||
_finish(); return
|
||||
_clock.advance_minutes(60)
|
||||
var report: Dictionary = _runtime.elevators.get_report()
|
||||
_expect(report[&"shafts"].size() == 1 and report[&"shafts"][0][&"stops"].size() == 4, "stacked modules share one cabin across four floors")
|
||||
_expect(_state.get_resource(&"materials") == 2, "buildings fit within 50 materials")
|
||||
var reception: FacilityInstance
|
||||
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
||||
if facility.definition_id == &"reception": reception = facility
|
||||
_expect(_cmd(&"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "hire receptionist with earned starting funds")
|
||||
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": &"staff_akane", &"facility_id": reception.instance_id, &"template_id": &"DAY"}).is_success(), "assign real commute")
|
||||
_expect(reception.active_staff_ids.is_empty(), "assignment alone does not open reception")
|
||||
for index: int in range(250):
|
||||
_clock.advance_minutes(1)
|
||||
var current: Dictionary = _runtime.elevators.get_report()
|
||||
if not current[&"trips"].is_empty(): _saw_trip = true
|
||||
for trip: Dictionary in current[&"trips"]:
|
||||
if trip[&"state"] != PathService.ELEVATOR_REQUEST_BOARDED: continue
|
||||
_saw_boarded = true
|
||||
if not _saved:
|
||||
_saved = _verify_mid_ride()
|
||||
_preview_snapshot = _runtime.create_snapshot()
|
||||
for guest: GuestRuntime in _runtime.guest_service.get_guests():
|
||||
if guest.current_cell.y >= 2: _saw_arrival = true
|
||||
if _saw_boarded and not _main.get_meta(&"captured", false) and OS.get_environment("THE_TREE_TRANSPORT_CAPTURE") == "1":
|
||||
_main.set_meta(&"captured", true)
|
||||
await _capture()
|
||||
if index % 30 == 0: await process_frame
|
||||
if not _saw_trip:
|
||||
print("TRANSPORT_DIAGNOSTIC ", _runtime.elevators.get_report(), " STAFF ", _runtime.staff_commute.get_report())
|
||||
for f: FacilityInstance in _runtime.facility_service.get_instances(): print("FAC ", f.definition_id, " ", f.state, " ", f.active_staff_ids)
|
||||
for g: GuestRuntime in _runtime.guest_service.get_guests(): print("GUEST ", g.guest_id, " ", g.state, " ", g.current_cell, " ", _runtime._project_guest_candidates(g.guest_id))
|
||||
_expect(_saw_trip, "natural guest requests an elevator")
|
||||
_expect(_saw_boarded, "natural guest boards the shared cabin")
|
||||
_expect(_saw_arrival, "natural guest physically reaches an upper floor")
|
||||
_expect(_saved, "mid-ride replay tested")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "current completed runtime restores")
|
||||
if OS.get_environment("THE_TREE_TRANSPORT_PREVIEW") == "1" and _failures == 0:
|
||||
_expect(_runtime.restore_snapshot(_preview_snapshot), "preview returns to the verified first boarding")
|
||||
if _failures > 0: _finish(); return
|
||||
await _capture()
|
||||
DisplayServer.window_set_title("The Tree · 昇降機と通勤の試遊(保存なし)")
|
||||
_clock.set_paused(true)
|
||||
print("Runtime transport: %d checks, %d failures" % [_checks, _failures])
|
||||
print("TRANSPORT_PREVIEW_READY Space resumes; test saves disabled")
|
||||
return
|
||||
_finish()
|
||||
|
||||
func _verify_mid_ride() -> bool:
|
||||
var save_path := "user://tests/transport_%d/ride" % OS.get_process_id()
|
||||
_expect(bool(_runtime.save_to_path(save_path).get(&"ok", false)), "boarded save writes isolated disk slot")
|
||||
_expect(bool(_runtime.load_from_path(save_path).get(&"ok", false)), "boarded disk slot reloads")
|
||||
var snapshot: Dictionary = _runtime.create_snapshot()
|
||||
_expect(snapshot[&"systems"][&"runtime"][&"runtime_schema_version"] == 8, "runtime schema8 records elevator state")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "mid-ride Runtime snapshot restores actor/cabin/Path ownership")
|
||||
var shaft: Dictionary = _runtime.elevators.get_report()[&"shafts"][0]
|
||||
var busy := _cmd(&"demolish_facility", {&"instance_id": shaft[&"module_ids"][1]})
|
||||
_expect(not busy.is_success() and busy.details.get(&"reason") == &"ELEVATOR_BUSY", "occupied shaft demolition rejected before spending or topology changes")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "snapshot resets rejected command metadata")
|
||||
_clock.advance_minutes(45)
|
||||
var expected: Dictionary = _runtime.create_snapshot()
|
||||
_expect(_runtime.restore_snapshot(snapshot), "rewind boarded save for deterministic replay")
|
||||
_clock.advance_minutes(45)
|
||||
_expect(_runtime._strict_variants_equal(expected, _runtime.create_snapshot()), "mid-ride save replays all Runtime state identically")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "restore original ride position after replay test")
|
||||
var stopped := _cmd(&"set_elevator_operation", {&"shaft_id": shaft[&"shaft_id"], &"operator_open": false})
|
||||
_expect(stopped.is_success(), "player stops elevator through public command")
|
||||
_expect(_runtime.elevators.get_report()[&"trips"].is_empty(), "stop releases waiting and disembarks passengers at safe floor")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "stopped elevator save restores")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "resume original operating snapshot")
|
||||
return true
|
||||
|
||||
func _capture() -> void:
|
||||
var preview: Control = _main.get("world_preview")
|
||||
_main.call("_on_facility_selected", _runtime.elevators.get_report()[&"shafts"][0][&"module_ids"][0])
|
||||
preview.call("set_zoom", 2.0)
|
||||
preview.set("pan_offset", preview.get("pan_offset") + preview.size * Vector2(0.5, 0.55) - preview.call("cell_to_canvas", Vector3i(2, 1, 0)))
|
||||
preview.queue_redraw()
|
||||
await process_frame
|
||||
await RenderingServer.frame_post_draw
|
||||
for trip: Dictionary in _runtime.elevators.get_report()[&"trips"]:
|
||||
if trip[&"state"] != PathService.ELEVATOR_REQUEST_BOARDED: continue
|
||||
var key := String(trip[&"actor_id"]).replace("guest:", "guest/").replace("staff:", "staff/")
|
||||
_expect((preview.get("_boarded_actor_ids") as Dictionary).has(key), "live rider identified in cabin rendering")
|
||||
_expect(not (preview.get("_actor_tracks") as Dictionary).has(key), "live rider is not duplicated on floor")
|
||||
var path := ProjectSettings.globalize_path("res://artifacts/transport/elevator-cabin.png")
|
||||
root.get_texture().get_image().save_png(path)
|
||||
|
||||
func _build(id: StringName, origin: Vector3i) -> bool:
|
||||
var result := _cmd(&"build_facility", {&"definition_id": id, &"origin": origin})
|
||||
_expect(result.is_success(), "build %s at %s %s" % [id, origin, result.message])
|
||||
return result.is_success()
|
||||
|
||||
func _cmd(kind: StringName, payload: Dictionary) -> CommandResult:
|
||||
_serial += 1
|
||||
return _runtime.submit_command(kind, payload, &"player", &"", 0, _clock.absolute_minute, StringName("transport.test.%d" % _serial))
|
||||
|
||||
func _expect(condition: bool, message: String) -> void:
|
||||
_checks += 1
|
||||
if not condition: _failures += 1
|
||||
print("%s %s" % ["PASS" if condition else "FAIL", message])
|
||||
|
||||
func _finish() -> void:
|
||||
print("Runtime transport: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(1 if _failures else 0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cityh7pucrssv
|
||||
@@ -0,0 +1,175 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _runtime: Variant
|
||||
var _clock: Variant
|
||||
var _state: Variant
|
||||
var _serial := 0
|
||||
const STAFF := &"staff_akane"
|
||||
const ACTOR := &"staff:staff_akane"
|
||||
|
||||
|
||||
class LateFailElevators extends ElevatorService:
|
||||
var fail_next_sync := false
|
||||
|
||||
func synchronize(configurations: Array, minute: int) -> Dictionary:
|
||||
var result := super.synchronize(configurations, minute)
|
||||
if fail_next_sync and result[&"ok"]:
|
||||
fail_next_sync = false
|
||||
last_error = "Injected failure after actual transport mutation."
|
||||
return {&"ok": false, &"reason": &"INJECTED_LATE_FAILURE"}
|
||||
return result
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_runtime = load("res://autoload/simulation_runtime.gd").new()
|
||||
_clock = load("res://autoload/game_clock.gd").new()
|
||||
_state = load("res://autoload/game_state.gd").new()
|
||||
_clock.reset(1, 6, 0)
|
||||
_state.reset()
|
||||
_expect(_runtime.initialize(_clock, _state, 81662)[&"valid"], "boundary fixture initializes")
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
_clock.set_paused(true)
|
||||
_expect(_command(&"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i.ZERO}).is_success(), "build lower lift from real starting budget")
|
||||
_clock.advance_minutes(310)
|
||||
_expect(_car().get(&"stops", []).size() == 2, "completed lower lift has two real stops")
|
||||
_expect(_command(&"hire_staff_candidate", {&"candidate_id": STAFF}).is_success(), "hire actual ground-floor actor")
|
||||
var idle: Dictionary = _runtime.create_snapshot()
|
||||
var lower_id: StringName = _car()[&"module_ids"][0]
|
||||
var extension := _command(&"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i(0, 2, 0)})
|
||||
_expect(extension.is_success(), "idle shaft accepts contiguous extension")
|
||||
var extension_id: StringName
|
||||
for job: ConstructionJob in _runtime.construction_service.get_jobs():
|
||||
if job.origin == Vector3i(0, 2, 0): extension_id = job.job_id
|
||||
var freeze: Dictionary = _runtime.advance_staff_commute(STAFF, Vector3i(0, 1, 0), _clock.absolute_minute)
|
||||
_expect(freeze.get(&"reason") == &"ELEVATOR_WAIT" and _runtime.elevators.get_report()[&"trips"].is_empty(), "in-progress extension freezes new boarding at Runtime movement boundary")
|
||||
_expect(_runtime.staff_service.get_staff(STAFF).current_cell == Vector3i.ZERO, "extension freeze never walks actor vertically")
|
||||
_clock.advance_minutes(5)
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "in-progress extension and frozen empty car survive save restore")
|
||||
_expect(_command(&"cancel_construction", {&"job_id": extension_id}).is_success(), "public cancellation releases extension freeze")
|
||||
var resumed: Dictionary = _runtime.advance_staff_commute(STAFF, Vector3i(0, 1, 0), _clock.absolute_minute)
|
||||
_expect(resumed.get(&"reason") == &"ELEVATOR_WAIT" and _runtime.elevators.get_actor_trip(ACTOR).get(&"state") == PathService.ELEVATOR_REQUEST_QUEUED, "after cancellation the same real actor queues for a cabin")
|
||||
_check_busy_commands(lower_id, "queued")
|
||||
_check_operations_rollback("queued")
|
||||
_clock.advance_minutes(1)
|
||||
_expect(_runtime.elevators.get_actor_trip(ACTOR).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "queued actor boards through next real minute")
|
||||
_check_busy_commands(lower_id, "boarded")
|
||||
_check_operations_rollback("boarded")
|
||||
var reassigned := _command(&"set_staff_work_assignment", {&"staff_id": STAFF, &"facility_id": &"", &"template_id": &"DAY"})
|
||||
_expect(reassigned.is_success() and _runtime.elevators.get_actor_trip(ACTOR).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "unassigning boarded worker preserves cabin ride until safe arrival")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "reassigned boarded worker remains a valid Runtime save")
|
||||
_clock.advance_minutes(20)
|
||||
_expect(_runtime.elevators.get_actor_trip(ACTOR).is_empty() and _runtime.staff_service.get_staff(STAFF).current_cell == Vector3i(0, 1, 0), "unassigned worker still physically finishes the existing ride")
|
||||
_expect(_runtime.restore_snapshot(idle), "restore original empty shaft for topology completion cases")
|
||||
var shaft_id: StringName = _car()[&"shaft_id"]
|
||||
_expect(_command(&"set_elevator_operation", {&"shaft_id": shaft_id, &"operator_open": false}).is_success(), "player stops idle shaft before extension")
|
||||
_expect(_command(&"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i(0, 2, 0)}).is_success(), "stopped idle shaft can extend")
|
||||
_clock.advance_minutes(310)
|
||||
_expect(_car()[&"stops"].size() == 4 and _car()[&"module_ids"].size() == 2 and not _car()[&"operator_open"], "successful extension keeps one cabin and persists player-stopped policy")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "completed extended shaft has exact save crosslinks")
|
||||
var upper_id: StringName = _car()[&"module_ids"][1]
|
||||
_expect(_command(&"demolish_facility", {&"instance_id": upper_id}).is_success(), "empty upper module demolition succeeds")
|
||||
_expect(_car()[&"stops"].size() == 2 and _car()[&"module_ids"] == [lower_id] and not _car()[&"operator_open"], "upper demolition removes its stops without re-enabling lower car")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "partially demolished shaft remains saveable")
|
||||
var injected := LateFailElevators.new()
|
||||
injected.configure(_runtime.path_service)
|
||||
_expect(injected.restore_snapshot(_runtime.elevators.create_snapshot()), "late-failure injector starts from exact live cabin state")
|
||||
_runtime.elevators = injected
|
||||
injected.fail_next_sync = true
|
||||
var before := _participants()
|
||||
var failure := _command(&"demolish_facility", {&"instance_id": lower_id})
|
||||
_expect(not failure.is_success() and failure.details.get(&"rolled_back", false), "post-demolition transport failure rolls back the public transaction")
|
||||
_expect(_participants() == before, "late rollback restores world, money, projections, construction, power and physical car exactly")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "late demolition rollback produces a valid canonical save")
|
||||
_expect(_command(&"demolish_facility", {&"instance_id": lower_id}).is_success(), "empty final shaft demolition succeeds on retry")
|
||||
_expect(_runtime.elevators.get_report()[&"shafts"].is_empty() and _runtime.path_service.get_vertical_walk_blockers().is_empty(), "final demolition clears cabin and all shaft vertical blockers")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "fully demolished world remains exactly saveable")
|
||||
_test_shift_end(idle)
|
||||
_runtime.free()
|
||||
_clock.free()
|
||||
_state.free()
|
||||
print("Transport boundaries: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(0 if _failures == 0 else 1)
|
||||
|
||||
|
||||
func _test_shift_end(idle: Dictionary) -> void:
|
||||
_expect(_runtime.restore_snapshot(idle), "shift-boundary fixture restores ordinary lower elevator")
|
||||
_expect(_command(&"build_facility", {&"definition_id": &"cafeteria", &"origin": Vector3i(2, 1, 0)}).is_success(), "real upper-floor cafeteria accepts commuting staff")
|
||||
_clock.advance_minutes(190)
|
||||
_clock.advance_minutes(1078 - _clock.absolute_minute)
|
||||
var cafeteria: StringName
|
||||
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
||||
if facility.definition_id == &"cafeteria": cafeteria = facility.instance_id
|
||||
_expect(not cafeteria.is_empty() and _command(&"hire_staff_candidate", {&"candidate_id": &"staff_chika"}).is_success(), "hire real cook just before DAY shift ends")
|
||||
var assignment := {&"staff_id": &"staff_chika", &"facility_id": cafeteria, &"template_id": &"DAY"}
|
||||
_expect(_command(&"set_staff_work_assignment", assignment).is_success(), "cook receives real persistent DAY work plan")
|
||||
var before_commute: Dictionary = _runtime.create_snapshot()
|
||||
_clock.advance_minutes(1)
|
||||
var actor_id := &"staff:staff_chika"
|
||||
var queued: Dictionary = _runtime.elevators.get_actor_trip(actor_id)
|
||||
_expect(queued.get(&"state") == PathService.ELEVATOR_REQUEST_QUEUED, "17:59 cook naturally queues during actual commute")
|
||||
var unchanged := _command(&"set_staff_work_assignment", assignment)
|
||||
_expect(unchanged.is_success() and unchanged.details.get(&"reason") == &"ASSIGNMENT_UNCHANGED" and _runtime.elevators.get_actor_trip(actor_id) == queued, "unchanged work plan preserves the same FIFO request")
|
||||
_clock.advance_minutes(1)
|
||||
var worker: StaffRuntime = _runtime.staff_service.get_staff(&"staff_chika")
|
||||
_expect(worker.state == StaffRuntime.State.OFF_DUTY and _runtime.elevators.get_actor_trip(actor_id).is_empty(), "18:00 shift end cancels stale commute before the available cabin can board")
|
||||
_expect(worker.current_cell == Vector3i.ZERO and _runtime.path_service.get_active_elevator_request(actor_id).is_empty(), "expired commute leaves cook at original landing without orphaned Path ownership")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "cancelled shift-boundary wait is a valid Runtime save")
|
||||
_expect(_runtime.restore_snapshot(before_commute), "restore 17:58 for already-boarded shift-boundary case")
|
||||
_runtime.advance_staff_commute(&"staff_chika", Vector3i(0, 1, 0), _clock.absolute_minute)
|
||||
_clock.advance_minutes(1)
|
||||
_expect(_runtime.elevators.get_actor_trip(actor_id).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "cook queued a minute earlier is already aboard before shift end")
|
||||
_clock.advance_minutes(1)
|
||||
worker = _runtime.staff_service.get_staff(&"staff_chika")
|
||||
_expect(worker.state == StaffRuntime.State.OFF_DUTY and _runtime.elevators.get_actor_trip(actor_id).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "shift end retains already-boarded rider until physical alighting")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "off-duty boarded employee remains a valid save")
|
||||
_clock.advance_minutes(20)
|
||||
worker = _runtime.staff_service.get_staff(&"staff_chika")
|
||||
_expect(worker.current_cell == Vector3i(0, 1, 0) and _runtime.elevators.get_actor_trip(actor_id).is_empty(), "off-duty boarded cook completes exactly one physical arrival")
|
||||
|
||||
|
||||
func _check_busy_commands(module_id: StringName, label: String) -> void:
|
||||
var before := _participants()
|
||||
var build := _command(&"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i(0, 2, 0)})
|
||||
_expect(not build.is_success() and build.details.get(&"reason") == &"ELEVATOR_BUSY" and _participants() == before, "%s passengers reject extension before any participant changes" % label)
|
||||
var demolition := _command(&"demolish_facility", {&"instance_id": module_id})
|
||||
_expect(not demolition.is_success() and demolition.details.get(&"reason") == &"ELEVATOR_BUSY" and _participants() == before, "%s passengers reject demolition before any participant changes" % label)
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "%s rejected topology commands preserve a valid save" % label)
|
||||
|
||||
|
||||
func _check_operations_rollback(label: String) -> void:
|
||||
var before: Dictionary = _runtime.create_snapshot()
|
||||
var token: Dictionary = _runtime._capture_operations_external_state()
|
||||
_expect(not token.is_empty(), "%s ride can participate in Operations snapshot boundary" % label)
|
||||
var stopped := _command(&"set_elevator_operation", {&"shaft_id": _car()[&"shaft_id"], &"operator_open": false})
|
||||
_expect(stopped.is_success() and _runtime.elevators.get_report()[&"trips"].is_empty(), "%s rollback fixture actually mutates car and Path ownership through public command" % label)
|
||||
_expect(_runtime._restore_operations_external_state(token), "%s Operations rollback restores external participants" % label)
|
||||
_expect(_runtime.create_snapshot() == before, "%s Operations rollback restores full Runtime including command IDs and event state exactly" % label)
|
||||
|
||||
|
||||
func _participants() -> Dictionary:
|
||||
var snapshot: Dictionary = _runtime._capture_infrastructure_demolition_state()
|
||||
snapshot.erase(&"domain_events")
|
||||
snapshot.erase(&"id_generator")
|
||||
return snapshot
|
||||
|
||||
|
||||
func _car() -> Dictionary:
|
||||
var shafts: Array = _runtime.elevators.get_report()[&"shafts"]
|
||||
return shafts[0] if not shafts.is_empty() else {}
|
||||
|
||||
|
||||
func _command(kind: StringName, payload: Dictionary) -> CommandResult:
|
||||
_serial += 1
|
||||
return _runtime.submit_command(kind, payload, &"player", &"", 0, _clock.absolute_minute, StringName("transport.boundary.%d" % _serial))
|
||||
|
||||
|
||||
func _expect(condition: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not condition: _failures += 1
|
||||
print("%s: %s" % ["PASS" if condition else "FAIL", label])
|
||||
@@ -0,0 +1 @@
|
||||
uid://bjv4yxwqibp1n
|
||||
@@ -0,0 +1,143 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _serial := 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var fixture := _fixture(81423)
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
var clock: Variant = fixture[&"clock"]
|
||||
clock.advance_minutes(3)
|
||||
var current: Dictionary = runtime.create_snapshot()
|
||||
_expect(current[&"systems"][&"runtime"][&"runtime_schema_version"] == 8, "Runtime emits schema8 with elevator aggregate")
|
||||
var legacy := current.duplicate(true)
|
||||
var data: Dictionary = legacy[&"systems"][&"runtime"]
|
||||
data[&"runtime_schema_version"] = 7
|
||||
data.erase(&"elevators")
|
||||
data[&"path"][&"schema_version"] = 3
|
||||
data[&"path"].erase(&"vertical_walk_blockers")
|
||||
_expect(runtime.restore_snapshot(legacy), "genuine v7 without lifts migrates from Path3 into Runtime8")
|
||||
_expect(runtime.create_snapshot() == current, "empty-lift migration preserves every existing system exactly")
|
||||
var bad := current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"runtime_schema_version"] = 7
|
||||
_expect(_rejects(runtime, bad), "outer-only downgrade retaining elevator state is rejected atomically")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"].erase(&"elevators")
|
||||
_expect(_rejects(runtime, bad), "Runtime8 cannot omit elevator aggregate")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"current_minute"] -= 1
|
||||
_expect(_rejects(runtime, bad), "elevator clock must match Runtime domain minute")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
|
||||
_expect(_rejects(runtime, bad), "Path3 relabel with retained vertical blocker metadata is rejected")
|
||||
_expect(_command(runtime, clock, &"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i(0, 0, 0)}).is_success(), "real construction starts elevator module")
|
||||
clock.advance_minutes(310)
|
||||
_expect(_command(runtime, clock, &"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "hire actual actor at entrance for ownership fixture")
|
||||
var worker: StaffRuntime = runtime.staff_service.get_staff(&"staff_akane")
|
||||
var actor_id := &"staff:staff_akane"
|
||||
_expect(worker != null and worker.current_cell == Vector3i.ZERO, "hired actor is physically at elevator's ground landing")
|
||||
var access: Array[StringName] = [&"staff"]
|
||||
var queued: Dictionary = runtime.elevators.request_trip(actor_id, Vector3i.ZERO, Vector3i(0, 1, 0), clock.absolute_minute, access)
|
||||
_expect(queued.get(&"ok", false), "queue real actor through transport ownership API: %s" % queued)
|
||||
if not queued.get(&"ok", false):
|
||||
for key: StringName in [&"runtime", &"clock", &"state"]: fixture[key].free()
|
||||
quit(1)
|
||||
return
|
||||
clock.advance_minutes(1)
|
||||
var trip: Dictionary = runtime.elevators.get_actor_trip(actor_id)
|
||||
_expect(trip.get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "save fixture owns a physical cabin seat")
|
||||
current = runtime.create_snapshot()
|
||||
_expect(runtime.restore_snapshot(current), "Runtime8 restores actual cabin actor and projected origin crosslinks")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"trips"][0][&"actor_id"] = &"staff:missing_person"
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"boarded"][0] = &"staff:missing_person"
|
||||
for unit: Dictionary in bad[&"systems"][&"runtime"][&"path"][&"active_elevator_units"]:
|
||||
if unit[&"unit_id"] == actor_id: unit[&"unit_id"] = &"staff:missing_person"
|
||||
_expect(_rejects(runtime, bad), "internally consistent seat and Path owner cannot refer to nonexistent Runtime actor")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"config"][&"module_ids"][0] = &"missing_module"
|
||||
_expect(_rejects(runtime, bad), "elevator cabin cannot substitute a module absent from real construction")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"boarded"] = []
|
||||
_expect(_rejects(runtime, bad), "active Path rider cannot lose its cabin seat")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"trips"][0][&"from"] = Vector3i(0, 1, 0)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"trips"][0][&"to"] = Vector3i.ZERO
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"boarding_floor"] = 1
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"target_floor"] = 0
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"current_floor"] = 1
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"phase_origin_floor"] = 1
|
||||
_expect(_rejects(runtime, bad), "valid reverse cabin route cannot move actor's saved logical origin")
|
||||
var shaft_id: StringName = runtime.elevators.get_report()[&"shafts"][0][&"shaft_id"]
|
||||
_expect(_command(runtime, clock, &"set_elevator_operation", {&"shaft_id": shaft_id, &"operator_open": false}).is_success(), "player closes running elevator through public command")
|
||||
current = runtime.create_snapshot()
|
||||
_expect(runtime.restore_snapshot(current) and not runtime.elevators.get_report()[&"shafts"][0][&"operator_open"], "closed operator policy survives Runtime restore")
|
||||
for key: StringName in [&"runtime", &"clock", &"state"]: fixture[key].free()
|
||||
_test_attendance_migration()
|
||||
print("Transport save: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(0 if _failures == 0 else 1)
|
||||
|
||||
|
||||
func _test_attendance_migration() -> void:
|
||||
var fixture := _fixture(81424)
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
var clock: Variant = fixture[&"clock"]
|
||||
_expect(_command(runtime, clock, &"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(2, 0, 0)}).is_success(), "attendance fixture builds a real workplace")
|
||||
clock.advance_minutes(180)
|
||||
var facility: FacilityInstance = runtime.facility_service.get_instances()[0]
|
||||
_expect(_command(runtime, clock, &"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "attendance fixture hires actual receptionist")
|
||||
_expect(_command(runtime, clock, &"set_staff_work_assignment", {&"staff_id": &"staff_akane", &"facility_id": facility.instance_id, &"template_id": &"DAY"}).is_success(), "attendance fixture assigns a real shift")
|
||||
var worker: StaffRuntime = runtime.staff_service.get_staff(&"staff_akane")
|
||||
_expect(not StaffCommuteService.is_at_facility(worker, facility), "employee has not yet physically walked to reception")
|
||||
# Reproduce the old logical-attendance rule without changing position, route,
|
||||
# income or inventory. Facility projection is rebuilt by its public service.
|
||||
worker.state = StaffRuntime.State.AVAILABLE
|
||||
runtime.facility_service.set_operational_status(facility.instance_id, {&"active_staff_ids": [&"staff_akane"]}, clock.absolute_minute)
|
||||
var logical: Dictionary = runtime.create_snapshot()
|
||||
_expect(_rejects(runtime, logical), "Runtime8 rejects an AVAILABLE employee counted at a distant workplace")
|
||||
var legacy := logical.duplicate(true)
|
||||
var data: Dictionary = legacy[&"systems"][&"runtime"]
|
||||
data[&"runtime_schema_version"] = 7
|
||||
data.erase(&"elevators")
|
||||
data[&"path"][&"schema_version"] = 3
|
||||
data[&"path"].erase(&"vertical_walk_blockers")
|
||||
_expect(runtime.restore_snapshot(legacy), "genuine v7 logical attendance migrates into physical attendance")
|
||||
facility = runtime.facility_service.get_instance(facility.instance_id)
|
||||
worker = runtime.staff_service.get_staff(&"staff_akane")
|
||||
_expect(facility.active_staff_ids.is_empty() and worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i.ZERO, "migration removes phantom workplace capacity and starts commute without teleporting")
|
||||
_expect(runtime.restore_snapshot(runtime.create_snapshot()), "migrated attendance is a valid current Runtime snapshot")
|
||||
for key: StringName in [&"runtime", &"clock", &"state"]: fixture[key].free()
|
||||
|
||||
|
||||
func _fixture(seed: int) -> Dictionary:
|
||||
var clock: Variant = load("res://autoload/game_clock.gd").new()
|
||||
var state: Variant = load("res://autoload/game_state.gd").new()
|
||||
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
|
||||
clock.reset(1, 6, 0)
|
||||
state.reset()
|
||||
_expect(runtime.initialize(clock, state, seed)[&"valid"], "transport save fixture initializes")
|
||||
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
clock.set_paused(true)
|
||||
return {&"clock": clock, &"state": state, &"runtime": runtime}
|
||||
|
||||
|
||||
func _command(runtime: Variant, clock: Variant, kind: StringName, payload: Dictionary) -> CommandResult:
|
||||
_serial += 1
|
||||
return runtime.submit_command(kind, payload, &"player", &"", 0, clock.absolute_minute, StringName("transport.save.%d" % _serial))
|
||||
|
||||
|
||||
func _rejects(runtime: Variant, candidate: Dictionary) -> bool:
|
||||
var before: Dictionary = runtime.create_snapshot()
|
||||
return not runtime.restore_snapshot(candidate) and runtime.create_snapshot() == before
|
||||
|
||||
|
||||
func _expect(condition: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not condition: _failures += 1
|
||||
print("%s: %s" % ["PASS" if condition else "FAIL", label])
|
||||
@@ -0,0 +1 @@
|
||||
uid://daygk8x6blorg
|
||||
@@ -0,0 +1,101 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _main: Control
|
||||
var _runtime: Variant
|
||||
var _clock: Variant
|
||||
var _module_id: StringName = &""
|
||||
var _shaft_id: StringName = &""
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
_clock = root.get_node("GameClock")
|
||||
_runtime = root.get_node("SimulationRuntime")
|
||||
var state: Variant = root.get_node("GameState")
|
||||
_clock.reset(1, 6, 0)
|
||||
state.reset()
|
||||
_runtime.initialize(_clock, state, 924263)
|
||||
_clock.set_paused(true)
|
||||
root.size = Vector2i(1280, 720)
|
||||
_main = load("res://scenes/main/main.tscn").instantiate() as Control
|
||||
_main.set("manual_save_path", "user://tests/elevator_ui_%d/manual" % OS.get_process_id())
|
||||
root.add_child(_main)
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
await process_frame
|
||||
await process_frame
|
||||
_expect(not _main.get("selected_elevator_controls").visible, "empty inspector hides lift controls")
|
||||
_main.call("_select_build_definition", &"guest_elevator")
|
||||
_main.call("_on_map_cell_clicked", Vector3i(0, 0, 0))
|
||||
_expect(_runtime.construction_service.get_jobs().size() == 1, "normal build UI pays for lift construction")
|
||||
_clock.advance_minutes(300)
|
||||
_main.call("_clear_build_selection")
|
||||
var report: Dictionary = _runtime.elevators.get_report()
|
||||
_expect(report[&"shafts"].size() == 1, "completed paid lift registers one real car")
|
||||
if report[&"shafts"].size() != 1:
|
||||
_finish()
|
||||
return
|
||||
_shaft_id = report[&"shafts"][0][&"shaft_id"]
|
||||
_module_id = report[&"shafts"][0][&"module_ids"][0]
|
||||
_main.call("_on_facility_selected", _module_id)
|
||||
_expect(_main.get("selected_elevator_controls").visible and _main.get("management_panel").visible, "selecting finished lift opens existing inspector controls")
|
||||
_expect(_main.get("selected_heading_label").text == "客用昇降機", "proxy lift gets localized title instead of syncing placeholder")
|
||||
var detail: String = _main.get("selected_detail_label").text
|
||||
_expect("現在階" in detail and "目的階" in detail and "乗客" in detail and "待機" in detail and "1F 〜 2F" in detail, "inspector displays real cabin floors, occupancy, queue and stops")
|
||||
var button: Button = _main.get("elevator_operation_button")
|
||||
_expect(not button.disabled and button.text == "昇降機を停止", "idle powered car exposes stop command")
|
||||
button.pressed.emit()
|
||||
_expect(not _shaft()[&"operator_open"] and button.text == "運行を再開", "paused stop applies real operator state and swaps button")
|
||||
_expect("手動停止" in _main.get("selected_detail_label").text, "stopped inspector state is explicit")
|
||||
_expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 1, 0)).is_empty(), "UI stop removes usable ride route while shaft blocks climbing")
|
||||
button.pressed.emit()
|
||||
_expect(_shaft()[&"operator_open"] and bool(_shaft()[&"operational"]) == bool(_shaft()[&"powered"]), "UI resume respects actual cabin power availability")
|
||||
_expect(bool(_shaft()[&"powered"]) or "魔力供給なし" in _main.get("selected_detail_label").text, "unpowered cabin cannot appear as running after operator resume")
|
||||
_clock.set_paused(false)
|
||||
button.pressed.emit()
|
||||
_expect(button.disabled and _runtime.command_queue.pending_count() == 1, "running-clock command locks further submission")
|
||||
_main.call("_on_elevator_operation_pressed")
|
||||
_expect(_runtime.command_queue.pending_count() == 1, "repeated stop press cannot enqueue duplicate command")
|
||||
_main.call("_save_manual")
|
||||
_clock.advance_minutes(1)
|
||||
_expect(not _shaft()[&"operator_open"], "queued command applies at next simulation minute")
|
||||
_main.call("_load_manual")
|
||||
_main.call("_on_facility_selected", _module_id)
|
||||
_expect(_runtime.command_queue.pending_count() == 1 and button.disabled, "manual load restores queued lift command and disables duplicate submission")
|
||||
_clock.advance_minutes(1)
|
||||
_clock.set_paused(true)
|
||||
_expect(not _shaft()[&"operator_open"] and not button.disabled, "restored command applies exactly once and unlocks control")
|
||||
button.pressed.emit()
|
||||
_expect(_shaft()[&"operator_open"], "post-load UI uses fresh idempotency sequence and can resume")
|
||||
_main.call("_on_map_cell_clicked", Vector3i(10, 0, 0))
|
||||
_expect(not _main.get("selected_elevator_controls").visible, "selecting empty ground removes obsolete operation control")
|
||||
_main.call("_on_facility_selected", _module_id)
|
||||
await process_frame
|
||||
await process_frame
|
||||
var panel_rect: Rect2 = _main.get("management_panel").get_global_rect()
|
||||
_expect(panel_rect.encloses(button.get_global_rect()) and _main.get_global_rect().encloses(panel_rect), "operation button remains visible inside inspector at a 1280 by 720 window")
|
||||
if OS.get_environment("THE_TREE_ELEVATOR_UI_CAPTURE") == "1":
|
||||
await RenderingServer.frame_post_draw
|
||||
var image_value := root.get_texture().get_image()
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://artifacts/transport"))
|
||||
image_value.save_png("res://artifacts/transport/elevator-inspector.png")
|
||||
_finish()
|
||||
|
||||
func _shaft() -> Dictionary:
|
||||
for row: Dictionary in _runtime.elevators.get_report()[&"shafts"]:
|
||||
if row[&"shaft_id"] == _shaft_id:
|
||||
return row
|
||||
return {}
|
||||
|
||||
func _finish() -> void:
|
||||
_clock.set_paused(true)
|
||||
print("Elevator management UI: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(_failures)
|
||||
|
||||
func _expect(ok: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not ok:
|
||||
_failures += 1
|
||||
push_error(label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cl4r811l5vs7i
|
||||
@@ -69,6 +69,10 @@ func _run() -> void:
|
||||
_panel.apply_button.pressed.emit()
|
||||
_expect(_assignment(&"staff_akane").get(&"facility_id") == _receptions[0],
|
||||
"paused Apply assigns the receptionist to the chosen completed facility")
|
||||
_expect(_runtime.staff_service.get_staff(&"staff_akane").state == StaffRuntime.State.COMMUTING
|
||||
and _runtime.facility_service.get_instance(_receptions[0]).active_staff_ids.is_empty(),
|
||||
"paused Apply visibly starts a commute and does not invent facility attendance")
|
||||
_expect("出勤中" in _panel.staff_detail_label.text, "staff panel distinguishes physical commuting from active duty")
|
||||
_expect("変更しました" in _panel.result_label.text and "毎日" in _panel.staff_detail_label.text,
|
||||
"assignment success and recurring schedule are visible in Japanese")
|
||||
_test_rejected_assignment()
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
extends SceneTree
|
||||
|
||||
const PathType = preload("res://scripts/world/path_service.gd")
|
||||
const ProjectorType = preload("res://scripts/world/facility_infrastructure_projector.gd")
|
||||
const AdapterType = preload("res://scripts/world/route_subslot_path_adapter.gd")
|
||||
const SpatialType = preload("res://scripts/world/spatial_subslot_service.gd")
|
||||
const GUEST = preload("res://data/facilities/guest_elevator.tres")
|
||||
const SERVICE = preload("res://data/facilities/service_elevator.tres")
|
||||
|
||||
class ControlledPath:
|
||||
extends PathService
|
||||
var fail_set := false
|
||||
var fail_remove := false
|
||||
func set_vertical_walk_blockers(owner_id: StringName, cells: Array[Vector3i]) -> bool:
|
||||
var result := super.set_vertical_walk_blockers(owner_id, cells)
|
||||
if fail_set:
|
||||
fail_set = false
|
||||
return false
|
||||
return result
|
||||
func remove_vertical_walk_blockers(owner_id: StringName) -> bool:
|
||||
var result := super.remove_vertical_walk_blockers(owner_id)
|
||||
if fail_remove:
|
||||
fail_remove = false
|
||||
return false
|
||||
return result
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
_test_static_shaft_and_cabin_edges()
|
||||
_test_owner_boundaries_and_cache()
|
||||
_test_snapshot_validation_and_legacy()
|
||||
_test_projection_and_rollback()
|
||||
print("Elevator topology: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(_failures)
|
||||
|
||||
func _test_static_shaft_and_cabin_edges() -> void:
|
||||
var path := PathType.new()
|
||||
var cells: Array[Vector3i] = [Vector3i(-1, 0, 0), Vector3i(0, -1, 0), Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0), Vector3i(1, 0, 0), Vector3i(1, 1, 0), Vector3i(2, 1, 0)]
|
||||
_expect(path.rebuild_graph(cells), "materialize ordinary graph")
|
||||
_expect(not path.find_path(Vector3i(-1, 0, 0), Vector3i(2, 1, 0)).is_empty(), "legacy plain corridor graph still walks vertically")
|
||||
_expect(path.set_vertical_walk_blockers(&"shaft.a", [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 0, 0), Vector3i(1, 1, 0)]), "mark entire two-column shaft")
|
||||
_expect(path.find_path(Vector3i(-1, 0, 0), Vector3i(2, 1, 0)).is_empty(), "neither shaft column admits ordinary climbing")
|
||||
_expect(path.last_no_route_reason == path.ROUTE_GOAL_UNREACHABLE, "diagnostic graph cannot invent walk bypass")
|
||||
_expect(path.find_path(Vector3i(0, -1, 0), Vector3i(0, 0, 0)).is_empty(), "edge touching bottom of shaft is suppressed")
|
||||
_expect(path.find_path(Vector3i(0, 1, 0), Vector3i(0, 2, 0)).is_empty(), "edge touching top of shaft is suppressed")
|
||||
_expect(path.find_path(Vector3i(-1, 0, 0), Vector3i(1, 0, 0)).size() == 3, "horizontal floor access through shaft remains")
|
||||
_expect(path.configure_elevator(&"transport.a", Vector3i(0, 0, 0), Vector3i(0, 1, 0)), "separate cabin connector enables rides")
|
||||
_expect(not path.find_path(Vector3i(-1, 0, 0), Vector3i(2, 1, 0)).is_empty(), "cabin restores reachable vertical route")
|
||||
var edge := path.get_edge_report(Vector3i(0, 0, 0), Vector3i(0, 1, 0))
|
||||
_expect(edge.get(&"movement_type") == path.MOVEMENT_ELEVATOR and edge.get(&"connector_id") == &"transport.a", "movement driver sees elevator edge")
|
||||
_expect(path.get_step_connector(Vector3i(0, 1, 0), Vector3i(0, 0, 0)) == &"transport.a", "reverse ride resolves same bidirectional connector")
|
||||
_expect(path.get_step_connector(Vector3i(-1, 0, 0), Vector3i(0, 0, 0)).is_empty(), "floor step has no cabin owner")
|
||||
edge[&"connector_id"] = &"forged"
|
||||
_expect(path.get_step_connector(Vector3i(0, 0, 0), Vector3i(0, 1, 0)) == &"transport.a", "edge report is defensive")
|
||||
_expect(path.set_connector_operational_state(&"transport.a", path.CONNECTOR_STOPPED), "stop car without removing shaft")
|
||||
_expect(path.find_path(Vector3i(-1, 0, 0), Vector3i(2, 1, 0)).is_empty(), "stopped car cannot be bypassed")
|
||||
_expect(path.last_no_route_reason == path.ROUTE_CONNECTOR_UNAVAILABLE, "diagnostic recognizes unavailable car")
|
||||
_expect(path.get_edge_report(Vector3i(0, 0, 0), Vector3i(0, 1, 0)).is_empty(), "stopped car has no usable step")
|
||||
_expect(path.remove_connector(&"transport.a"), "remove transport connector independently")
|
||||
_expect(path.find_path(Vector3i(0, 0, 0), Vector3i(0, 1, 0)).is_empty(), "removing cabin retains shaft walk suppression")
|
||||
_expect(path.configure_elevator(&"transport.staff", Vector3i(0, 0, 0), Vector3i(0, 1, 0), {}, &"staff"), "configure service-only cabin")
|
||||
_expect(path.find_path(Vector3i(0, 0, 0), Vector3i(0, 1, 0), [0], []).is_empty(), "guest cannot use service cabin")
|
||||
_expect(path.get_step_connector(Vector3i(0, 0, 0), Vector3i(0, 1, 0), [&"staff"]) == &"transport.staff", "staff access identifies service cabin")
|
||||
|
||||
func _test_owner_boundaries_and_cache() -> void:
|
||||
var path := PathType.new()
|
||||
var bottom := Vector3i(0, 0, 0)
|
||||
var top := Vector3i(0, 1, 0)
|
||||
path.rebuild_graph([bottom, top])
|
||||
path.find_path(bottom, top)
|
||||
var revision := path.graph_revision
|
||||
_expect(path.cached_path_count() > 0, "seed walk path cache")
|
||||
_expect(path.set_vertical_walk_blockers(&"owner.a", [bottom]), "first owner blocks one endpoint")
|
||||
_expect(path.graph_revision > revision and path.cached_path_count() == 0, "blocker edit invalidates cached routes")
|
||||
var snapshot := path.create_snapshot()
|
||||
_expect(path.set_vertical_walk_blockers(&"owner.a", [bottom]) and path.create_snapshot() == snapshot, "same owner configuration is idempotent")
|
||||
_expect(path.set_vertical_walk_blockers(&"owner.b", [bottom]), "independent owner can overlap")
|
||||
_expect(path.remove_vertical_walk_blockers(&"owner.a") and path.find_path(bottom, top).is_empty(), "removing one owner retains other's restriction")
|
||||
var defensive := path.get_vertical_walk_blockers()
|
||||
defensive[&"owner.b"].clear()
|
||||
_expect(path.find_path(bottom, top).is_empty(), "blocker read model cannot mutate graph")
|
||||
_expect(path.remove_vertical_walk_blockers(&"owner.b") and path.find_path(bottom, top) == [bottom, top], "last owner removal restores ordinary topology")
|
||||
snapshot = path.create_snapshot()
|
||||
_expect(not path.set_vertical_walk_blockers(&"", [bottom]), "reject empty owner")
|
||||
_expect(not path.set_vertical_walk_blockers(&" bad", [bottom]), "reject noncanonical owner")
|
||||
_expect(not path.set_vertical_walk_blockers(&"owner", []), "empty replacement must use explicit removal")
|
||||
_expect(not path.set_vertical_walk_blockers(&"owner", [bottom, bottom]), "reject duplicate owned cell")
|
||||
_expect(not path.set_vertical_walk_blockers(&"owner", [Vector3i(4, 4, 0)]), "reject unknown graph cell")
|
||||
_expect(not path.remove_vertical_walk_blockers(&"unknown"), "reject removing unknown owner")
|
||||
_expect(path.create_snapshot() == snapshot, "invalid blocker mutations leave full state unchanged")
|
||||
|
||||
func _test_snapshot_validation_and_legacy() -> void:
|
||||
var path := PathType.new()
|
||||
var cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 0, 0)]
|
||||
path.rebuild_graph(cells)
|
||||
path.set_vertical_walk_blockers(&"shaft.a", [cells[0], cells[1]])
|
||||
path.set_vertical_walk_blockers(&"shaft.b", [cells[2]])
|
||||
var snapshot := path.create_snapshot()
|
||||
_expect(snapshot[&"schema_version"] == 4, "Path v4 declares shaft ownership")
|
||||
var restored := PathType.new()
|
||||
_expect(restored.restore_snapshot(snapshot) and restored.rebuild_graph(cells), "restore shaft topology before authoritative graph materialization")
|
||||
_expect(restored.create_snapshot() == snapshot and restored.find_path(cells[0], cells[1]).is_empty(), "round trip restores exact restrictions and revision")
|
||||
var malformed_cases: Array[Dictionary] = []
|
||||
var missing := snapshot.duplicate(true)
|
||||
missing.erase(&"vertical_walk_blockers")
|
||||
malformed_cases.append(missing)
|
||||
var owner_duplicate := snapshot.duplicate(true)
|
||||
owner_duplicate[&"vertical_walk_blockers"].append(owner_duplicate[&"vertical_walk_blockers"][0].duplicate(true))
|
||||
malformed_cases.append(owner_duplicate)
|
||||
var wrong_order := snapshot.duplicate(true)
|
||||
wrong_order[&"vertical_walk_blockers"].reverse()
|
||||
malformed_cases.append(wrong_order)
|
||||
var wrong_cells := snapshot.duplicate(true)
|
||||
wrong_cells[&"vertical_walk_blockers"][0][&"cells"].reverse()
|
||||
malformed_cases.append(wrong_cells)
|
||||
var unknown_cell := snapshot.duplicate(true)
|
||||
unknown_cell[&"vertical_walk_blockers"][0][&"cells"] = [Vector3i(999, 999, 0)]
|
||||
malformed_cases.append(unknown_cell)
|
||||
var duplicate_cell := snapshot.duplicate(true)
|
||||
duplicate_cell[&"vertical_walk_blockers"][0][&"cells"] = [cells[0], cells[0]]
|
||||
malformed_cases.append(duplicate_cell)
|
||||
var unknown_field := snapshot.duplicate(true)
|
||||
unknown_field[&"vertical_walk_blockers"][0][&"extra"] = true
|
||||
malformed_cases.append(unknown_field)
|
||||
var empty_cells := snapshot.duplicate(true)
|
||||
empty_cells[&"vertical_walk_blockers"][0][&"cells"] = []
|
||||
malformed_cases.append(empty_cells)
|
||||
for malformed: Dictionary in malformed_cases:
|
||||
_expect(not restored.restore_snapshot(malformed), "reject malformed shaft ownership snapshot")
|
||||
_expect(restored.create_snapshot() == snapshot, "snapshot rejection is atomic")
|
||||
for version: int in [1, 2, 3]:
|
||||
var legacy := snapshot.duplicate(true)
|
||||
legacy[&"schema_version"] = version
|
||||
_expect(not restored.restore_snapshot(legacy), "old version with retained shaft field cannot disable safety")
|
||||
legacy.erase(&"vertical_walk_blockers")
|
||||
_expect(restored.restore_snapshot(legacy), "true legacy snapshot lacking shaft field migrates")
|
||||
_expect(restored.get_vertical_walk_blockers().is_empty() and not restored.find_path(cells[0], cells[1]).is_empty(), "legacy plain topology remains compatible")
|
||||
_expect(restored.restore_snapshot(snapshot), "return to current shaft snapshot")
|
||||
|
||||
func _test_projection_and_rollback() -> void:
|
||||
var context := _context()
|
||||
var guest := _instance(GUEST, &"guest", Vector3i(0, 0, 0))
|
||||
var service := _instance(SERVICE, &"service", Vector3i(4, 0, 1))
|
||||
_expect(context.projector.project_facility(&"project.guest", GUEST, guest).is_success(), "project guest elevator ROUTE footprint")
|
||||
_expect(context.projector.project_facility(&"project.service", SERVICE, service).is_success(), "project service elevator ROUTE footprint")
|
||||
_expect(context.source.get_route_cells().size() == 10, "all occupied 2x2 and 3x2 cells are floor-accessible")
|
||||
_expect(context.projector.get_projection(&"guest")[&"connector_id"] == &"", "projector owns static shaft separately from transport connector")
|
||||
_expect(context.projector.validate_invariants()[&"ok"], "shaft owner and record crosslinks match")
|
||||
var saved := _snapshots(context)
|
||||
_expect(context.projector.restore_snapshot(saved.projector) and context.projector.create_snapshot() == saved.projector, "current elevator projection snapshot roundtrip preserves history and static ownership")
|
||||
context.path.remove_vertical_walk_blockers(&"infra.shaft:guest")
|
||||
_expect(context.projector.is_projection_stale(), "missing shaft owner makes infrastructure stale")
|
||||
_expect(not context.projector.restore_snapshot(saved.projector), "metadata restore cannot accept missing static shaft restriction")
|
||||
_expect(context.path.restore_snapshot(saved.path), "repair exact shaft state")
|
||||
_expect(context.projector.validate_invariants()[&"ok"], "restored shaft repairs infrastructure crosslink")
|
||||
var orphan_cells: Array[Vector3i] = [Vector3i(0, 0, 0)]
|
||||
context.path.set_vertical_walk_blockers(&"infra.shaft:orphan", orphan_cells)
|
||||
_expect(context.projector.is_projection_stale(), "untracked managed shaft owner rejected")
|
||||
context.path.remove_vertical_walk_blockers(&"infra.shaft:orphan")
|
||||
_expect(context.projector.unproject_facility(&"remove.guest", &"guest").is_success(), "unproject guest shaft with its route")
|
||||
_expect(not context.path.get_vertical_walk_blockers().has(&"infra.shaft:guest") and not context.path.has_cell(Vector3i(0, 0, 0)), "demolition removes static owner and route cells")
|
||||
_expect(context.path.get_vertical_walk_blockers().has(&"infra.shaft:service") and context.projector.validate_invariants()[&"ok"], "independent service shaft remains intact")
|
||||
var controlled := ControlledPath.new()
|
||||
var failure_context := _context(controlled)
|
||||
var before := _snapshots(failure_context)
|
||||
controlled.fail_set = true
|
||||
_expect(not failure_context.projector.project_facility(&"fail.project", GUEST, guest).is_success(), "injected shaft mutation failure rejects project")
|
||||
_expect(_snapshots(failure_context) == before, "failed shaft project restores source, route adapter, Path, and metadata exactly")
|
||||
_expect(failure_context.projector.project_facility(&"fail.project", GUEST, guest).is_success(), "failed transaction leaves request id available")
|
||||
before = _snapshots(failure_context)
|
||||
controlled.fail_remove = true
|
||||
_expect(not failure_context.projector.unproject_facility(&"fail.remove", &"guest").is_success(), "injected shaft removal failure rejects demolition")
|
||||
_expect(_snapshots(failure_context) == before, "failed shaft removal restores every dependency exactly")
|
||||
_expect(failure_context.projector.unproject_facility(&"fail.remove", &"guest").is_success(), "failed demolition remains retryable")
|
||||
|
||||
func _context(path_value: PathService = null) -> Dictionary:
|
||||
var source := SpatialType.new()
|
||||
var path := path_value if path_value != null else PathType.new()
|
||||
var adapter := AdapterType.new()
|
||||
_expect(adapter.configure(source, path, []), "configure route adapter")
|
||||
var projector := ProjectorType.new()
|
||||
_expect(projector.configure(adapter, source, path), "configure infrastructure projector")
|
||||
return {&"source": source, &"path": path, &"adapter": adapter, &"projector": projector}
|
||||
|
||||
func _instance(definition: FacilityDefinition, instance_id: StringName, origin: Vector3i) -> Dictionary:
|
||||
var cells: Array[Vector3i] = []
|
||||
for y: int in range(definition.footprint.y):
|
||||
for x: int in range(definition.footprint.x):
|
||||
cells.append(origin + Vector3i(x, y, 0))
|
||||
return {&"instance_id": instance_id, &"definition_id": definition.id, &"origin": origin, &"rotation": 0, &"layer": definition.layer, &"occupied_cells": cells, &"construction_cost": definition.construction_cost.duplicate(true), &"source_job_id": StringName("build." + String(instance_id)), &"state": &"ACTIVE", &"completed_at": 300}
|
||||
|
||||
func _snapshots(context: Dictionary) -> Dictionary:
|
||||
return {&"source": context.source.create_snapshot(), &"path": context.path.create_snapshot(), &"adapter": context.adapter.create_snapshot(), &"projector": context.projector.create_snapshot()}
|
||||
|
||||
func _expect(ok: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not ok:
|
||||
_failures += 1
|
||||
push_error(label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b54qfpjj4idli
|
||||
Reference in New Issue
Block a user