Add explicit stairs and measure elevator congestion with real actors

This commit is contained in:
Takeshi
2026-09-09 14:26:38 +09:00
parent e246ce382b
commit fb4ad23190
57 changed files with 3030 additions and 162 deletions
@@ -381,8 +381,11 @@ func _test_v3_migration_and_v4_tamper_guards() -> void:
v3_runtime[&"runtime_schema_version"] = 3
v3_runtime.erase(&"kitchen_production")
v3_runtime.erase(&"elevators")
v3_runtime.erase(&"legacy_stairs")
v3_runtime[&"path"][&"schema_version"] = 3
v3_runtime[&"path"].erase(&"vertical_walk_blockers")
v3_runtime[&"path"].erase(&"vertical_walk_requires_connector")
v3_runtime[&"path"].erase(&"transit_actor_ids")
v3_runtime.erase(&"operations")
v3_runtime.erase(&"catalog_manifest")
v3_runtime.erase(&"progression")
@@ -399,10 +402,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"]) == 8, "v3 migration emits canonical Runtime schema v8")
_expect(int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 9, "v3 migration emits canonical Runtime schema v9")
var digest := SimulationDigest.compute(migrated)
_expect(_runtime.restore_snapshot(migrated), "canonical v8 snapshot restores")
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "canonical v8 restore is exact")
_expect(_runtime.restore_snapshot(migrated), "canonical v9 snapshot restores")
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "canonical v9 restore is exact")
var progressed_legacy: Dictionary = v3.duplicate(true)
progressed_legacy[&"game_state"][&"world_tree_rank"] = 2
+22 -22
View File
@@ -1,7 +1,7 @@
extends SceneTree
const FOURTEEN_DAYS_MINUTES := 14 * SimulationKernel.MINUTES_PER_DAY
const RUNTIME_SNAPSHOT_VERSION := 8
const RUNTIME_SNAPSHOT_VERSION := 9
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_v8_tamper()
_test_full_partial_payroll_bankruptcy_and_v9_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 v8"
"fixture emits Runtime snapshot schema v9"
)
if enable_operations:
var enabled: Dictionary = runtime.enable_production_operations()
@@ -217,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 v8 snapshot round trip"
"command history survives an exact v9 snapshot round trip"
)
var restored_retry: CommandResult = runtime.submit_command(
&"change_resources",
@@ -257,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 v8 snapshot round trip is exact"
"24-hour actual-tick v9 snapshot round trip is exact"
)
_free_fixture(fixture)
func _test_full_partial_payroll_bankruptcy_and_v8_tamper() -> void:
func _test_full_partial_payroll_bankruptcy_and_v9_tamper() -> void:
var fixture := _new_fixture(73_002, false)
var runtime: Variant = fixture[&"runtime"]
var clock: Variant = fixture[&"clock"]
@@ -349,7 +349,7 @@ func _test_full_partial_payroll_bankruptcy_and_v8_tamper() -> void:
var terminal_digest := SimulationDigest.compute(terminal_snapshot)
_expect(
runtime.restore_snapshot(terminal_snapshot),
"BANKRUPT terminal v8 snapshot restores without clearing terminal state"
"BANKRUPT terminal v9 snapshot restores without clearing terminal state"
)
clock.advance_minutes(180)
_expect(
@@ -362,32 +362,32 @@ func _test_full_partial_payroll_bankruptcy_and_v8_tamper() -> void:
"BANKRUPT cannot be acknowledged away"
)
_assert_v8_tamper_rejected(
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
tampered[&"systems"][&"runtime"][&"operations"][&"personal_action_points"] = 99,
"v8 rejects an out-of-range Operations action-point total"
"v9 rejects an out-of-range Operations action-point total"
)
_assert_v8_tamper_rejected(
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
tampered[&"systems"][&"runtime"][&"next_command_id"] = int(
tampered[&"systems"][&"runtime"][&"next_command_id"]
) + 1,
"v8 rejects command high-water gaps"
"v9 rejects command high-water gaps"
)
_assert_v8_tamper_rejected(
_assert_v9_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",
"v8 rejects accepted-command/idempotency cross-link tampering"
"v9 rejects accepted-command/idempotency cross-link tampering"
)
_assert_v8_tamper_rejected(
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
@@ -395,26 +395,26 @@ func _test_full_partial_payroll_bankruptcy_and_v8_tamper() -> void:
summaries[0][&"category_totals"][&"wages"] = int(
summaries[0][&"category_totals"][&"wages"]
) + 1,
"v8 rejects DaySummary/payroll wage reconciliation tampering"
"v9 rejects DaySummary/payroll wage reconciliation tampering"
)
_assert_v8_tamper_rejected(
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
tampered[&"systems"][&"runtime"][&"unexpected_v8_field"] = true,
"v8 rejects unknown Runtime fields"
tampered[&"systems"][&"runtime"][&"unexpected_v9_field"] = true,
"v9 rejects unknown Runtime fields"
)
_assert_v8_tamper_rejected(
_assert_v9_tamper_rejected(
runtime,
terminal_snapshot,
func(tampered: Dictionary) -> void:
tampered[&"systems"][&"runtime"][&"operations"][&"coordinator"][&"unexpected_field"] = true,
"v8 rejects unknown Coordinator fields"
"v9 rejects unknown Coordinator fields"
)
_free_fixture(fixture)
func _assert_v8_tamper_rejected(
func _assert_v9_tamper_rejected(
runtime: Variant,
canonical: Dictionary,
mutator: Callable,
@@ -463,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 v8 midpoint snapshot")
_expect(restored, "fresh Runtime restores the enabled v9 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"]) == 8, "Runtime snapshot advances to schema v8")
_expect(int(disabled_runtime[&"runtime_schema_version"]) == 9, "Runtime snapshot advances to schema v9")
_expect(
disabled_runtime[&"operations"] == {
&"wrapper_version": 1,
@@ -362,8 +362,14 @@ func _test_daily_payroll_financials_and_crosslink_tamper() -> void:
downgraded[&"systems"][&"runtime"][&"runtime_schema_version"] = 4
downgraded[&"systems"][&"runtime"].erase(&"kitchen_production")
downgraded[&"systems"][&"runtime"].erase(&"elevators")
downgraded[&"systems"][&"runtime"].erase(&"legacy_stairs")
for infrastructure_key: StringName in [&"excavation", &"spatial_subslots", &"route_projection", &"facility_infrastructure"]:
downgraded[&"systems"][&"runtime"].erase(infrastructure_key)
downgraded[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
downgraded[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
downgraded[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_requires_connector")
downgraded[&"systems"][&"runtime"][&"path"].erase(&"transit_actor_ids")
_expect(_runtime._legacy_infrastructure_history_is_absent(downgraded[&"systems"][&"runtime"]), "Operations downgrade fixture excludes unrelated later infrastructure owners")
_expect(
_rejects_atomically(downgraded),
"relabelled enabled v5 cannot discard Operations history through v4 migration"
+1
View File
@@ -66,6 +66,7 @@ func _test_tick_command_execution() -> void:
func _test_starter_world_layers() -> void:
_reset_runtime()
_expect(_runtime.definition_registry.facility_count() == 18, "Runtime loads baseline sixteen plus two required stair definitions")
var front_start := Vector3i(-2, 0, WorldService.LAYER_FRONT)
var back_goal := Vector3i(2, 0, WorldService.LAYER_BACK)
var guest_path: Array[Vector3i] = _runtime.path_service.find_path(
+5 -3
View File
@@ -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"]) == 4, "shaft-safe traffic snapshot uses schema version 4")
_expect(int(snapshot[&"schema_version"]) == 5, "traffic snapshot uses schema version 5 with floor policy and transit ownership")
_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), "v4 snapshot restores before authoritative world graph")
_expect(restored.restore_snapshot(snapshot), "v5 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, "v4 snapshot roundtrip is exact")
_expect(restored.create_snapshot() == snapshot, "v5 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")
@@ -408,6 +408,8 @@ func _test_snapshot_roundtrip_and_atomic_rejection() -> void:
v2_snapshot.erase(&"known_elevator_request_ids")
v2_snapshot.erase(&"active_elevator_units")
v2_snapshot.erase(&"vertical_walk_blockers")
v2_snapshot.erase(&"vertical_walk_requires_connector")
v2_snapshot.erase(&"transit_actor_ids")
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 := 8
const RUNTIME_SNAPSHOT_VERSION := 9
const INFRASTRUCTURE_SNAPSHOT_KEYS: Array[StringName] = [
&"excavation",
&"spatial_subslots",
@@ -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 v8 with infrastructure state")
_expect(current_version, "Runtime emits schema v9 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 v8 infrastructure surface is not connected yet; behavioral cases skipped.")
print(" PENDING: Runtime v9 infrastructure surface is not connected yet; behavioral cases skipped.")
return ready
@@ -347,12 +347,17 @@ func _test_completed_proxy_projection() -> void:
_expect(not _complete_excavation(fixture, front_a, &"infra.proxy.dig.a").is_empty(), "first proxy cell is excavated")
_expect(not _complete_excavation(fixture, front_b, &"infra.proxy.dig.b").is_empty(), "adjacent proxy cell is excavated")
runtime.world_service.set_support(front_a, 10, true)
runtime.world_service.set_support(front_b, 10, true)
runtime.world_service.set_support(Vector3i(0, -1, PathType.BACK), 10, true)
runtime.world_service.set_support(Vector3i(1, -1, PathType.BACK), 10, true)
var guest := _complete_proxy(
fixture, &"guest_corridor", front_a, &"infra.proxy.guest"
)
var adjacent_guest := _complete_proxy(
fixture, &"guest_corridor", front_b, &"infra.proxy.guest.adjacent"
)
_expect(adjacent_guest[&"ok"], "same-floor guest corridor completes beside the proxy")
var service := _complete_proxy(
fixture,
&"service_corridor",
@@ -397,8 +402,12 @@ func _test_completed_proxy_projection() -> void:
var door_back := Vector3i(0, -1, PathType.BACK)
var service_back := Vector3i(1, -1, PathType.BACK)
_expect(
runtime.path_service.find_path(entrance, front_a) == [entrance, front_a],
"guest corridor becomes a real FRONT Path edge"
runtime.path_service.find_path(front_b, front_a) == [front_b, front_a],
"guest corridors form a real same-floor FRONT Path edge"
)
_expect(
runtime.path_service.find_path(entrance, front_a).is_empty(),
"corridor projection does not create an implicit staircase from ground level"
)
_expect(
runtime.path_service.find_path(
@@ -728,10 +737,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 v8 snapshot restores")
_expect(runtime.restore_snapshot(snapshot), "valid Runtime v9 snapshot restores")
_expect(
runtime.create_snapshot() == snapshot and runtime.latest_digest == expected_digest,
"Runtime v8 round trip is exact"
"Runtime v9 round trip is exact"
)
_assert_runtime_restore_rejected(
@@ -739,7 +748,7 @@ func _test_current_snapshot_strictness() -> void:
snapshot,
func(candidate: Dictionary) -> void:
candidate[&"systems"][&"runtime"][&"unknown_infrastructure"] = {},
"unknown v8 Runtime field"
"unknown v9 Runtime field"
)
for key: StringName in INFRASTRUCTURE_SNAPSHOT_KEYS:
_assert_runtime_restore_rejected(
@@ -817,12 +826,15 @@ func _test_v5_proxy_migration_determinism() -> void:
legacy_runtime[&"runtime_schema_version"] = 5
legacy_runtime.erase(&"kitchen_production")
legacy_runtime.erase(&"elevators")
legacy_runtime.erase(&"legacy_stairs")
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")
legacy_runtime[&"path"].erase(&"vertical_walk_requires_connector")
legacy_runtime[&"path"].erase(&"transit_actor_ids")
_free_fixture(baseline)
_expect(
stripped_v6_events > 0 and _legacy_v5_infrastructure_shape_is_clean(legacy_runtime),
@@ -837,8 +849,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"]) == 8,
"v5 migration is immediately re-emitted as v8"
int(first_migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 9,
"v5 migration is immediately re-emitted as v9"
)
_expect(
first_runtime.facility_infrastructure_projector.has_projection(instance_id)
@@ -876,6 +888,9 @@ func _test_rank_expansion_preserves_route_union() -> void:
if not built[&"ok"] or not runtime.path_service.has_cell(route_cell):
_free_fixture(fixture)
return
var adjacent_cell := Vector3i(1, 1, PathType.FRONT)
var adjacent := _complete_proxy(fixture, &"guest_corridor", adjacent_cell, &"infra.expansion.adjacent")
_expect(adjacent[&"ok"], "rank expansion fixture has a same-floor corridor connection")
_expect(_promote_rank_two(fixture, &"infra.expansion.rank"), "rank expansion commits with an existing route")
_expect(
runtime.route_subslot_path_adapter.get_projected_route_cells().has(route_cell)
@@ -890,10 +905,12 @@ func _test_rank_expansion_preserves_route_union() -> void:
"rank expansion replaces the adapter base with all expanded World walkability"
)
_expect(
runtime.path_service.find_path(
Vector3i(0, 0, PathType.FRONT), route_cell
) == [Vector3i(0, 0, PathType.FRONT), route_cell],
"expanded base plus pre-existing route remains reachable"
runtime.path_service.find_path(adjacent_cell, route_cell) == [adjacent_cell, route_cell],
"expanded base preserves the pre-existing same-floor route"
)
_expect(
runtime.path_service.find_path(Vector3i(0, 0, PathType.FRONT), route_cell).is_empty(),
"rank expansion does not add implicit vertical walking"
)
_free_fixture(fixture)
@@ -1039,7 +1056,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") or runtime_data.has(&"elevators") or runtime_data.has(&"staff_commute"):
if runtime_data.has(&"kitchen_production") or runtime_data.has(&"elevators") or runtime_data.has(&"legacy_stairs") 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)):
+26 -9
View File
@@ -3,6 +3,7 @@ extends SceneTree
const POST_V2_RUNTIME_KEYS := [
&"kitchen_production",
&"elevators",
&"legacy_stairs",
&"movement_credits",
&"catalog_manifest",
&"progression",
@@ -159,11 +160,22 @@ func _test_congestion_credit_and_alternate_route() -> void:
_reset_runtime(8_103)
for x in range(0, 3):
_runtime.world_service.set_walkable(Vector3i(x, 1, WorldService.LAYER_FRONT), true)
_expect(_runtime.rebuild_path_graph_from_world(), "parallel walkable lane rebuilds into PathService")
var definition_id: StringName = &"guest_corridor" if x == 1 else &"guest_stairs"
var built: CommandResult = _runtime.submit_command(
&"build_facility", {&"definition_id": definition_id, &"origin": Vector3i(x, 1, WorldService.LAYER_FRONT)},
&"manager", &"", 0, -1, StringName("path.parallel.build.%d" % x)
)
_expect(built.is_success(), "parallel lane uses normal %s construction at x=%d" % [definition_id, x])
if not built.is_success():
return
_clock.advance_minutes(10)
_expect(_runtime.construction_service.get_job(built.details[&"job_id"]).state == ConstructionJob.State.COMPLETED, "parallel lane construction completes")
_expect(_runtime.path_service.get_vertical_walk_requires_connector(), "parallel lane retains strict floor policy")
var congested := Vector3i(1, 0, WorldService.LAYER_FRONT)
_runtime.path_service.configure_cell_traffic(congested, 1, 0, false)
for blocker_index in range(4):
# Two 150-cost stairs make the upper route 500; six occupants make the
# direct two-cell lane 575, so the stair detour remains strictly cheaper.
for blocker_index in range(6):
_expect(
_register_idle_staff(StringName("blocker_%d" % blocker_index), congested),
"alternate-route blocker %d registers" % blocker_index
@@ -181,6 +193,9 @@ func _test_congestion_credit_and_alternate_route() -> void:
)
_clock.advance_one_minute()
var picker: GuestRuntime = _runtime.guest_service.get_guest(&"route_picker")
_expect(picker.current_cell == Vector3i(0, 0, 0) and _runtime.get_actor_movement_credit(&"guest:route_picker")[&"credit"] == 1000, "first stair minute preserves incomplete movement credit")
_clock.advance_one_minute()
picker = _runtime.guest_service.get_guest(&"route_picker")
_expect(
picker != null and picker.current_cell == Vector3i(0, 1, 0),
"live congestion makes Runtime choose the cheaper parallel lane (actual %s)" % (
@@ -303,11 +318,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"]) == 8, "Runtime snapshot advertises schema v8")
_expect(int(runtime_data[&"runtime_schema_version"]) == 9, "Runtime snapshot advertises schema v9")
_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 v8 snapshot restores")
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "mid-move v8 save/load is exact")
_expect(_runtime.restore_snapshot(snapshot), "valid mid-move v9 snapshot restores")
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == digest, "mid-move v9 save/load is exact")
var corruptions: Array[Dictionary] = []
var bad_actor := snapshot.duplicate(true)
@@ -345,6 +360,8 @@ func _test_current_snapshot_crosslinks_and_legacy_v2() -> void:
_strip_post_v2_runtime_state(legacy_data)
legacy_data[&"path"][&"schema_version"] = 3
legacy_data[&"path"].erase(&"vertical_walk_blockers")
legacy_data[&"path"].erase(&"vertical_walk_requires_connector")
legacy_data[&"path"].erase(&"transit_actor_ids")
legacy_data[&"path"][&"actor_cells"] = []
legacy_data[&"path"][&"actor_progress"] = []
for traffic: Dictionary in legacy_data[&"path"][&"cell_traffic"]:
@@ -353,16 +370,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-v8 aggregate and dedicated history instead of relabelling v8"
"legacy fixture removes every v3-v9 aggregate and dedicated history instead of relabelling v9"
)
_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"]) == 8
int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 9
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 v8 ownership while preserving blocked traffic state"
"legacy migration emits canonical v9 ownership while preserving blocked traffic state"
)
+3
View File
@@ -795,8 +795,11 @@ func _test_snapshot_replay_corruption_and_legacy_migration() -> void:
legacy[&"systems"][&"runtime"].erase(&"runtime_schema_version")
legacy[&"systems"][&"runtime"].erase(&"kitchen_production")
legacy[&"systems"][&"runtime"].erase(&"elevators")
legacy[&"systems"][&"runtime"].erase(&"legacy_stairs")
legacy[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
legacy[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
legacy[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_requires_connector")
legacy[&"systems"][&"runtime"][&"path"].erase(&"transit_actor_ids")
legacy[&"systems"][&"runtime"].erase(&"operations")
legacy[&"systems"][&"runtime"].erase(&"defense")
legacy[&"systems"][&"runtime"][&"rng_streams"].erase("defense")
+2 -2
View File
@@ -42,9 +42,9 @@ func _run() -> void:
return
await _advance(180)
_command(&"build_facility", {&"definition_id": &"standard_room", &"origin": Vector3i(-8, 2, 0)})
_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(-4, 1, 0)})
_command(&"build_facility", {&"definition_id": &"guest_stairs", &"origin": Vector3i(-4, 1, 0)})
await _advance(60)
_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(-4, 2, 0)})
_command(&"build_facility", {&"definition_id": &"guest_stairs", &"origin": Vector3i(-4, 2, 0)})
await _advance(10)
for x: int in [-5, -6]:
_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(x, 2, 0)})
+18 -10
View File
@@ -9,7 +9,7 @@ func _initialize() -> void:
func _run() -> void:
print("P1: Runtime v8 supply save migration")
print("P1: Runtime v9 supply save migration")
var source := _fixture(7301)
var first := _fixture(7302)
var second := _fixture(7303)
@@ -19,31 +19,36 @@ 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"] == 8, "current Runtime emits schema v8")
_expect(canonical_data[&"runtime_schema_version"] == 9, "current Runtime emits schema v9")
_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.erase(&"legacy_stairs")
legacy_data[&"path"][&"schema_version"] = 3
legacy_data[&"path"].erase(&"vertical_walk_blockers")
legacy_data[&"path"].erase(&"vertical_walk_requires_connector")
legacy_data[&"path"].erase(&"transit_actor_ids")
_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 v8")
_expect(first_runtime.restore_snapshot(legacy), "genuine v6 state restores into v9")
var migrated: Dictionary = first_runtime.create_snapshot()
_expect(migrated[&"systems"][&"runtime"][&"runtime_schema_version"] == 8, "v6 migration re-emits current schema")
_expect(migrated[&"systems"][&"runtime"][&"runtime_schema_version"] == 9, "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")
var expected := canonical.duplicate(true)
expected[&"systems"][&"runtime"][&"path"][&"graph_revision"] += 1
_expect(migrated == expected, "v6 migration preserves all state except the one-time strict topology revision")
_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 v8 round trip is exact")
_expect(first_runtime.restore_snapshot(migrated) and first_runtime.create_snapshot() == migrated, "canonical v9 round trip is exact")
var missing_supply := canonical.duplicate(true)
missing_supply[&"systems"][&"runtime"].erase(&"kitchen_production")
_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 v8 with retained kitchen state cannot masquerade as v6")
_expect(_rejects_atomically(first_runtime, missing_supply), "v9 cannot omit its supply aggregate")
var relabelled := legacy.duplicate(true)
relabelled[&"systems"][&"runtime"][&"kitchen_production"] = canonical_data[&"kitchen_production"].duplicate(true)
_expect(_rejects_atomically(first_runtime, relabelled), "relabelled v9 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")
@@ -61,8 +66,11 @@ func _run() -> void:
orphaned[&"systems"][&"runtime"][&"runtime_schema_version"] = 6
orphaned[&"systems"][&"runtime"].erase(&"kitchen_production")
orphaned[&"systems"][&"runtime"].erase(&"elevators")
orphaned[&"systems"][&"runtime"].erase(&"legacy_stairs")
orphaned[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
orphaned[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
orphaned[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_requires_connector")
orphaned[&"systems"][&"runtime"][&"path"].erase(&"transit_actor_ids")
_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()
+165
View File
@@ -0,0 +1,165 @@
extends SceneTree
var _checks := 0
var _failures := 0
var _runtime: Variant
var _clock: Variant
var _state: Variant
var _serial := 0
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()
_reset()
_expect(_runtime.path_service.get_vertical_walk_requires_connector(), "new Runtime requires explicit vertical transport")
_expect(_runtime.definition_registry.facility_count() == 18, "Runtime registers baseline16 and two staircase definitions")
var before: Dictionary = _runtime.create_snapshot()
var invalid := _command(&"build_facility", {&"definition_id": &"guest_stairs", &"origin": Vector3i(4, 3, 0)})
_expect(not invalid.is_success() and invalid.details.get(&"reason") == &"STAIR_LANDING_MISSING", "stairs without a lower landing reject before any construction")
_expect(_runtime.construction_service.get_jobs().is_empty() and _state.get_resource(&"gold") == 5000, "missing landing spends no money and starts no job")
_build(&"guest_stairs", Vector3i(4, 1, 0))
_build(&"guest_stairs", Vector3i(4, 2, 0))
var route: Array = _runtime.path_service.find_path(Vector3i(4, 0, 0), Vector3i(4, 2, 0))
_expect(route.size() == 3 and _runtime.path_service.calculate_path_cost(route) == 300, "two staircase floors cost three walking minutes")
_expect(_runtime._estimated_actor_path_minutes(route, Array([], TYPE_STRING_NAME, &"", null)) == 3, "actor arrival estimate includes stair time")
var spawn := _command(&"spawn_guest", {&"guest_id": &"stair.guest", &"guest_type_id": &"traveler", &"gold": 100,
&"options": {&"current_cell": Vector3i(4, 0, 0), &"state": GuestRuntime.State.MOVING,
&"state_entered_at": _clock.absolute_minute - 1, &"planned_exit_at": _clock.absolute_minute + 2000,
&"path_handle": {&"target_cell": Vector3i(4, 2, 0)}}})
_expect(spawn.is_success(), "public guest spawn creates a real stair traveller")
_clock.advance_minutes(1)
var guest: GuestRuntime = _runtime.guest_service.get_guest(&"stair.guest")
_expect(guest.current_cell == Vector3i(4, 0, 0), "after one minute guest is still completing the first staircase")
var mid: Dictionary = _runtime.create_snapshot()
_expect(mid[&"systems"][&"runtime"][&"movement_credits"][0][&"credit"] == 1000, "partial staircase persists one minute of movement credit")
_expect(_runtime.restore_snapshot(mid) and _runtime.create_snapshot() == mid, "half-finished staircase restores exactly")
_clock.advance_minutes(2)
var arrived: Dictionary = _runtime.create_snapshot()
_expect(_runtime.guest_service.get_guest(&"stair.guest").current_cell == Vector3i(4, 2, 0), "two floors complete in three actual simulation minutes")
_expect(_runtime.restore_snapshot(mid), "restore partial staircase for deterministic replay")
_clock.advance_minutes(2)
_expect(_runtime.create_snapshot() == arrived, "fractional stair travel replays every aggregate exactly")
var bad := mid.duplicate(true)
bad[&"systems"][&"runtime"][&"movement_credits"][0][&"credit"] = 1500
_expect(not _runtime.restore_snapshot(bad) and _runtime.create_snapshot() == arrived, "unspent complete stair step is rejected atomically")
bad = mid.duplicate(true)
bad[&"systems"][&"runtime"][&"path"][&"vertical_walk_requires_connector"] = false
_expect(not _runtime.restore_snapshot(bad), "current Runtime cannot save unrestricted vertical walking")
bad = mid.duplicate(true)
bad[&"systems"][&"runtime"].erase(&"legacy_stairs")
_expect(not _runtime.restore_snapshot(bad), "current Runtime requires stair ownership metadata")
bad = mid.duplicate(true)
bad[&"systems"][&"runtime"][&"runtime_schema_version"] = 8
bad[&"systems"][&"runtime"].erase(&"legacy_stairs")
bad[&"systems"][&"runtime"][&"path"][&"vertical_walk_requires_connector"] = false
_expect(not _runtime.restore_snapshot(bad), "older Runtime label cannot smuggle current Path policy fields")
var blockade := mid.duplicate(true)
for traffic: Dictionary in blockade[&"systems"][&"runtime"][&"path"][&"cell_traffic"]:
if traffic[&"cell"] == Vector3i(4, 1, 0):
traffic[&"temporary_block"] = true
_expect(_runtime.restore_snapshot(blockade), "temporary blockade preserves valid fractional staircase credit")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "blocked partial staircase re-saves canonically")
_reset()
_build(&"guest_corridor", Vector3i(4, 1, 0))
_build(&"guest_stairs", Vector3i(4, 2, 0))
var lower_id := _facility_at(Vector3i(4, 1, 0))
var upper_id := _facility_at(Vector3i(4, 2, 0))
var blocked := _command(&"demolish_facility", {&"instance_id": lower_id})
_expect(not blocked.is_success() and blocked.details.get(&"reason") == &"STAIR_LANDING_IN_USE", "occupied lower landing rejects demolition before a broken stair is created")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "rejected stair demolition remains saveable")
_expect(_command(&"demolish_facility", {&"instance_id": upper_id}).is_success(), "upper staircase can be removed first")
_expect(_command(&"demolish_facility", {&"instance_id": lower_id}).is_success(), "unused lower corridor can then be removed")
_reset()
_build(&"service_stairs", Vector3i(4, 1, 1))
_expect(_runtime.path_service.find_path(Vector3i(4, 0, 1), Vector3i(4, 1, 1)).is_empty(), "guests cannot use service stairs")
_expect(_runtime.path_service.find_path(Vector3i(4, 0, 1), Vector3i(4, 1, 1), [0, 1], [&"staff"]).size() == 2, "staff can use service stairs")
_test_legacy()
_test_occupied_stair_demolition()
print("Runtime stairs: %d checks, %d failures" % [_checks, _failures])
_runtime.free()
_clock.free()
_state.free()
quit(1 if _failures else 0)
func _test_legacy() -> void:
_reset()
_build(&"guest_corridor", Vector3i(4, 1, 0))
_build(&"guest_corridor", Vector3i(4, 2, 0))
_expect(_runtime.path_service.find_path(Vector3i(4, 0, 0), Vector3i(4, 2, 0)).is_empty(), "new corridors do not act as invisible stairs")
var legacy: Dictionary = _runtime.create_snapshot()
var data: Dictionary = legacy[&"systems"][&"runtime"]
data[&"runtime_schema_version"] = 8
data.erase(&"legacy_stairs")
data[&"path"][&"schema_version"] = 4
data[&"path"].erase(&"vertical_walk_requires_connector")
data[&"path"].erase(&"transit_actor_ids")
_expect(_runtime.restore_snapshot(legacy), "actual v8 structure migrates existing vertical corridors")
var records: Array = _runtime.legacy_stairs.create_snapshot()[&"stairs"]
_expect(records.size() == 2 and _runtime.path_service.get_vertical_walk_requires_connector(), "migration materializes only the two original vertical links")
var route: Array = _runtime.path_service.find_path(Vector3i(4, 0, 0), Vector3i(4, 2, 0))
_expect(route.size() == 3 and _runtime.path_service.calculate_path_cost(route) == 200, "old corridor stairs retain their original speed and reachability")
var canonical: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(canonical) and _runtime.create_snapshot() == canonical, "migrated save is canonical and migration does not repeat")
var malformed := canonical.duplicate(true)
malformed[&"systems"][&"runtime"][&"legacy_stairs"][&"stairs"].pop_back()
_expect(not _runtime.restore_snapshot(malformed) and _runtime.create_snapshot() == canonical, "orphan legacy connector is rejected atomically")
var upper_id := _facility_at(Vector3i(4, 2, 0))
_expect(_command(&"demolish_facility", {&"instance_id": upper_id}).is_success(), "legacy upper landing can be demolished normally")
_expect(_runtime.legacy_stairs.create_snapshot()[&"stairs"].size() == 1, "demolition retires that stair's ownership record")
_build(&"guest_corridor", Vector3i(4, 2, 0))
_expect(_runtime.path_service.find_path(Vector3i(4, 1, 0), Vector3i(4, 2, 0)).is_empty(), "rebuilding a plain corridor never regenerates a legacy staircase")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "pruned and rebuilt topology remains saveable")
func _reset() -> void:
_clock.reset(1, 6, 0)
_state.reset()
_expect(_runtime.initialize(_clock, _state, 90416)[&"valid"], "stairs fixture initializes")
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
_clock.set_paused(true)
func _build(id: StringName, origin: Vector3i) -> void:
var built := _command(&"build_facility", {&"definition_id": id, &"origin": origin})
_expect(built.is_success(), "normal construction accepts %s %s: %s" % [id, origin, built.message])
_clock.advance_minutes(10)
_expect(not _facility_at(origin).is_empty(), "construction completes %s %s" % [id, origin])
func _facility_at(origin: Vector3i) -> StringName:
for facility: Dictionary in _runtime.construction_service.get_facilities():
if facility[&"origin"] == origin:
return facility[&"instance_id"]
return &""
func _command(type: StringName, payload: Dictionary) -> CommandResult:
_serial += 1
return _runtime.submit_command(type, payload, &"manager", &"", 0, -1, StringName("runtime.stairs.%d" % _serial))
func _expect(ok: bool, label: String) -> void:
_checks += 1
if not ok:
_failures += 1
push_error(label)
else:
print("PASS: ", label)
func _test_occupied_stair_demolition() -> void:
_reset()
_build(&"guest_stairs", Vector3i(4, 1, 0))
var stair_id := _facility_at(Vector3i(4, 1, 0))
_expect(_command(&"spawn_guest", {&"guest_id": &"occupied.stair", &"guest_type_id": &"traveler", &"gold": 100,
&"options": {&"current_cell": Vector3i(4, 1, 0), &"state": GuestRuntime.State.MOVING,
&"state_entered_at": _clock.absolute_minute - 1, &"planned_exit_at": _clock.absolute_minute + 100,
&"path_handle": {&"target_cell": Vector3i(4, 0, 0)}}}).is_success(), "real guest is standing on upper staircase landing")
var paths: Dictionary = _runtime.path_service.create_snapshot()
var rejected := _command(&"demolish_facility", {&"instance_id": stair_id})
_expect(not rejected.is_success() and rejected.details.get(&"reason") == &"FACILITY_OCCUPIED", "occupied staircase cannot delete a real guest's landing")
_expect(_runtime.path_service.create_snapshot() == paths and _runtime.guest_service.get_guest(&"occupied.stair").current_cell == Vector3i(4, 1, 0), "demolition rejection preserves actor projection and position")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "occupied staircase rejection leaves a valid canonical save")
_clock.advance_minutes(2)
_expect(_runtime.guest_service.get_guest(&"occupied.stair").current_cell == Vector3i(4, 0, 0), "guest physically finishes descending before demolition")
_expect(_command(&"demolish_facility", {&"instance_id": stair_id}).is_success(), "empty staircase can be removed after the guest leaves")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "post-descent demolition remains saveable")
@@ -0,0 +1 @@
uid://blt3w5drpduju
+1 -1
View File
@@ -93,7 +93,7 @@ func _verify_mid_ride() -> bool:
_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(snapshot[&"systems"][&"runtime"][&"runtime_schema_version"] == 9, "runtime schema9 records elevator state and explicit vertical routing")
_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]})
@@ -0,0 +1,337 @@
extends SceneTree
## Fixed-demand MOVE-06 acceptance. Guests use public spawning plus a fixed
## MOVING destination, as in test_runtime_path_traffic. Production Runtime owns
## every path choice, walking credit, congestion delay and elevator transition.
## This isolates equipment capacity; it does not exercise natural Guest demand,
## service purchasing or economic return on investment.
const SEED := 806042
const POPULATION := 48
const WINDOW_MINUTES := 480
const START := Vector3i(2, 0, 0)
const GOAL := Vector3i(2, 1, 0)
const DEMAND_HASH := "5bafa62275370c2a6ce5b4d4f283c3d7679c679a2b3fc6b054b681f2647456cf"
const REPORT_PATH := "res://artifacts/transport/runtime-comparison.json"
var _checks := 0
var _failures := 0
var _runtime: Variant
var _clock: Variant
var _state: Variant
var _start_minute := 0
var _records: Dictionary = {}
var _trace: Array[Dictionary] = []
var _errors: Array[String] = []
var _demand_rows: Array[Dictionary] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var global_clock := root.get_node_or_null("GameClock")
if global_clock != null: global_clock.set_paused(true)
_demand_rows = _demand()
_expect(SimulationDigest.compute(_demand_rows) == DEMAND_HASH, "Runtime and pure-service fixtures share the exact seeded 48-person demand manifest")
var before := _observe(1, true)
var after := _observe(2, true)
if before.is_empty() or after.is_empty():
_finish()
return
_expect(before[&"demand_hash"] == after[&"demand_hash"] and before[&"start_minute"] == after[&"start_minute"] and before[&"window_minutes"] == after[&"window_minutes"], "same seed, arrivals, endpoints and absolute observation window in both layouts")
_expect(before[&"arrived_count"] == POPULATION and after[&"arrived_count"] == POPULATION, "both layouts receive all 48 real GuestRuntime actors")
_expect(before[&"completed_count"] == POPULATION and after[&"completed_count"] == POPULATION, "both layouts physically complete all 48 journeys inside the fixed window")
_expect(before[&"unfinished_count"] == 0 and after[&"unfinished_count"] == 0, "no incomplete passenger is omitted from the comparison")
_expect(before[&"shortest_walking_steps"] == 4 and after[&"shortest_walking_steps"] == 4, "both physical layouts have equal four-step shortest walking distance to each cabin")
_expect(before[&"walking_steps_min"] >= 4 and after[&"walking_steps_min"] >= 4, "actual Runtime guests never shorten the common four-step geometry by a hidden route or teleport")
_expect(before[&"walking_steps"][&"samples"] == POPULATION and after[&"walking_steps"][&"samples"] == POPULATION, "actual walking and detour statistics retain all completed guests")
_expect(after[&"shaft_usage"].size() == 2, "normal Runtime path choice sends passengers to both installed cabins")
_expect(before[&"queue_wait_minutes"][&"samples"] == POPULATION and after[&"queue_wait_minutes"][&"samples"] == POPULATION, "wait statistics include the entire boarded cohort")
_expect(_is_improved(before, after), "additional identical cabin improves wait and journey P95 with equal fully completed demand")
var replay := _observe(2, false)
_expect(after == replay, "fresh same-seed Runtime replay reproduces all outcomes and the full movement event trace hash")
var incomplete := after.duplicate(true)
incomplete[&"completed_count"] = 0
incomplete[&"unfinished_count"] = POPULATION
incomplete[&"queue_wait_minutes"] = _stats([])
incomplete[&"journey_minutes"] = _stats([])
_expect(not _is_improved(before, incomplete), "an unfinished zero-completion result cannot be accepted as faster")
var report := {
&"scope": "Fixed-demand public-spawn GuestRuntime harness. Actual Runtime routing, movement credit, congestion and paid construction; not natural economic Guest AI or complete P2/S14 acceptance.",
&"route_policy": "Production Runtime find_path re-evaluates each walking minute; global explicit vertical connectors; no benchmark blockers or debug simulation jumps. Congestion-driven route reconsideration may add walking; actual detours are reported, never frozen or discarded.",
&"completion_policy": "Record actual common-destination arrival, then normal begin_leaving/despawn_guest so completed actors no longer obstruct later arrivals.",
&"percentile_policy": "Nearest-rank P95 over observed boarding waits/completed journeys, with unfinished arrivals separately censored. Improvement requires both layouts to finish the entire equal cohort.",
&"seed": SEED, &"demand_hash": DEMAND_HASH, &"demand": _demand_rows,
&"before": before, &"after": after,
&"equipment_cost_difference": {&"gold": after[&"costs"][&"gold"] - before[&"costs"][&"gold"],
&"materials": after[&"costs"][&"materials"] - before[&"costs"][&"materials"],
&"maintenance_mana_demand": after[&"costs"][&"maintenance_mana_demand"] - before[&"costs"][&"maintenance_mana_demand"]},
&"improvement_accepted": _is_improved(before, after), &"checks": _checks, &"failures": _failures}
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://artifacts/transport"))
var output := FileAccess.open(REPORT_PATH, FileAccess.WRITE)
if output != null: output.store_string(JSON.stringify(report, "\t") + "\n")
print("RUNTIME_TRANSPORT_COMPARISON ", JSON.stringify({&"before": _summary(before), &"after": _summary(after), &"improved": report[&"improvement_accepted"]}))
_finish()
func _demand() -> Array[Dictionary]:
var rng := RandomNumberGenerator.new()
rng.seed = SEED
var result: Array[Dictionary] = []
for index: int in range(POPULATION):
result.append({&"actor_id": StringName("comparison/%03d" % index),
&"arrived_at": (index / 4) * 15 + rng.randi_range(0, 2),
&"from": [START.x, START.y, START.z], &"to": [GOAL.x, GOAL.y, GOAL.z]})
result.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
if a[&"arrived_at"] != b[&"arrived_at"]: return a[&"arrived_at"] < b[&"arrived_at"]
return str(a[&"actor_id"]) < str(b[&"actor_id"]))
return result
func _prepare(cabins: int) -> Dictionary:
_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, SEED)[&"valid"], "%d-cabin Runtime initializes at normal starting resources" % cabins)
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
_clock.set_paused(true)
var builds: Array = [[&"guest_elevator", Vector3i.ZERO],
[&"guest_corridor", Vector3i(2, 1, 0)], [ &"guest_corridor", Vector3i(3, 1, 0)]]
if cabins == 2: builds.append([&"guest_elevator", Vector3i(4, 0, 0)])
var success := true
for index: int in range(builds.size()):
var result: CommandResult = _runtime.submit_command(&"build_facility", {
&"definition_id": builds[index][0], &"origin": builds[index][1]},
&"player", &"", 0, _clock.absolute_minute, StringName("comparison.build.%d" % index))
success = success and result.is_success()
_expect(success, "%d-cabin layout is purchased through normal public construction" % cabins)
_clock.advance_minutes(310)
var report: Dictionary = _runtime.elevators.get_report()
var maintenance := 0
var equal_walking := true
for shaft: Dictionary in report[&"shafts"]:
success = success and shaft[&"operational"] and shaft[&"capacity"] == 4 and shaft[&"travel_minutes_per_floor"] == 10
var lower: Vector3i = shaft[&"stops"][0]
var upper: Vector3i = shaft[&"stops"][1]
var approach: Array[Vector3i] = _runtime.path_service.find_path(START, lower)
var departure: Array[Vector3i] = _runtime.path_service.find_path(upper, GOAL)
equal_walking = equal_walking and approach.size() == 3 and departure.size() == 3
for cell: Vector3i in approach: equal_walking = equal_walking and cell.y == START.y
for cell: Vector3i in departure: equal_walking = equal_walking and cell.y == GOAL.y
for module: StringName in shaft[&"module_ids"]:
var facility: Dictionary = _runtime.construction_service.get_facility(module)
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(facility[&"definition_id"])
maintenance += definition.maintenance_mana
_expect(success and report[&"shafts"].size() == cabins, "%d identical four-person cabins complete normally and receive real network power" % cabins)
_expect(equal_walking, "%d-cabin layout has two real approach and two departure walking steps at every boarding position" % cabins)
_expect(_runtime.path_service.get_vertical_walk_requires_connector(), "fixture uses production explicit vertical connector policy")
_start_minute = _clock.absolute_minute
_records = {}
_trace = []
_errors = []
_subscribe()
return {&"gold": 5000 - _state.get_resource(&"gold"), &"materials": 50 - _state.get_resource(&"materials"), &"maintenance_mana_demand": maintenance} if success else {}
func _observe(cabins: int, check_midride: bool) -> Dictionary:
var costs := _prepare(cabins)
if costs.is_empty():
_dispose()
return {}
_spawn_arrivals(0)
var checked_phases: Dictionary = {}
for offset: int in range(1, WINDOW_MINUTES + 1):
_tick(offset)
if check_midride:
for shaft: Dictionary in _runtime.elevators.get_report()[&"shafts"]:
var phase: StringName = shaft[&"phase"]
var kind: StringName = &"QUEUED" if shaft[&"waiting_count"] > 0 else phase
if kind in [&"QUEUED", &"BOARDING"] and not checked_phases.has(kind):
checked_phases[kind] = true
_check_replay(offset, kind)
if check_midride: _expect(checked_phases.has(&"QUEUED") and checked_phases.has(&"BOARDING"), "%d-cabin comparison exercises both queued and boarding snapshot checkpoints" % cabins)
_expect(_errors.is_empty(), "%d-cabin actors retain valid lifecycle, projection, event timing and physical floor transitions: %s" % [cabins, _errors])
var result := _report(cabins, costs)
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://artifacts/transport"))
var trace_output := FileAccess.open("res://artifacts/transport/runtime-comparison-%d-trace.json" % cabins, FileAccess.WRITE)
if trace_output != null: trace_output.store_string(JSON.stringify(_trace, "\t") + "\n")
_expect(_runtime.guest_service.get_guests().size() == result[&"unfinished_count"], "only genuinely unfinished guests remain in live Runtime after the observation window")
_expect(_runtime.path_service.get_transit_actor_ids().size() == result[&"aboard_count"], "Path transit occupancy equals actual unfinished boarded passengers")
_dispose()
return result
func _tick(offset: int) -> void:
_clock.advance_minutes(1)
for guest_id: StringName in _records:
var record: Dictionary = _records[guest_id]
if record[&"completed_at"] >= 0: continue
var guest: GuestRuntime = _runtime.guest_service.get_guest(guest_id)
if guest == null:
_errors.append("Guest vanished before reaching destination: %s" % guest_id)
continue
if guest.current_cell != GOAL: continue
record[&"completed_at"] = offset
_trace.append({&"kind": &"COMPLETED", &"guest_id": guest_id, &"minute": offset})
var leaving: Dictionary = _runtime.guest_service.begin_leaving(guest_id, _clock.absolute_minute, &"COMPARISON_DESTINATION_REACHED")
var despawned: Dictionary = _runtime.guest_service.despawn_guest(guest_id, _clock.absolute_minute)
if not leaving.get(&"ok", false) or not despawned.get(&"ok", false): _errors.append("Normal destination despawn failed: %s" % guest_id)
if _runtime.path_service.has_actor_projection(StringName("guest:" + String(guest_id))): _errors.append("Completed actor retained Path projection: %s" % guest_id)
_spawn_arrivals(offset)
func _spawn_arrivals(offset: int) -> void:
for row: Dictionary in _demand_rows:
if row[&"arrived_at"] != offset: continue
var guest_id: StringName = row[&"actor_id"]
_records[guest_id] = {&"guest_id": guest_id, &"arrived_at": offset, &"requested_at": -1,
&"boarded_at": -1, &"alighted_at": -1, &"completed_at": -1, &"walking_steps": 0, &"shaft_id": &""}
var result: CommandResult = _runtime.submit_command(&"spawn_guest", {
&"guest_id": guest_id, &"guest_type_id": &"traveler", &"gold": 100,
&"options": {&"current_cell": START, &"state": GuestRuntime.State.MOVING,
&"state_entered_at": _clock.absolute_minute - 1,
&"planned_exit_at": _clock.absolute_minute + 2000,
&"path_handle": {&"target_cell": GOAL}}},
&"world", guest_id, 10, -1, StringName("comparison.spawn.%s" % guest_id))
if not result.is_success(): _errors.append("Public spawn rejected: %s" % guest_id)
_trace.append({&"kind": &"SPAWNED", &"guest_id": guest_id, &"minute": offset})
func _subscribe() -> void:
_runtime.domain_events.subscribe(&"*", &"transport.comparison", Callable(self, "_on_event"))
func _on_event(event: SimulationDomainEvent) -> void:
if event.event_type not in [&"GUEST_MOVED", &"ELEVATOR_TRIP_REQUESTED", &"ELEVATOR_BOARDED", &"ELEVATOR_ARRIVED"]: return
var guest_id := StringName(String(event.entity_ids[0]).trim_prefix("guest:"))
if not _records.has(guest_id): return
var record: Dictionary = _records[guest_id]
var offset := event.absolute_minute - _start_minute
_trace.append({&"kind": event.event_type, &"guest_id": guest_id, &"minute": offset, &"payload": event.payload.duplicate(true)})
match event.event_type:
&"GUEST_MOVED":
var from: Vector3i = event.payload[&"from_cell"]
var to: Vector3i = event.payload[&"cell"]
if from.y != to.y: _errors.append("Guest walked vertically without a cabin: %s" % guest_id)
record[&"walking_steps"] += 1
&"ELEVATOR_TRIP_REQUESTED":
if record[&"requested_at"] >= 0: _errors.append("Journey requested more than one ride: %s" % guest_id)
record[&"requested_at"] = offset
record[&"shaft_id"] = event.payload[&"shaft_id"]
&"ELEVATOR_BOARDED":
record[&"boarded_at"] = offset
if int(event.payload.get(&"wait_minutes", -1)) != offset - record[&"requested_at"]: _errors.append("Boarding event wait disagrees with observed request: %s" % guest_id)
&"ELEVATOR_ARRIVED":
record[&"alighted_at"] = offset
if event.payload.get(&"emergency", false) or event.payload.get(&"cancelled", false): _errors.append("Healthy comparison ride unexpectedly cancelled: %s" % guest_id)
func _check_replay(offset: int, label: StringName) -> void:
var saved: Dictionary = _runtime.create_snapshot()
var records := _records.duplicate(true)
var trace := _trace.duplicate(true)
var errors := _errors.duplicate()
for step: int in range(offset + 1, offset + 13): _tick(step)
var expected: Dictionary = _runtime.create_snapshot()
var expected_records := _records.duplicate(true)
var expected_trace := _trace.duplicate(true)
var expected_errors := _errors.duplicate()
var restored: bool = _runtime.restore_snapshot(saved)
_expect(restored, "%s mid-journey aggregate Runtime snapshot restores" % label)
if not restored: return
_records = records.duplicate(true)
_trace.assign(trace.duplicate(true))
_errors.assign(errors)
_subscribe()
for step: int in range(offset + 1, offset + 13): _tick(step)
_expect(_runtime.create_snapshot() == expected and _records == expected_records and _trace == expected_trace and _errors == expected_errors, "%s restore replays the next 12 minutes including scheduled spawns with exact state and event trace" % label)
_expect(_runtime.restore_snapshot(saved), "%s checkpoint restores again before continuing the original observation window" % label)
_records = records
_trace.assign(trace)
_errors.assign(errors)
_subscribe()
func _report(cabins: int, costs: Dictionary) -> Dictionary:
var waits: Array[int] = []
var journeys: Array[int] = []
var rides: Array[int] = []
var walks: Array[int] = []
var completed: Array[Dictionary] = []
var unfinished: Array[Dictionary] = []
var usage: Dictionary = {}
var waiting := 0
var aboard := 0
var detours := 0
for guest_id: StringName in _records:
var record: Dictionary = _records[guest_id]
if record[&"boarded_at"] >= 0: waits.append(record[&"boarded_at"] - record[&"requested_at"])
if record[&"alighted_at"] >= 0: rides.append(record[&"alighted_at"] - record[&"boarded_at"])
if not String(record[&"shaft_id"]).is_empty(): usage[record[&"shaft_id"]] = int(usage.get(record[&"shaft_id"], 0)) + 1
if record[&"completed_at"] >= 0:
journeys.append(record[&"completed_at"] - record[&"arrived_at"])
walks.append(record[&"walking_steps"])
if record[&"walking_steps"] > 4: detours += 1
completed.append(record.duplicate(true))
else:
var trip: Dictionary = _runtime.elevators.get_actor_trip(StringName("guest:" + String(guest_id)))
var phase: StringName = trip.get(&"state", &"WALKING")
if phase == PathService.ELEVATOR_REQUEST_QUEUED: waiting += 1
if phase == PathService.ELEVATOR_REQUEST_BOARDED: aboard += 1
unfinished.append({&"guest_id": guest_id, &"state": phase, &"age_minutes": WINDOW_MINUTES - record[&"arrived_at"],
&"observed_wait_minutes": WINDOW_MINUTES - record[&"requested_at"] if phase == PathService.ELEVATOR_REQUEST_QUEUED else 0})
walks.sort()
return {&"shaft_count": cabins, &"costs": costs, &"seed": SEED, &"start_minute": _start_minute,
&"window_minutes": WINDOW_MINUTES, &"demand_count": POPULATION, &"demand_hash": SimulationDigest.compute(_demand_rows),
&"arrived_count": _records.size(), &"not_arrived_count": POPULATION - _records.size(),
&"completed_count": completed.size(), &"unfinished_count": unfinished.size(), &"failed_count": _errors.size(),
&"waiting_count": waiting, &"aboard_count": aboard, &"walking_count": unfinished.size() - waiting - aboard,
&"queue_wait_minutes": _stats(waits), &"journey_minutes": _stats(journeys), &"ride_minutes": _stats(rides),
&"shortest_walking_steps": 4, &"walking_steps": _stats(walks), &"detour_guest_count": detours,
&"walking_steps_min": walks[0] if not walks.is_empty() else -1, &"walking_steps_max": walks[-1] if not walks.is_empty() else -1,
&"shaft_usage": usage, &"completed_guests": completed, &"unfinished_guests": unfinished,
&"event_trace_hash": SimulationDigest.compute(_trace), &"event_count": _trace.size()}
func _stats(values: Array[int]) -> Dictionary:
if values.is_empty(): return {&"samples": 0, &"mean": null, &"p95": null, &"max": null}
var sorted := values.duplicate()
sorted.sort()
var total := 0
for value: int in sorted: total += value
return {&"samples": sorted.size(), &"mean": float(total) / sorted.size(), &"p95": sorted[ceili(sorted.size() * 0.95) - 1], &"max": sorted[-1]}
func _is_improved(before: Dictionary, after: Dictionary) -> bool:
return before[&"demand_hash"] == after[&"demand_hash"] and before[&"window_minutes"] == after[&"window_minutes"] \
and before[&"completed_count"] == POPULATION and after[&"completed_count"] == POPULATION \
and before[&"failed_count"] == 0 and after[&"failed_count"] == 0 \
and before[&"queue_wait_minutes"][&"samples"] == POPULATION and after[&"queue_wait_minutes"][&"samples"] == POPULATION \
and after[&"queue_wait_minutes"][&"p95"] < before[&"queue_wait_minutes"][&"p95"] \
and after[&"journey_minutes"][&"p95"] < before[&"journey_minutes"][&"p95"]
func _summary(report: Dictionary) -> Dictionary:
return {&"cabins": report[&"shaft_count"], &"completed": report[&"completed_count"], &"unfinished": report[&"unfinished_count"],
&"wait": report[&"queue_wait_minutes"], &"journey": report[&"journey_minutes"], &"walking_steps_min": report[&"walking_steps_min"],
&"walking_steps_max": report[&"walking_steps_max"], &"walking_steps": report[&"walking_steps"], &"detour_guest_count": report[&"detour_guest_count"], &"shaft_usage": report[&"shaft_usage"], &"trace_hash": report[&"event_trace_hash"]}
func _dispose() -> void:
_runtime.free()
_clock.free()
_state.free()
func _expect(condition: bool, label: String) -> void:
_checks += 1
if not condition: _failures += 1
print("%s: %s" % ["PASS" if condition else "FAIL", label])
func _finish() -> void:
print("Runtime transport comparison: %d checks, %d failures" % [_checks, _failures])
quit(0 if _failures == 0 else 1)
@@ -0,0 +1 @@
uid://ct2kere2fgrfe
@@ -0,0 +1,271 @@
extends SceneTree
## MOVE-06 component comparison: fixed arrival demand, actual public construction, and the
## production Path/Elevator services. These are measurement actors, not the
## economic Guest AI. Route choice is committed once at common-lobby arrival.
## Both scenarios use Path's production explicit floor-connector policy.
const SEED := 806042
const POPULATION := 48
const WINDOW_MINUTES := 480
const START := Vector3i(2, 0, 0)
const GOAL := Vector3i(2, 1, 0)
const CREDIT_SCALE := 1000
const REPORT_PATH := "res://artifacts/transport/path-comparison.json"
const DEMAND_PATH := "res://artifacts/transport/comparison-demand.json"
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var global_clock := root.get_node_or_null("GameClock")
if global_clock != null: global_clock.set_paused(true)
var demand := _demand()
_expect(demand.size() == POPULATION and SimulationDigest.compute(demand) == SimulationDigest.compute(_demand()), "seed fixes all 48 actor IDs, arrival minutes, origins and destinations")
var one := _prepare(1)
var two := _prepare(2)
if one.is_empty() or two.is_empty():
_finish()
return
var before := _observe(one, demand, WINDOW_MINUTES)
var after := _observe(two, demand, WINDOW_MINUTES)
_expect(before[&"demand_hash"] == after[&"demand_hash"] and before[&"demand_count"] == after[&"demand_count"], "both configurations replay the identical demand manifest")
_expect(before[&"window_minutes"] == after[&"window_minutes"], "both configurations use the same fixed observation window")
_expect(before[&"arrived_count"] == POPULATION and after[&"arrived_count"] == POPULATION, "no passengers are removed to make the improved layout faster")
_expect(before[&"completed_count"] + before[&"unfinished_count"] == POPULATION and after[&"completed_count"] + after[&"unfinished_count"] == POPULATION, "completed and censored unfinished populations account for every arrival")
_expect(before[&"failed_count"] == 0 and after[&"failed_count"] == 0, "every measurement actor has a legal route and conserved Path ownership")
_expect(before[&"walking_steps_min"] == 4 and before[&"walking_steps_max"] == 4 and after[&"walking_steps_min"] == 4 and after[&"walking_steps_max"] == 4, "left and right boarding positions have the same four horizontal walking steps")
_expect(after[&"shaft_usage"].size() == 2, "public shortest-path costs distribute demand to both installed cabins")
_expect(before[&"completed_count"] == POPULATION and after[&"completed_count"] == POPULATION, "both layouts complete the entire cohort inside the fixed window")
_expect(_is_improved(before, after), "second identical cabin improves wait and journey P95 only after both layouts complete equal demand")
var replay := _observe(one, demand, WINDOW_MINUTES)
_expect(replay == before, "same topology snapshot and seed reproduce all measured outcomes exactly")
var censored := _observe(two, demand, 30)
_expect(censored[&"unfinished_count"] > 0 and not _is_improved(before, censored), "short-window incomplete observations cannot masquerade as an improvement")
var report := {
&"scope": "Fixed-demand Path/Elevator actor harness; not natural economic Guest AI or full S14 congestion acceptance.",
&"route_policy": "Public find_path at common-lobby arrival; commit that route for this journey; real shared-cabin boarding and minute ticks.",
&"topology_policy": "Production explicit vertical connectors; no benchmark floor-separation blockers.",
&"seed": SEED, &"demand_count": POPULATION, &"observation_minutes": WINDOW_MINUTES,
&"demand_hash": SimulationDigest.compute(demand), &"before": before, &"after": after,
&"equipment_cost_difference": {
&"gold": two[&"costs"][&"gold"] - one[&"costs"][&"gold"],
&"materials": two[&"costs"][&"materials"] - one[&"costs"][&"materials"],
&"maintenance_mana_demand": two[&"costs"][&"maintenance_mana_demand"] - one[&"costs"][&"maintenance_mana_demand"]},
&"improvement_accepted": _is_improved(before, after), &"checks": _checks, &"failures": _failures}
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://artifacts/transport"))
var output := FileAccess.open(REPORT_PATH, FileAccess.WRITE)
if output != null: output.store_string(JSON.stringify(report, "\t") + "\n")
var manifest := FileAccess.open(DEMAND_PATH, FileAccess.WRITE)
if manifest != null: manifest.store_string(JSON.stringify(demand, "\t") + "\n")
print("TRANSPORT_COMPARISON ", JSON.stringify({&"before": _summary(before), &"after": _summary(after), &"improved": report[&"improvement_accepted"]}))
_finish()
func _demand() -> Array[Dictionary]:
var rng := RandomNumberGenerator.new()
rng.seed = SEED
var result: Array[Dictionary] = []
for index: int in range(POPULATION):
result.append({&"actor_id": StringName("comparison/%03d" % index),
&"arrived_at": (index / 4) * 15 + rng.randi_range(0, 2),
&"from": [START.x, START.y, START.z], &"to": [GOAL.x, GOAL.y, GOAL.z]})
result.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
if a[&"arrived_at"] != b[&"arrived_at"]: return a[&"arrived_at"] < b[&"arrived_at"]
return str(a[&"actor_id"]) < str(b[&"actor_id"]))
return result
func _prepare(cabins: int) -> Dictionary:
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
var clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
clock.reset(1, 6, 0)
state.reset()
_expect(runtime.initialize(clock, state, SEED)[&"valid"], "%d-cabin fixture initializes with normal 5000G/50 materials" % cabins)
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
clock.set_paused(true)
var builds: Array = [
[&"guest_elevator", Vector3i.ZERO],
[&"guest_corridor", Vector3i(2, 1, 0)],
[&"guest_corridor", Vector3i(3, 1, 0)]]
if cabins == 2: builds.append([&"guest_elevator", Vector3i(4, 0, 0)])
var success := true
for index: int in range(builds.size()):
var result: CommandResult = runtime.submit_command(&"build_facility", {
&"definition_id": builds[index][0], &"origin": builds[index][1]},
&"player", &"", 0, clock.absolute_minute, StringName("comparison.build.%d" % index))
_expect(result.is_success(), "%d-cabin case pays for %s at %s" % [cabins, builds[index][0], builds[index][1]])
success = success and result.is_success()
clock.advance_minutes(310)
var report: Dictionary = runtime.elevators.get_report()
_expect(report[&"shafts"].size() == cabins, "%d-cabin construction physically completes" % cabins)
success = success and report[&"shafts"].size() == cabins
var maintenance := 0
for shaft: Dictionary in report[&"shafts"]:
_expect(shaft[&"operational"] and shaft[&"capacity"] == 4 and shaft[&"travel_minutes_per_floor"] == 10, "comparison cabins have identical power, capacity and speed")
for module: StringName in shaft[&"module_ids"]:
var facility: Dictionary = runtime.construction_service.get_facility(module)
var definition: FacilityDefinition = runtime.definition_registry.get_facility(facility[&"definition_id"])
maintenance += definition.maintenance_mana
var result := {&"shaft_count": cabins, &"minute": clock.absolute_minute,
&"cells": runtime.route_subslot_path_adapter.get_projected_walkable_cells(),
&"path_snapshot": runtime.path_service.create_snapshot(),
&"elevator_snapshot": runtime.elevators.create_snapshot(),
&"costs": {&"gold": 5000 - state.get_resource(&"gold"), &"materials": 50 - state.get_resource(&"materials"),
&"maintenance_mana_demand": maintenance}}
runtime.free()
clock.free()
state.free()
return result if success else {}
func _observe(prepared: Dictionary, demand: Array[Dictionary], window: int) -> Dictionary:
var path := PathService.new()
path.rebuild_graph(prepared[&"cells"])
path.restore_snapshot(prepared[&"path_snapshot"])
_expect(path.get_vertical_walk_requires_connector(), "prepared Runtime uses production strict floor routing without benchmark overrides")
var elevators := ElevatorService.new()
elevators.configure(path)
elevators.restore_snapshot(prepared[&"elevator_snapshot"])
var actors: Dictionary = {}
var shaft_usage: Dictionary = {}
var completed: Array[Dictionary] = []
var failed := 0
var start_minute: int = prepared[&"minute"]
for offset: int in range(window + 1):
var minute := start_minute + offset
if offset > 0:
for arrival: Dictionary in elevators.advance_one_minute(minute):
var actor: Dictionary = actors[arrival[&"actor_id"]]
actor[&"cell"] = arrival[&"cell"]
actor[&"route_index"] += 1
actor[&"alighted_at"] = offset
actor[&"next_move_at"] = offset + 1
path.project_actor_position(arrival[&"actor_id"], arrival[&"cell"], minute)
for row: Dictionary in demand:
if row[&"arrived_at"] != offset: continue
var actor_id: StringName = row[&"actor_id"]
path.actor_enter_cell(actor_id, START, minute)
var route := path.find_path(START, GOAL, [0])
var actor := {&"actor_id": actor_id, &"arrived_at": offset, &"cell": START,
&"route": route, &"route_index": 0, &"requested_at": -1, &"boarded_at": -1,
&"alighted_at": -1, &"completed_at": -1, &"walking_steps": 0,
&"credit": 0, &"next_move_at": offset + 1, &"failed": route.is_empty()}
actors[actor_id] = actor
if route.is_empty(): failed += 1
var actor_ids: Array = actors.keys()
actor_ids.sort()
for actor_id: StringName in actor_ids:
var actor: Dictionary = actors[actor_id]
if actor[&"failed"] or actor[&"completed_at"] >= 0: continue
var trip := elevators.get_actor_trip(actor_id)
if not trip.is_empty():
if trip[&"state"] == PathService.ELEVATOR_REQUEST_BOARDED and actor[&"boarded_at"] < 0:
actor[&"boarded_at"] = offset
continue
if actor[&"cell"] == GOAL:
actor[&"completed_at"] = offset
completed.append(actor.duplicate(true))
path.despawn_actor(actor_id)
continue
if actor[&"next_move_at"] > offset: continue
var route: Array = actor[&"route"]
var index: int = actor[&"route_index"]
var next: Vector3i = route[index + 1]
var connector := path.get_step_connector(actor[&"cell"], next)
if elevators.owns_connector(connector):
var request := elevators.request_trip(actor_id, actor[&"cell"], next, minute)
if not request[&"ok"]:
actor[&"failed"] = true
failed += 1
else:
actor[&"requested_at"] = offset
var shaft: StringName = request[&"trip"][&"shaft_id"]
shaft_usage[shaft] = int(shaft_usage.get(shaft, 0)) + 1
continue
actor[&"credit"] += roundi(path.get_speed_multiplier(actor[&"cell"]) * CREDIT_SCALE)
if actor[&"credit"] < CREDIT_SCALE: continue
if not path.project_actor_position(actor_id, next, minute):
actor[&"failed"] = true
failed += 1
continue
actor[&"cell"] = next
actor[&"route_index"] += 1
actor[&"walking_steps"] += 1
actor[&"credit"] -= CREDIT_SCALE
if next == GOAL:
actor[&"completed_at"] = offset
completed.append(actor.duplicate(true))
path.despawn_actor(actor_id)
var waits: Array[int] = []
var journeys: Array[int] = []
var rides: Array[int] = []
var walking_steps: Array[int] = []
var unfinished: Array[Dictionary] = []
var waiting := 0
var aboard := 0
var walking := 0
for actor_id: StringName in actors:
var actor: Dictionary = actors[actor_id]
if actor[&"boarded_at"] >= 0: waits.append(actor[&"boarded_at"] - actor[&"requested_at"])
if actor[&"alighted_at"] >= 0: rides.append(actor[&"alighted_at"] - actor[&"boarded_at"])
if actor[&"completed_at"] >= 0:
journeys.append(actor[&"completed_at"] - actor[&"arrived_at"])
walking_steps.append(actor[&"walking_steps"])
else:
var state: StringName = elevators.get_actor_trip(actor_id).get(&"state", &"WALKING")
if state == PathService.ELEVATOR_REQUEST_QUEUED: waiting += 1
elif state == PathService.ELEVATOR_REQUEST_BOARDED: aboard += 1
else: walking += 1
unfinished.append({&"actor_id": actor_id, &"state": state, &"age_minutes": window - actor[&"arrived_at"],
&"observed_wait_minutes": window - actor[&"requested_at"] if state == PathService.ELEVATOR_REQUEST_QUEUED else 0})
walking_steps.sort()
return {&"shaft_count": prepared[&"shaft_count"], &"costs": prepared[&"costs"],
&"window_minutes": window, &"demand_count": demand.size(), &"demand_hash": SimulationDigest.compute(demand),
&"arrived_count": actors.size(), &"not_arrived_count": demand.size() - actors.size(),
&"completed_count": completed.size(), &"unfinished_count": actors.size() - completed.size(),
&"waiting_count": waiting, &"aboard_count": aboard, &"walking_count": walking, &"failed_count": failed,
&"queue_wait_minutes": _stats(waits), &"journey_minutes": _stats(journeys), &"ride_minutes": _stats(rides),
&"walking_steps_min": walking_steps[0] if not walking_steps.is_empty() else -1,
&"walking_steps_max": walking_steps[-1] if not walking_steps.is_empty() else -1,
&"shaft_usage": shaft_usage, &"completed_actors": completed, &"unfinished_actors": unfinished}
func _stats(values: Array[int]) -> Dictionary:
if values.is_empty(): return {&"samples": 0, &"mean": null, &"p95": null, &"max": null}
var sorted := values.duplicate()
sorted.sort()
var total := 0
for value: int in sorted: total += value
return {&"samples": sorted.size(), &"mean": float(total) / sorted.size(),
&"p95": sorted[maxi(0, ceili(sorted.size() * 0.95) - 1)], &"max": sorted[-1]}
func _is_improved(before: Dictionary, after: Dictionary) -> bool:
return before[&"demand_hash"] == after[&"demand_hash"] and before[&"window_minutes"] == after[&"window_minutes"] \
and before[&"completed_count"] == before[&"demand_count"] and after[&"completed_count"] == before[&"completed_count"] \
and before[&"failed_count"] == 0 and after[&"failed_count"] == 0 \
and after[&"queue_wait_minutes"][&"p95"] < before[&"queue_wait_minutes"][&"p95"] \
and after[&"journey_minutes"][&"p95"] < before[&"journey_minutes"][&"p95"]
func _summary(report: Dictionary) -> Dictionary:
return {&"cabins": report[&"shaft_count"], &"completed": report[&"completed_count"],
&"unfinished": report[&"unfinished_count"], &"wait": report[&"queue_wait_minutes"],
&"journey": report[&"journey_minutes"], &"shaft_usage": report[&"shaft_usage"]}
func _expect(condition: bool, label: String) -> void:
_checks += 1
if not condition: _failures += 1
print("%s: %s" % ["PASS" if condition else "FAIL", label])
func _finish() -> void:
print("Path and cabin comparison: %d checks, %d failures" % [_checks, _failures])
quit(0 if _failures == 0 else 1)
@@ -0,0 +1 @@
uid://b5fq4vxbrlmdn
@@ -0,0 +1,157 @@
extends SceneTree
## A genuine pre-policy Path4 saved every boarded actor in floor density.
## Rebuild that old representation from five publicly spawned Runtime guests,
## then verify migration and cross-service rollback with occupied physical cars.
var _checks := 0
var _failures := 0
var _serial := 0
var _runtime: Variant
var _clock: Variant
var _state: Variant
var _arrivals: Dictionary = {}
var _original_requests: Dictionary = {}
var _arrival_errors: Array[String] = []
const GOAL := Vector3i(0, 1, 0)
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var global_clock := root.get_node_or_null("GameClock")
if global_clock != null: global_clock.set_paused(true)
_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, 90583)[&"valid"], "density migration fixture initializes")
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
_clock.set_paused(true)
for build: Array in [[&"guest_elevator", Vector3i.ZERO], [&"guest_corridor", Vector3i(4, 1, 0)], [&"guest_corridor", Vector3i(4, 2, 0)]]:
_expect(_command(&"build_facility", {&"definition_id": build[0], &"origin": build[1]}).is_success(), "normal construction pays for migration topology")
_clock.advance_minutes(10)
_clock.advance_minutes(280)
for index: int in range(5):
var id := StringName("density.guest.%d" % index)
_expect(_command(&"spawn_guest", {&"guest_id": id, &"guest_type_id": &"traveler", &"gold": 100,
&"options": {&"current_cell": Vector3i.ZERO, &"state": GuestRuntime.State.MOVING,
&"state_entered_at": _clock.absolute_minute - 1, &"planned_exit_at": _clock.absolute_minute + 2000,
&"path_handle": {&"target_cell": GOAL}}}).is_success(), "public Runtime spawn creates actual passenger %d" % index)
_clock.advance_minutes(2)
var car: Dictionary = _runtime.elevators.get_report()[&"shafts"][0]
_expect(car[&"phase"] == &"BOARDING" and car[&"passenger_count"] == 4 and car[&"waiting_count"] == 1, "four real guests board while the fifth waits at the same floor")
_expect(_runtime.path_service.get_current_density(Vector3i.ZERO) == 1 and _runtime.path_service.get_transit_actor_ids().size() == 4, "new physical floor density excludes four occupied cabin seats")
var statistics: Dictionary = _runtime.get_elevator_wait_statistics(car[&"shaft_id"])
_expect(statistics[&"sample_count"] == 4 and statistics[&"mean_wait_minutes"] == 1.0 and statistics[&"p95_wait_minutes"] == 1 and statistics[&"oldest_wait_minutes"] == 1, "live wait read model reports real boarding events and current queue age")
var current: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(current) and _runtime.get_elevator_wait_statistics(car[&"shaft_id"]) == statistics, "wait statistics survive actual Runtime restore without duplicate samples")
var legacy := _as_v8_path4(current)
var data: Dictionary = legacy[&"systems"][&"runtime"]
var old_path: Dictionary = data[&"path"]
var old_density := 0
for cell: Dictionary in old_path[&"cell_traffic"]:
if cell[&"cell"] == Vector3i.ZERO: old_density = cell[&"current_density"]
_expect(old_density == 5 and not old_path.has(&"transit_actor_ids") and not old_path.has(&"vertical_walk_requires_connector") and not data.has(&"legacy_stairs"), "legacy fixture has genuine Path4 fields and all five old boarding-anchor occupants")
var forged := legacy.duplicate(true)
for cell: Dictionary in forged[&"systems"][&"runtime"][&"path"][&"cell_traffic"]:
if cell[&"cell"] == Vector3i.ZERO: cell[&"current_density"] = 1
_expect(_rejects(forged), "v8 relabel with new excluded-passenger density is rejected before migration")
_expect(_runtime.restore_snapshot(legacy), "actual v8 plus Path4 aggregate migrates occupied car and queued passengers")
_expect(_runtime.get_elevator_wait_statistics(car[&"shaft_id"])[&"sample_count"] == 0 and _runtime.get_elevator_wait_statistics(car[&"shaft_id"])[&"oldest_wait_minutes"] == 1, "legacy load shows no fabricated boarding samples but retains the actual queue age")
_expect(_runtime.elevators.create_snapshot() == data[&"elevators"], "migration preserves cabin phase, seats, queue IDs, travel clocks and operator power exactly")
_expect(_runtime.path_service.get_current_density(Vector3i.ZERO) == 1 and _runtime.path_service.get_transit_actor_ids().size() == 4, "migration recomputes floor density without removing any actor or cabin seat")
_expect(_runtime.path_service.get_vertical_walk_requires_connector() and _runtime.legacy_stairs.create_snapshot()[&"stairs"].size() == 2, "same migration materializes two old corridor stairs and closes implicit vertical walking")
_expect(_runtime.path_service.find_path(Vector3i(4, 0, 0), Vector3i(4, 2, 0)).size() == 3, "old corridor vertical reachability remains available after policy migration")
for guest: GuestRuntime in _runtime.guest_service.get_guests():
_expect(guest.current_cell == Vector3i.ZERO and _runtime.path_service.get_actor_projected_cell(StringName("guest:" + String(guest.guest_id))) == Vector3i.ZERO, "migration preserves actual passenger's logical boarding anchor")
var canonical: Dictionary = _runtime.create_snapshot()
_expect(canonical[&"systems"][&"runtime"][&"runtime_schema_version"] == 9 and canonical[&"systems"][&"runtime"][&"path"][&"schema_version"] == 5, "resave emits current Runtime9 and Path5 only")
_expect(_runtime.restore_snapshot(canonical) and _runtime.create_snapshot() == canonical, "canonical migrated save reloads exactly without applying density or stairs twice")
forged = canonical.duplicate(true)
forged[&"systems"][&"runtime"][&"path"][&"transit_actor_ids"].pop_back()
_expect(_rejects(forged), "current transit list cannot omit a real boarded guest")
forged = canonical.duplicate(true)
for cell: Dictionary in forged[&"systems"][&"runtime"][&"path"][&"cell_traffic"]:
if cell[&"cell"] == Vector3i.ZERO: cell[&"current_density"] = 5
_expect(_rejects(forged), "current save cannot restore old double-counted cabin density")
forged = canonical.duplicate(true)
forged[&"systems"][&"runtime"][&"legacy_stairs"][&"stairs"].pop_back()
_expect(_rejects(forged), "migrated occupied-car snapshot rejects orphan legacy connector metadata")
var token: Dictionary = _runtime._capture_event_action_token()
var first_guest := &"density.guest.0"
_expect(_runtime.guest_service.begin_leaving(first_guest, _clock.absolute_minute, &"TEST_EVENT_EXIT")[&"ok"] and _runtime.guest_service.despawn_guest(first_guest, _clock.absolute_minute)[&"ok"], "event rollback fixture removes a real boarded actor through normal lifecycle")
_expect(_runtime._restore_event_action_token(token)[&"ok"] and _runtime.create_snapshot() == canonical, "event action rollback restores guests, seats, Path density, stair ownership and all snapshot participants exactly")
for trip: Dictionary in _runtime.elevators.get_report()[&"trips"]:
_original_requests[trip[&"request_id"]] = trip[&"actor_id"]
_runtime.domain_events.subscribe(&"ELEVATOR_ARRIVED", &"density.migration.arrivals", Callable(self, "_on_arrival"))
for step: int in range(80):
_clock.advance_minutes(1)
for guest: GuestRuntime in _runtime.guest_service.get_guests():
if guest.current_cell == GOAL:
_runtime.guest_service.begin_leaving(guest.guest_id, _clock.absolute_minute, &"TEST_DESTINATION_REACHED")
_runtime.guest_service.despawn_guest(guest.guest_id, _clock.absolute_minute)
var exactly_once := _arrivals.size() == 5
for request_id: StringName in _original_requests:
exactly_once = exactly_once and _arrivals.get(request_id, 0) == 1
_expect(exactly_once and _arrival_errors.is_empty(), "all five migrated request IDs reach the real destination exactly once")
_expect(_runtime.guest_service.get_guests().is_empty() and _runtime.elevators.get_report()[&"trips"].is_empty() and _runtime.path_service.get_transit_actor_ids().is_empty(), "completed lifecycle leaves no orphan guest, Path transit actor or elevator trip")
var final_state: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(final_state) and _runtime.create_snapshot() == final_state, "post-arrival migrated world remains strictly canonical")
_runtime.free()
_clock.free()
_state.free()
print("Transport density migration: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures else 0)
func _as_v8_path4(current: Dictionary) -> Dictionary:
var result := current.duplicate(true)
var data: Dictionary = result[&"systems"][&"runtime"]
data[&"runtime_schema_version"] = 8
data.erase(&"legacy_stairs")
var path: Dictionary = data[&"path"]
path[&"schema_version"] = 4
path.erase(&"vertical_walk_requires_connector")
path.erase(&"transit_actor_ids")
var old_densities: Dictionary = {}
for actor: Dictionary in path[&"actor_cells"]:
old_densities[actor[&"cell"]] = int(old_densities.get(actor[&"cell"], 0)) + 1
for cell: Dictionary in path[&"cell_traffic"]:
cell[&"current_density"] = int(old_densities.get(cell[&"cell"], 0))
# V8 had no boarding telemetry event. Stable ID high-water marks remain
# monotonic; retiring those observer-only events does not renumber history.
var events: Dictionary = data[&"domain_events"]
var retired: Array = []
for event: Dictionary in events["recent_events"]:
if event["event_type"] == "ELEVATOR_BOARDED": retired.append(event["event_id"])
events["recent_events"] = events["recent_events"].filter(func(event: Dictionary) -> bool: return event["event_id"] not in retired)
events["published_ids"] = events["published_ids"].filter(func(id: String) -> bool: return id not in retired)
return result
func _on_arrival(event: SimulationDomainEvent) -> void:
var request_id: StringName = event.payload[&"request_id"]
if not _original_requests.has(request_id):
_arrival_errors.append("Unrequested duplicate ride: %s" % request_id)
return
_arrivals[request_id] = int(_arrivals.get(request_id, 0)) + 1
if event.payload[&"cell"] != GOAL or event.payload.get(&"emergency", false): _arrival_errors.append("Unexpected migrated destination")
func _command(type: StringName, payload: Dictionary) -> CommandResult:
_serial += 1
return _runtime.submit_command(type, payload, &"world", &"", 0, -1, StringName("density.migration.%d" % _serial))
func _rejects(candidate: Dictionary) -> bool:
var before: Dictionary = _runtime.create_snapshot()
return not _runtime.restore_snapshot(candidate) and _runtime.create_snapshot() == before
func _expect(ok: bool, label: String) -> void:
_checks += 1
if not ok: _failures += 1
print("%s: %s" % ["PASS" if ok else "FAIL", label])
@@ -0,0 +1 @@
uid://dv2w40du2y6mx
+16 -6
View File
@@ -15,21 +15,28 @@ func _run() -> void:
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")
_expect(current[&"systems"][&"runtime"][&"runtime_schema_version"] == 9, "Runtime emits schema9 with elevator and explicit vertical-route aggregates")
var legacy := current.duplicate(true)
var data: Dictionary = legacy[&"systems"][&"runtime"]
data[&"runtime_schema_version"] = 7
data.erase(&"elevators")
data.erase(&"legacy_stairs")
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")
data[&"path"].erase(&"vertical_walk_requires_connector")
data[&"path"].erase(&"transit_actor_ids")
_expect(runtime.restore_snapshot(legacy), "genuine v7 without lifts migrates from Path3 into Runtime9")
var migrated: Dictionary = runtime.create_snapshot()
var expected := current.duplicate(true)
# Closing the old implicit-WALK topology bumps only the Path graph revision.
expected[&"systems"][&"runtime"][&"path"][&"graph_revision"] += 1
_expect(migrated == expected, "empty-lift migration preserves all state except the explicit topology revision")
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")
_expect(_rejects(runtime, bad), "Runtime9 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")
@@ -53,7 +60,7 @@ func _run() -> void:
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")
_expect(runtime.restore_snapshot(current), "Runtime9 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"
@@ -100,13 +107,16 @@ func _test_attendance_migration() -> void:
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")
_expect(_rejects(runtime, logical), "Runtime9 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.erase(&"legacy_stairs")
data[&"path"][&"schema_version"] = 3
data[&"path"].erase(&"vertical_walk_blockers")
data[&"path"].erase(&"vertical_walk_requires_connector")
data[&"path"].erase(&"transit_actor_ids")
_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")
+8 -3
View File
@@ -46,12 +46,17 @@ func _run() -> void:
_expect(_guide.get_progress().get(&"stage") == &"room", "completed reception advances room guidance")
_build(&"standard_room", Vector3i(-8, 2, 0))
_build(&"standard_room", Vector3i(-6, 2, 0))
_build(&"guest_corridor", Vector3i(-4, 1, 0))
_build(&"guest_stairs", Vector3i(-4, 1, 0))
await _advance(60)
_build(&"guest_corridor", Vector3i(-4, 2, 0))
var access_guide := _guide.get_progress()
_expect(access_guide.get(&"stage") == &"access" and access_guide.get(&"action") == &"build_stairs" and "上下階は客用階段" in access_guide.get(&"detail", ""), "guide explains explicit stairs before the upper rooms become accessible")
await _capture("stairs-guidance")
_main.call("_on_opening_action", access_guide[&"action"])
_expect(_main.get("world_preview").build_definition.id == &"guest_stairs", "opening-guide action selects the real guest stairs palette entry")
_build(&"guest_stairs", Vector3i(-4, 2, 0))
await _advance(10)
for x: int in range(-8, -3):
_build(&"guest_corridor", Vector3i(x, 3, 0))
_build(&"guest_stairs" if x == -4 else &"guest_corridor", Vector3i(x, 3, 0))
await _advance(10)
var reception_id: StringName = &""
for facility: FacilityInstance in _runtime.facility_service.get_instances():
+3 -3
View File
@@ -49,7 +49,7 @@ func _run() -> void:
"world preview consumes the scene theme instead of hard-coded asset paths"
)
_expect(
visual_theme.facility_icons.size() == 16
visual_theme.facility_icons.size() == 18
and visual_theme.navigation_icons.size() == 6
and visual_theme.resource_icons.size() == 4
and visual_theme.control_icons.size() == 6,
@@ -92,12 +92,12 @@ func _run() -> void:
"dashboard exposes action points and payroll arrears"
)
var palette := _main.find_child("BuildPalette", true, false) as GridContainer
_expect(palette.get_child_count() == 16, "all sixteen MVP facilities appear in the build palette")
_expect(palette.get_child_count() == 18, "all sixteen MVP facilities and two stairs appear in the build palette")
var iconized_build_buttons := 0
for build_button_value: Variant in (_main.get("build_buttons") as Dictionary).values():
if (build_button_value as Button).icon != null:
iconized_build_buttons += 1
_expect(iconized_build_buttons == 16, "all facility catalog buttons consume theme-provided icons")
_expect(iconized_build_buttons == 18, "all facility catalog buttons consume theme-provided icons")
var palette_scroll := _main.find_child("BuildPaletteScroll", true, false) as ScrollContainer
_expect(
palette_scroll != null and palette_scroll.is_ancestor_of(palette),
+152
View File
@@ -0,0 +1,152 @@
extends SceneTree
const LegacyType := preload("res://scripts/world/legacy_stair_service.gd")
class LegacyViewRuntime extends RefCounted:
var world_service := WorldService.new()
var path_service := PathService.new()
var legacy_stairs := LegacyType.new()
class WaitStatisticsRuntime extends RefCounted:
var samples := 0
func get_elevator_wait_statistics(_shaft_id: StringName) -> Dictionary:
return {&"sample_count":samples, &"mean_wait_minutes":2.5, &"p95_wait_minutes":7,
&"oldest_wait_minutes":9, &"window_minutes":60, &"event_history_limit":256}
var _checks := 0
var _failures := 0
var _main: Control
var _runtime: Variant
var _clock: Variant
var _preview: WorldPreview
var _guest_stairs: StringName = &""
var _service_stairs: StringName = &""
var _elevator: 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()
_expect(_runtime.initialize(_clock,state,947263)[&"valid"], "normal Runtime initializes the transport UI fixture")
_clock.set_paused(true)
root.size = Vector2i(1280,720)
_main = load("res://scenes/main/main.tscn").instantiate() as Control
root.add_child(_main)
_runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
_preview = _main.get("world_preview")
await process_frame
await process_frame
var buttons: Dictionary = _main.get("build_buttons")
_expect(buttons.has(&"guest_stairs") and buttons.has(&"service_stairs"), "both registered stair definitions appear in the building palette")
_expect("客用階段" in buttons[&"guest_stairs"].text and "業務用階段" in buttons[&"service_stairs"].text, "stairs use the localized catalog names")
var icon := buttons[&"guest_stairs"].icon as AtlasTexture
_expect(icon != null and icon.atlas == _preview.visual_theme.pixel_dungeon_tiles and icon.region == Rect2(48,48,16,16), "stair controls reuse the existing real Kenney Tiny Dungeon stair tile")
_main.call("_select_build_definition", &"guest_stairs")
_main.call("_on_map_cell_clicked", Vector3i(-1,1,0))
_main.call("_set_layer", 1)
_main.call("_select_build_definition", &"service_stairs")
_main.call("_on_map_cell_clicked", Vector3i(-1,1,1))
_main.call("_set_layer", 0)
_main.call("_select_build_definition", &"guest_elevator")
_main.call("_on_map_cell_clicked", Vector3i(0,0,0))
_expect(_runtime.construction_service.get_jobs().size() == 3, "normal palette and map clicks pay for two stairs and one elevator")
_clock.advance_minutes(310)
_main.call("_clear_build_selection")
for facility: Dictionary in _runtime.construction_service.get_facilities():
match facility[&"definition_id"]:
&"guest_stairs": _guest_stairs = facility[&"instance_id"]
&"service_stairs": _service_stairs = facility[&"instance_id"]
&"guest_elevator": _elevator = facility[&"instance_id"]
_expect(not _guest_stairs.is_empty() and not _service_stairs.is_empty() and not _elevator.is_empty(), "all three paid construction projects finish")
if _guest_stairs.is_empty() or _service_stairs.is_empty() or _elevator.is_empty():
_finish()
return
_main.call("_on_facility_selected", _guest_stairs)
_expect(_main.get("selected_heading_label").text == "客用階段", "finished route-proxy stairs display an inspector instead of a syncing placeholder")
var detail: String = _main.get("selected_detail_label").text
_expect("1F ↔ 2F" in detail and "客・従業員用" in detail and "魔力は不要" in detail, "guest stair inspector explains its floors, users and operation")
_expect(not _main.get("selected_elevator_controls").visible, "stairs have no cabin stop button")
_main.call("_on_facility_selected", _service_stairs)
detail = _main.get("selected_detail_label").text
_expect(_main.get("selected_heading_label").text == "業務用階段" and "従業員専用" in detail and "BACK" in detail, "service stair inspector identifies staff-only back-layer access")
_main.call("_on_facility_selected", _elevator)
detail = _main.get("selected_detail_label").text
_expect("最近の乗車記録 0人(計測待ち)" in detail and "現在の最長待ち 0分" in detail, "unused real elevator labels the sample as pending instead of an observed zero average")
_expect("直近256件" in _main.get("selected_detail_label").tooltip_text and "過去60分" in _main.get("selected_detail_label").tooltip_text, "statistics tooltip explains retained-event sampling")
_test_sample_format()
await _test_legacy_stair_selection()
_main.call("_on_facility_selected", _guest_stairs)
_expect(_main.get("selected_detail_label").tooltip_text.is_empty(), "switching from elevator to stairs removes obsolete wait-statistics tooltip")
_preview.set_zoom(2.5)
_preview.pan_offset += _preview.size*0.5-_preview.cell_to_canvas(Vector3i(0,1,0))
_preview.queue_redraw()
await process_frame
await process_frame
if OS.get_environment("THE_TREE_STAIRS_UI_CAPTURE") == "1":
await RenderingServer.frame_post_draw
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://artifacts/transport"))
root.get_texture().get_image().save_png("res://artifacts/transport/stairs-inspector.png")
_finish()
func _test_sample_format() -> void:
var fixture := WaitStatisticsRuntime.new()
_main.set("_runtime", fixture)
var pending: String = _main.call("_elevator_wait_text", &"fixture")
_expect("計測待ち" in pending and not "平均待ち" in pending and "9分" in pending, "no boarding samples still show the current live queue age")
fixture.samples = 4
var measured: String = _main.call("_elevator_wait_text", &"fixture")
_expect("最近の乗車記録 4人" in measured and "平均待ち 2.5分 / 95% 7.0分" in measured, "sampled average and P95 retain precision and correct labels")
_expect("現在の最長待ち 9分" in measured and not "直近60分の乗車" in measured, "current queue age stays distinct from the retained boarding sample")
_main.set("_runtime", _runtime)
func _test_legacy_stair_selection() -> void:
var fixture := LegacyViewRuntime.new()
fixture.world_service.reset({&"min_x":-2,&"max_x":2,&"min_y":-1,&"max_y":2})
fixture.path_service.rebuild_graph([Vector3i.ZERO,Vector3i(0,1,0),Vector3i(0,0,1),Vector3i(0,1,1)])
_expect(fixture.legacy_stairs.configure(fixture.path_service) and fixture.legacy_stairs.migrate_existing_vertical_walks(), "legacy view uses real canonical stair migration")
var before := SimulationDigest.compute(_runtime.create_snapshot())
_preview.set_runtime(fixture)
_preview.reset_view()
_preview.layer_mode = WorldPreview.LayerMode.FRONT
var rect: Rect2 = _preview.call("_legacy_stair_rect", Vector3i(0,1,0))
var stair_id := _preview.legacy_stair_at_canvas(rect.get_center())
_expect(stair_id == &"legacy.stairs:0:0:0", "legacy stairs are hit-tested on the upper landing")
_expect(_preview.legacy_stair_at_canvas(_preview.get_cell_rect(Vector3i(0,1,0)).position+Vector2.ONE).is_empty(), "small legacy stair target preserves the rest of an existing room's click area")
_expect(_preview.legacy_stair_at_canvas(_preview.cell_to_canvas(Vector3i.ZERO)).is_empty(), "lower floor cannot select the stair glyph drawn upstairs")
_click(rect.get_center())
_expect(_main.get("_selected_entity_kind") == &"legacy_stairs" and _main.get("_selected_entity_id") == stair_id, "legacy glyph click opens its dedicated selection without clearing its ID")
_expect("既設の階段" == _main.get("selected_heading_label").text and "1F ↔ 2F" in _main.get("selected_detail_label").text, "legacy inspector describes the preserved connection")
_preview.layer_mode = WorldPreview.LayerMode.BACK
_click(rect.get_center())
_expect(_main.get("_selected_entity_id") == &"legacy.stairs:1:0:0" and "客・従業員用" in _main.get("selected_detail_label").text and "BACK" in _main.get("selected_detail_label").text, "legacy BACK stairs preserve the old public access instead of falsely claiming staff-only access")
var records := _preview.get_legacy_stair_records()
records[0][&"id"] = &"changed.view.copy"
_expect(not str(fixture.legacy_stairs.create_snapshot()).contains("changed.view.copy"), "visual records cannot mutate saved stair ownership")
await process_frame
await process_frame
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == before, "stair painting, selection and inspection never advance or mutate Runtime")
_preview.set_runtime(_runtime)
_main.call("_set_layer", 0)
func _click(at: Vector2) -> void:
var event := InputEventMouseButton.new()
event.button_index = MOUSE_BUTTON_LEFT
event.pressed = true
event.position = at
_preview.call("_gui_input",event)
func _finish() -> void:
_clock.set_paused(true)
print("Stair and transport details 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://bkmdj2r61ue64
+3 -1
View File
@@ -101,7 +101,7 @@ func _test_snapshot_validation_and_legacy() -> void:
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")
_expect(snapshot[&"schema_version"] == 5, "Path v5 retains shaft ownership and explicit floor policy")
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")
@@ -138,6 +138,8 @@ func _test_snapshot_validation_and_legacy() -> void:
legacy[&"schema_version"] = version
_expect(not restored.restore_snapshot(legacy), "old version with retained shaft field cannot disable safety")
legacy.erase(&"vertical_walk_blockers")
legacy.erase(&"vertical_walk_requires_connector")
legacy.erase(&"transit_actor_ids")
_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")
+213
View File
@@ -0,0 +1,213 @@
extends SceneTree
const PathType = preload("res://scripts/world/path_service.gd")
const LegacyType = preload("res://scripts/world/legacy_stair_service.gd")
class ControlledPath:
extends PathService
var fail_restore_once := false
var fail_restore_always := false
var reenter: LegacyStairService
var nested_rejected := false
func restore_snapshot(snapshot: Dictionary) -> bool:
var result := super.restore_snapshot(snapshot)
if reenter != null:
var target := reenter
reenter = null
nested_rejected = not target.migrate_existing_vertical_walks()
if fail_restore_always:
set_vertical_walk_requires_connector(not get_vertical_walk_requires_connector())
return false
if fail_restore_once:
fail_restore_once = false
return false
return result
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_test_one_time_migration_and_access()
_test_prune_and_no_regeneration()
_test_snapshot_ownership()
_test_atomic_failure_and_reentry()
print("Legacy stairs: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
func _test_one_time_migration_and_access() -> void:
var path := PathType.new()
var cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0), Vector3i(1, 0, 0), Vector3i(1, 1, 0), Vector3i(2, 0, 0), Vector3i(2, 1, 0), Vector3i(0, 0, 1), Vector3i(0, 1, 1)]
_expect(path.rebuild_graph(cells), "materialize legacy floors")
_expect(path.configure_connector(&"staff.door", Vector3i(0, 0, 0), Vector3i(0, 0, 1), 120, true, &"staff"), "existing staff door remains separately owned")
_expect(path.configure_connector(&"external.express", Vector3i(0, 0, 0), Vector3i(0, 1, 0), 50, false, &"permit"), "existing cheaper one-way connector coexists with WALK")
var shaft_cells: Array[Vector3i] = [Vector3i(2, 0, 0), Vector3i(2, 1, 0)]
_expect(path.set_vertical_walk_blockers(&"infra.shaft:lift", shaft_cells), "existing elevator shaft suppresses ordinary climbing")
_expect(path.set_cell_temporary_block(Vector3i(1, 1, 0), true), "temporary traffic block does not remove structural connection")
_expect(path.project_occupancy([{&"actor_id": &"staff:porter", &"cell": Vector3i(0, 0, 1)}]), "preserve existing occupancy during migration")
var before := path.create_snapshot()
var legacy := LegacyType.new()
_expect(legacy.configure(path), "bind legacy owner")
_expect(legacy.migrate_existing_vertical_walks(), "one migration materializes ordinary vertical WALK edges")
var snapshot := legacy.create_snapshot()
_expect(snapshot[&"stairs"].size() == 4, "both FRONT and BACK WALK edges convert once, without shaft bypass")
_expect(path.get_vertical_walk_requires_connector() and legacy.validate_invariants()[&"ok"], "migration enables strict floor policy")
_expect(path.has_connector(&"legacy.stairs:0:0:0") and path.has_connector(&"legacy.stairs:0:0:1"), "stable IDs encode layer, x and lower floor")
_expect(not path.has_connector(&"legacy.stairs:0:2:0"), "shaft has no free compatibility stairs")
_expect(path.get_connector(&"legacy.stairs:0:1:0")[&"cost"] == 100, "temporary blocked route still migrates at original WALK score")
_expect(path.get_connector(&"legacy.stairs:1:0:0")[&"access_id"] == &"", "legacy BACK route keeps its previous access semantics")
_expect(path.get_connector(&"staff.door") == _connector(before, &"staff.door"), "staff door cost and permission are unchanged")
_expect(path.get_connector(&"external.express") == _connector(before, &"external.express"), "one-way custom connector retains exact configuration")
var after := path.create_snapshot()
for key: StringName in [&"cell_traffic", &"actor_cells", &"actor_progress", &"vertical_walk_blockers", &"active_elevator_units", &"known_elevator_request_ids", &"transit_actor_ids", &"traffic_revision"]:
_expect(after[key] == before[key], "migration preserves unrelated Path field %s" % key)
_expect(path.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0), [0], []).size() == 3, "ordinary guests retain their old multi-floor route")
_expect(path.find_path(Vector3i(0, 0, 0), Vector3i(0, 1, 1), [0, 1], []).is_empty(), "migration does not bypass staff-door access")
_expect(not path.find_path(Vector3i(0, 0, 0), Vector3i(0, 1, 1), [0, 1], [&"staff"]).is_empty(), "staff keep cross-layer commute via original door")
_expect(legacy.migrate_existing_vertical_walks() and legacy.create_snapshot() == snapshot and path.create_snapshot() == after, "repeat migration is an exact no-op")
cells.append(Vector3i(0, 3, 0))
_expect(path.rebuild_graph(cells), "append newly built upper corridor after migration")
_expect(legacy.migrate_existing_vertical_walks() and legacy.create_snapshot() == snapshot, "new vertical adjacency never receives compatibility stairs")
_expect(path.find_path(Vector3i(0, 2, 0), Vector3i(0, 3, 0)).is_empty(), "new floors require a real stair or elevator")
snapshot[&"stairs"].clear()
_expect(legacy.create_snapshot()[&"stairs"].size() == 4, "snapshot returns defensive metadata")
func _test_prune_and_no_regeneration() -> void:
var path := PathType.new()
var cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0), Vector3i(1, 0, 0), Vector3i(1, 1, 0)]
path.rebuild_graph(cells)
var legacy := LegacyType.new()
legacy.configure(path)
_expect(legacy.migrate_existing_vertical_walks(), "migrate prune fixture")
cells.erase(Vector3i(0, 1, 0))
_expect(path.rebuild_graph(cells), "demolish middle landing")
_expect(not legacy.validate_invariants()[&"ok"], "missing landing marks legacy ownership stale until prune")
_expect(legacy.prune_invalid(), "prune both incident stair edges atomically")
_expect(legacy.create_snapshot()[&"stairs"].size() == 1 and not path.has_connector(&"legacy.stairs:0:0:0") and not path.has_connector(&"legacy.stairs:0:0:1"), "removed landing retires both owned connector records")
cells.append(Vector3i(0, 1, 0))
_expect(path.rebuild_graph(cells) and legacy.prune_invalid(), "rebuild same coordinates after demolition")
_expect(path.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)).is_empty(), "retired links never resurrect at reused coordinates")
var blocked: Array[Vector3i] = [Vector3i(1, 1, 0)]
_expect(path.set_vertical_walk_blockers(&"new.shaft", blocked), "new elevator shaft covers remaining landing")
_expect(legacy.prune_invalid() and legacy.create_snapshot()[&"stairs"].is_empty(), "new shaft removes touching compatibility connector")
_expect(path.remove_vertical_walk_blockers(&"new.shaft") and legacy.prune_invalid(), "remove elevator shaft later")
_expect(not path.has_connector(&"legacy.stairs:0:1:0"), "removing a shaft does not restore compatibility stairs")
var before := path.create_snapshot()
_expect(legacy.prune_invalid() and path.create_snapshot() == before, "unchanged prune does not advance graph revision")
path.set_vertical_walk_requires_connector(false)
before = path.create_snapshot()
_expect(not legacy.migrate_existing_vertical_walks() and not legacy.prune_invalid(), "policy tampering cannot restart closed migration")
_expect(path.create_snapshot() == before, "rejected repeated migration does not mutate tampered Path")
func _test_snapshot_ownership() -> void:
var path := PathType.new()
var cells: Array[Vector3i] = [Vector3i(-1, -1, 0), Vector3i(-1, 0, 0), Vector3i(2, 0, 1), Vector3i(2, 1, 1)]
path.rebuild_graph(cells)
var legacy := LegacyType.new()
legacy.configure(path)
_expect(legacy.migrate_existing_vertical_walks(), "migrate negative floor and BACK snapshot fixture")
var saved_path := path.create_snapshot()
var saved := legacy.create_snapshot()
var restored_path := PathType.new()
var restored := LegacyType.new()
_expect(restored.configure(restored_path), "bind destination before restoring Path")
_expect(restored_path.restore_snapshot(saved_path) and restored_path.rebuild_graph(cells), "restore and materialize saved Path")
_expect(restored.restore_snapshot(saved) and restored.create_snapshot() == saved, "strict current legacy snapshot roundtrip")
var malformed: Array[Dictionary] = []
var row := saved.duplicate(true)
row[&"extra"] = true
malformed.append(row)
row = saved.duplicate(true)
row[&"schema_version"] = 2
malformed.append(row)
row = saved.duplicate(true)
row[&"schema_version"] = "1"
malformed.append(row)
row = saved.duplicate(true)
row[&"stairs"].reverse()
malformed.append(row)
row = saved.duplicate(true)
row[&"stairs"].append(row[&"stairs"][0].duplicate(true))
malformed.append(row)
row = saved.duplicate(true)
row[&"stairs"][0][&"id"] = "legacy.stairs:forged"
malformed.append(row)
row = saved.duplicate(true)
row[&"stairs"][0][&"to"] = Vector3i(-1, 1, 0)
malformed.append(row)
row = saved.duplicate(true)
row[&"stairs"][0][&"to"] = Vector3i(-1, 0, 1)
malformed.append(row)
row = saved.duplicate(true)
row[&"stairs"][0][&"cost"] = 100
malformed.append(row)
row = saved.duplicate(true)
row[&"stairs"].clear()
malformed.append(row)
for bad: Dictionary in malformed:
_expect(not restored.restore_snapshot(bad), "reject malformed or incomplete compatibility ownership")
_expect(restored.create_snapshot() == saved and restored_path.create_snapshot() == saved_path, "legacy snapshot rejection preserves metadata and Path exactly")
restored_path.configure_connector(&"legacy.stairs:orphan", cells[0], cells[1])
_expect(not restored.restore_snapshot(saved) and not restored.prune_invalid(), "untracked managed connector blocks restore and prune")
restored_path.remove_connector(&"legacy.stairs:orphan")
restored_path.configure_connector(&"legacy.stairs:0:-1:-1", cells[0], cells[1], 100, true, &"staff")
_expect(not restored.restore_snapshot(saved) and not restored.prune_invalid(), "owned connector permission tampering is rejected")
_expect(restored_path.restore_snapshot(saved_path), "repair exact connector configuration")
restored_path.set_vertical_walk_requires_connector(false)
_expect(not restored.restore_snapshot(saved), "current legacy snapshot cannot restore with free vertical walking")
var fresh_path := PathType.new()
fresh_path.rebuild_graph(cells)
fresh_path.set_vertical_walk_requires_connector(true)
var fresh := LegacyType.new()
fresh.configure(fresh_path)
_expect(fresh.migrate_existing_vertical_walks() and fresh.create_snapshot()[&"stairs"].is_empty(), "new strict game never gains migration stairs")
_expect(fresh.restore_snapshot({&"schema_version": 1, &"stairs": []}), "new game empty legacy snapshot is valid")
var out_of_bounds_path := PathType.new()
out_of_bounds_path.rebuild_graph([Vector3i(100_001, 0, 0), Vector3i(100_001, 1, 0)])
var out_of_bounds := LegacyType.new()
out_of_bounds.configure(out_of_bounds_path)
var out_of_bounds_before := out_of_bounds_path.create_snapshot()
_expect(not out_of_bounds.migrate_existing_vertical_walks() and out_of_bounds_path.create_snapshot() == out_of_bounds_before, "unsupported migration coordinates cannot create unrestorable records")
func _test_atomic_failure_and_reentry() -> void:
var path := ControlledPath.new()
var cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0)]
path.rebuild_graph(cells)
var legacy := LegacyType.new()
legacy.configure(path)
var before := path.create_snapshot()
var metadata := legacy.create_snapshot()
path.fail_restore_once = true
_expect(not legacy.migrate_existing_vertical_walks(), "injected partial Path apply failure rejects migration")
_expect(path.create_snapshot() == before and legacy.create_snapshot() == metadata, "failed migration restores Path and owned metadata exactly")
path.reenter = legacy
_expect(legacy.migrate_existing_vertical_walks() and path.nested_rejected, "outer migration succeeds while nested migration is rejected")
cells.erase(Vector3i(0, 1, 0))
path.rebuild_graph(cells)
before = path.create_snapshot()
metadata = legacy.create_snapshot()
path.fail_restore_once = true
_expect(not legacy.prune_invalid(), "injected partial prune failure rejects ownership removal")
_expect(path.create_snapshot() == before and legacy.create_snapshot() == metadata, "failed prune restores both dangling records and original Path for caller rollback")
_expect(legacy.prune_invalid(), "prune can retry after exact rollback")
var broken_path := ControlledPath.new()
broken_path.rebuild_graph([Vector3i(0, 0, 0), Vector3i(0, 1, 0)])
var broken := LegacyType.new()
broken.configure(broken_path)
broken_path.fail_restore_always = true
_expect(not broken.migrate_existing_vertical_walks(), "repeated rollback failure rejects migration")
_expect(broken.validate_invariants()[&"quarantined"] and not broken.migrate_existing_vertical_walks(), "uncertain rollback quarantines future mutation")
func _connector(snapshot: Dictionary, connector_id: StringName) -> Dictionary:
for connector: Dictionary in snapshot[&"connectors"]:
if StringName(connector[&"id"]) == connector_id:
return connector
return {}
func _expect(ok: bool, label: String) -> void:
_checks += 1
if not ok:
_failures += 1
push_error(label)
+1
View File
@@ -0,0 +1 @@
uid://c6pvi4t0vy41s
+120
View File
@@ -0,0 +1,120 @@
extends SceneTree
var _checks := 0
var _failures := 0
const ORIGIN := Vector3i.ZERO
const DESTINATION := Vector3i(0, 1, 0)
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_test_occupancy_lifecycle()
_test_projection_and_despawn()
_test_save_and_migration()
print("Path transit density: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
func _path() -> PathService:
var path := PathService.new()
path.rebuild_graph([ORIGIN, DESTINATION, Vector3i(1, 0, 0)])
path.configure_elevator(&"lift", ORIGIN, DESTINATION, {&"cabin_capacity": 4})
path.set_cell_base_capacity(ORIGIN, 1)
return path
func _queue(path: PathService, actor: StringName, id: StringName) -> void:
_expect(path.enqueue_elevator_request(&"lift", {&"request_id": id, &"unit_id": actor, &"floor_from": 0, &"floor_to": 1, &"enqueued_at": 10})[&"ok"], "elevator request registers: %s" % actor)
func _populate(path: PathService) -> void:
for id: StringName in [&"guest:a", &"guest:b", &"staff:c"]:
path.project_actor_position(id, ORIGIN, 10)
func _test_occupancy_lifecycle() -> void:
var path := _path()
_populate(path)
_expect(path.get_current_density(ORIGIN) == 3 and is_equal_approx(path.get_speed_multiplier(ORIGIN), 1.0 / 3.0), "all three floor actors initially contribute to congestion")
_queue(path, &"guest:a", &"ride.a")
_expect(path.get_current_density(ORIGIN) == 3 and path.get_transit_actor_ids().is_empty(), "queued passengers still occupy the floor")
var before := path.create_snapshot()
var graph_before := path.graph_revision
var boarded := path.dequeue_elevator_boarding(&"lift", 0, 11, 1)
_expect(boarded.size() == 1 and path.get_transit_actor_ids() == [&"guest:a"], "boarding derives transit occupancy from the existing Path owner")
_expect(path.get_current_density(ORIGIN) == 2 and is_equal_approx(path.get_speed_multiplier(ORIGIN), 0.5), "a boarded passenger no longer slows the remaining floor actors")
_expect(path.get_actor_projected_cell(&"guest:a") == ORIGIN and path.create_snapshot()[&"actor_progress"] == before[&"actor_progress"], "boarding retains logical origin and progress records")
_expect(path.graph_revision == graph_before and path.traffic_revision > before[&"traffic_revision"], "boarding changes traffic costs without changing structural topology")
var stable := path.create_snapshot()
_expect(path.dequeue_elevator_boarding(&"lift", 0, 11, 1).is_empty() and path.get_current_density(ORIGIN) == 2 \
and path.get_transit_actor_ids() == [&"guest:a"] and path.traffic_revision == stable[&"traffic_revision"], "repeated empty boarding does not change occupancy or traffic revision")
_expect(path.complete_elevator_request(&"lift", &"ride.a"), "completion releases the existing boarded owner")
_expect(path.get_current_density(ORIGIN) == 3 and not path.is_actor_in_transit(&"guest:a"), "completed trip restores the anchor's floor contribution before Runtime relocates it")
_expect(path.project_actor_position(&"guest:a", DESTINATION, 12), "Runtime arrival projects the actor at the real destination")
_expect(path.get_current_density(ORIGIN) == 2 and path.get_current_density(DESTINATION) == 1, "arrival moves exactly one floor occupant from origin to destination")
stable = path.create_snapshot()
_expect(not path.complete_elevator_request(&"lift", &"ride.a") and path.create_snapshot() == stable, "duplicate completion cannot release density twice")
_queue(path, &"guest:b", &"ride.b")
_expect(path.cancel_elevator_request(&"lift", &"ride.b") and path.get_current_density(ORIGIN) == 2, "cancelling a queued ride keeps its waiting actor on the floor")
func _test_projection_and_despawn() -> void:
var path := _path()
_populate(path)
_queue(path, &"staff:c", &"ride.c")
path.dequeue_elevator_boarding(&"lift", 0, 11, 1)
_expect(path.project_occupancy([{&"actor_id": &"guest:a", &"cell": ORIGIN}, {&"actor_id": &"staff:c", &"cell": ORIGIN}]), "batch projection retains existing boarded ownership")
_expect(path.get_current_density(ORIGIN) == 1 and path.get_transit_actor_ids() == [&"staff:c"], "batch occupancy excludes the passenger exactly once")
_expect(path.rebuild_graph([ORIGIN, DESTINATION, Vector3i(1, 0, 0)]) and path.get_current_density(ORIGIN) == 1, "topology refresh does not reintroduce cabin passengers into density")
var revision := path.traffic_revision
_expect(path.project_actor_position(&"staff:c", DESTINATION, 12), "transit projection can update its logical anchor without materializing an occupant")
_expect(path.get_current_density(ORIGIN) == 1 and path.get_current_density(DESTINATION) == 0 and path.traffic_revision == revision, "moving an excluded anchor changes no floor density or route costs")
_expect(path.despawn_actor(&"staff:c") and path.get_current_density(DESTINATION) == 0 and path.get_transit_actor_ids().is_empty(), "despawning a passenger does not decrement a zero-density floor")
_expect(path.complete_elevator_request(&"lift", &"ride.c") and path.get_current_density(DESTINATION) == 0, "later owner cleanup cannot resurrect a despawned passenger")
_queue(path, &"unprojected_unit", &"ride.unit")
path.dequeue_elevator_boarding(&"lift", 0, 13, 1)
_expect(path.get_transit_actor_ids().is_empty() and path.get_current_density(ORIGIN) == 1, "standalone elevator units without actor projections do not affect floor density")
_expect(path.project_actor_position(&"unprojected_unit", ORIGIN, 14) and path.is_actor_in_transit(&"unprojected_unit") and path.get_current_density(ORIGIN) == 1, "a projection added after boarding inherits its existing transit ownership")
_expect(path.actor_leave_cell(&"unprojected_unit", false) and not path.is_actor_in_transit(&"unprojected_unit"), "leaving the actor graph clears transit membership even when retaining progress")
func _test_save_and_migration() -> void:
var path := _path()
_populate(path)
_queue(path, &"guest:a", &"ride.a")
_queue(path, &"staff:c", &"ride.c")
path.dequeue_elevator_boarding(&"lift", 0, 11, 2)
var saved := path.create_snapshot()
_expect(saved[&"transit_actor_ids"] == [&"guest:a", &"staff:c"] and path.get_current_density(ORIGIN) == 1, "v5 saves canonical excluded actors and physical floor density")
var restored := _path()
_expect(restored.restore_snapshot(saved) and restored.create_snapshot() == saved, "boarded Path v5 snapshot restores exactly")
for raw: Variant in [null, [], ["missing"], ["guest:a", "guest:a"], ["staff:c", "guest:a"], [17, "staff:c"]]:
var bad := saved.duplicate(true)
bad[&"transit_actor_ids"] = raw
_expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == saved, "invalid transit ownership rejects atomically: %s" % str(raw))
var wrong_density := saved.duplicate(true)
for row: Dictionary in wrong_density[&"cell_traffic"]:
if row[&"cell"] == ORIGIN: row[&"current_density"] += 1
_expect(not restored.restore_snapshot(wrong_density) and restored.create_snapshot() == saved, "v5 cannot count an excluded passenger back into origin density")
var legacy := saved.duplicate(true)
legacy[&"schema_version"] = 4
legacy.erase(&"transit_actor_ids")
legacy.erase(&"vertical_walk_requires_connector")
_expect(not restored.restore_snapshot(legacy) and restored.create_snapshot() == saved, "legacy snapshots must first satisfy their original full-occupancy density rule")
for row: Dictionary in legacy[&"cell_traffic"]:
if row[&"cell"] == ORIGIN: row[&"current_density"] = 3
_expect(restored.restore_snapshot(legacy), "genuine v4 boarded save migrates")
var migrated := restored.create_snapshot()
_expect(restored.get_current_density(ORIGIN) == 1 and migrated[&"transit_actor_ids"] == saved[&"transit_actor_ids"], "legacy migration removes only projected boarded actors from density")
_expect(migrated[&"actor_cells"] == legacy[&"actor_cells"] and migrated[&"actor_progress"] == legacy[&"actor_progress"], "migration preserves every logical actor location and progress timestamp")
_expect(migrated[&"traffic_revision"] == legacy[&"traffic_revision"] + 1 and migrated[&"graph_revision"] == legacy[&"graph_revision"], "migration advances traffic revision once without rewriting topology")
_expect(restored.restore_snapshot(migrated) and restored.create_snapshot() == migrated, "canonical reload does not repeat legacy density subtraction")
var replay := _path()
_expect(replay.restore_snapshot(migrated), "a second runtime starts from the same boarded snapshot")
for service: PathService in [restored, replay]:
service.complete_elevator_request(&"lift", &"ride.a")
service.project_actor_position(&"guest:a", DESTINATION, 12)
service.complete_elevator_request(&"lift", &"ride.c")
service.project_actor_position(&"staff:c", DESTINATION, 12)
_expect(restored.create_snapshot() == replay.create_snapshot() and restored.get_current_density(DESTINATION) == 2, "arrival replay preserves exact ownership, density and timestamps after migration")
func _expect(ok: bool, label: String) -> void:
_checks += 1
if not ok:
_failures += 1
push_error(label)
@@ -0,0 +1 @@
uid://bwopfir6rudpp
+105
View File
@@ -0,0 +1,105 @@
extends SceneTree
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
_test_policy_and_connectors()
_test_recovery_and_diagnostics()
_test_snapshots()
print("Path vertical policy: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
func _path() -> PathService:
var path := PathService.new()
path.rebuild_graph([Vector3i.ZERO, Vector3i(1, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 1, 0), Vector3i(0, 0, 1), Vector3i(0, 1, 1)])
return path
func _test_policy_and_connectors() -> void:
var path := _path()
var bottom := Vector3i.ZERO
var top := Vector3i(0, 1, 0)
_expect(not path.get_vertical_walk_requires_connector(), "standalone Path retains legacy cardinal movement by default")
var old_route := path.find_path(bottom, top)
_expect(old_route == [bottom, top] and path.cached_path_count() == 1, "legacy upper-floor route can be cached")
var graph_before := path.graph_revision
_expect(path.set_vertical_walk_requires_connector(true), "strict vertical policy enables")
_expect(path.graph_revision == graph_before + 1 and path.cached_path_count() == 0, "policy change invalidates previously cached floor climbing")
_expect(path.find_path(bottom, top).is_empty() and path.last_no_route_reason == PathService.ROUTE_GOAL_UNREACHABLE, "adjacent floors require an explicit vertical connection")
_expect(path.calculate_path_cost(old_route) == -1 and path.get_edge_report(bottom, top).is_empty(), "cost and movement edge resolution cannot bypass strict routing")
_expect(path.find_path(bottom, Vector3i(1, 0, 0)).size() == 2, "horizontal walking remains available")
var raw_vertical := false
for edge: Dictionary in path.get_neighbors(bottom):
if edge[&"to"] == top and edge[&"movement_type"] == PathService.MOVEMENT_WALK: raw_vertical = true
_expect(raw_vertical, "raw structural neighbors remain available to legacy-stair migration")
var snapshot := path.create_snapshot()
_expect(path.set_vertical_walk_requires_connector(true) and path.create_snapshot() == snapshot, "reapplying the same policy is an exact no-op")
_expect(path.configure_connector(&"stairs", bottom, top, 150), "explicit public stairs register")
_expect(path.find_path(bottom, top) == [bottom, top] and path.get_step_connector(bottom, top) == &"stairs", "explicit stairs work through strict policy")
_expect(path.calculate_path_cost([bottom, top]) == 150, "stairs keep their defined movement cost")
path.set_connector_enabled(&"stairs", false)
_expect(path.find_path(bottom, top).is_empty() and path.last_no_route_reason == PathService.ROUTE_CONNECTOR_UNAVAILABLE, "closed stairs cannot fall back to implicit floor climbing")
path.configure_connector(&"service_stairs", Vector3i(0, 0, 1), Vector3i(0, 1, 1), 150, true, &"staff")
_expect(path.find_path(Vector3i(0, 0, 1), Vector3i(0, 1, 1), [1], []).is_empty(), "service stairs retain staff-only access")
_expect(path.find_path(Vector3i(0, 0, 1), Vector3i(0, 1, 1), [1], [&"staff"]).size() == 2, "authorized staff use explicit BACK stairs")
path.configure_elevator(&"lift", bottom, top)
_expect(path.get_step_connector(bottom, top) == &"lift", "an operational elevator remains an explicit vertical route")
path.set_connector_enabled(&"lift", false)
_expect(path.find_path(bottom, top).is_empty(), "a stopped lift never exposes an ordinary vertical edge")
path.set_vertical_walk_blockers(&"shaft", [bottom, top])
path.set_vertical_walk_requires_connector(false)
_expect(path.get_edge_report(bottom, top).is_empty(), "physical shaft blockers survive opting out of the global floor policy")
func _test_recovery_and_diagnostics() -> void:
var path := PathService.new()
var bottom := Vector3i.ZERO
var top := Vector3i(0, 1, 0)
path.rebuild_graph([bottom, top])
path.set_vertical_walk_requires_connector(true)
path.set_cell_temporary_block(bottom, true)
_expect(not path.find_nearest_walkable_recovery(bottom).get(&"found", false), "recovery cannot choose an unreachable floor solely because it is nearby")
path.configure_connector(&"stairs", bottom, top, 150)
_expect(path.find_nearest_walkable_recovery(bottom).get(&"cell") == top, "a real public stair makes the upper recovery proposal structurally reachable")
path.set_connector_enabled(&"stairs", false)
_expect(not path.find_nearest_walkable_recovery(bottom).get(&"found", false), "recovery respects an unavailable vertical connector")
path.set_cell_temporary_block(bottom, false)
path.remove_connector(&"stairs")
path.rebuild_graph([bottom, top, Vector3i(1, 0, 0), Vector3i(1, 1, 0)])
path.set_cell_temporary_block(Vector3i(1, 0, 0), true)
_expect(path.find_path(bottom, top).is_empty() and path.last_no_route_reason == PathService.ROUTE_GOAL_UNREACHABLE, "diagnostics do not invent a blocked walking route between floors")
path.set_vertical_walk_requires_connector(false)
_expect(path.find_path(bottom, top) == [bottom, top], "explicit legacy opt-out restores standalone vertical walking")
func _test_snapshots() -> void:
var path := _path()
path.set_vertical_walk_requires_connector(true)
path.set_vertical_walk_blockers(&"shaft", [Vector3i.ZERO, Vector3i(0, 1, 0)])
var saved := path.create_snapshot()
_expect(saved[&"schema_version"] == 5 and saved[&"vertical_walk_requires_connector"] == true and saved[&"transit_actor_ids"] == [], "Path v5 saves explicit policy and transit occupancy")
var restored := _path()
_expect(restored.restore_snapshot(saved) and restored.create_snapshot() == saved, "strict policy and shaft ownership restore exactly")
for value: Variant in [0, 1, "true", null]:
var bad := saved.duplicate(true)
bad[&"vertical_walk_requires_connector"] = value
_expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == saved, "non-bool vertical policy rejects atomically: %s" % str(value))
var missing := saved.duplicate(true)
missing.erase(&"vertical_walk_requires_connector")
_expect(not restored.restore_snapshot(missing), "v5 cannot silently default a missing policy")
var legacy := saved.duplicate(true)
legacy[&"schema_version"] = 4
_expect(not restored.restore_snapshot(legacy), "outer-only legacy downgrade cannot hide new policy fields")
legacy.erase(&"vertical_walk_requires_connector")
legacy.erase(&"transit_actor_ids")
_expect(restored.restore_snapshot(legacy) and not restored.get_vertical_walk_requires_connector(), "v4 defaults to its original vertical rules for Runtime-managed migration")
_expect(restored.get_vertical_walk_blockers() == path.get_vertical_walk_blockers(), "v4 keeps existing shaft ownership independently of v5 policy")
var migrated := restored.create_snapshot()
_expect(restored.restore_snapshot(migrated) and restored.create_snapshot() == migrated, "legacy migration is canonical and does not repeat on second restore")
func _expect(ok: bool, label: String) -> void:
_checks += 1
if not ok:
_failures += 1
push_error(label)
@@ -0,0 +1 @@
uid://bttq5agd5o82p
+203
View File
@@ -0,0 +1,203 @@
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 CatalogType = preload("res://scripts/data/facility_catalog.gd")
const RegistryType = preload("res://scripts/core/definition_registry.gd")
const GUEST = preload("res://data/facilities/infrastructure/guest_stairs.tres")
const SERVICE = preload("res://data/facilities/infrastructure/service_stairs.tres")
const CORRIDOR = preload("res://data/facilities/guest_corridor.tres")
class ControlledPath:
extends PathService
var fail_configure := false
var fail_remove := false
func configure_connector(connector_id: StringName, from: Vector3i, to: Vector3i, cost: int = 150, bidirectional: bool = true, access_id: StringName = &"", enabled: bool = true, options: Dictionary = {}) -> bool:
var result := super.configure_connector(connector_id, from, to, cost, bidirectional, access_id, enabled, options)
if fail_configure:
fail_configure = false
return false
return result
func remove_connector(connector_id: StringName) -> bool:
var result := super.remove_connector(connector_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_catalog_compatibility()
_test_guest_stairs_and_stacking()
_test_service_permissions()
_test_lower_landing_and_atomic_demolition()
_test_snapshot_and_schema_validation()
_test_injected_connector_failures()
print("Stair infrastructure: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
func _test_catalog_compatibility() -> void:
_expect(CatalogType.load_all().size() == 16 and CatalogType.FACILITY_PATHS.size() == 16, "baseline sixteen remains unchanged")
_expect(CatalogType.load_expansion().size() == 16 and CatalogType.EXPANSION_FACILITY_PATHS.size() == 16, "optional expansion sixteen remains unchanged")
_expect(CatalogType.load_available().size() == 16 and CatalogType.load_available(true).size() == 32, "existing catalog loading contract remains unchanged")
var infrastructure := CatalogType.load_infrastructure()
_expect(infrastructure.size() == 2 and CatalogType.INFRASTRUCTURE_FACILITY_IDS == [&"guest_stairs", &"service_stairs"], "required stair infrastructure is explicitly listed")
for definition: FacilityDefinition in infrastructure:
_expect(definition.is_valid(), "stair definition validates")
_expect(CatalogType.by_id(definition.id) == definition, "global stable ID lookup resolves stairs")
_expect(definition.footprint == Vector2i.ONE and definition.allowed_rotations == [0], "stairs use an unrotated upper landing")
_expect(definition.build_minutes == 10 and definition.maintenance_mana == 0 and definition.weight_per_cell == 1, "stairs preserve low-cost unpowered construction")
_expect(int(definition.construction_cost[&"materials"]) == 0 and definition.pass_through, "stairs require no materials and provide floor access")
_expect(GUEST.construction_cost[&"gold"] == 40 and SERVICE.construction_cost[&"gold"] == 50, "guest and service stair prices match approved plan")
var registry := RegistryType.new()
registry.rebuild()
_expect(registry.is_valid() and registry.facility_count() == 16, "default nonrecursive registry remains baseline compatible")
var required_paths: Array[String] = CatalogType.FACILITY_PATHS.duplicate()
required_paths.append_array(CatalogType.INFRASTRUCTURE_FACILITY_PATHS)
registry.rebuild_from_paths(required_paths)
_expect(registry.is_valid() and registry.facility_count() == 18, "Runtime can explicitly load all eighteen required facilities")
func _test_guest_stairs_and_stacking() -> void:
var bottom := Vector3i(0, 0, 0)
var middle := Vector3i(0, 1, 0)
var upper := Vector3i(0, 2, 0)
var context := _context([bottom, Vector3i(-1, 1, 0), Vector3i(1, 2, 0)])
_expect(context.projector.project_facility(&"build.first", GUEST, _instance(GUEST, &"first", middle)).is_success(), "guest stairs project onto existing lower landing")
_expect(context.source.get_entity_cells(&"infra.route:first") == [middle], "only the upper landing consumes ROUTE subslot")
var connector: Dictionary = context.path.get_connector(&"infra.connector:first")
_expect(connector[&"from"] == middle and connector[&"to"] == bottom and connector[&"cost"] == 150, "stair connector joins one floor at approved route cost")
_expect(connector[&"movement_type"] == PathType.MOVEMENT_CONNECTOR and connector[&"bidirectional"] and connector[&"access_id"] == &"", "guest stairs work in both directions without elevator dispatch")
_expect(context.path.find_path(bottom, Vector3i(-1, 1, 0), [0], []) == [bottom, middle, Vector3i(-1, 1, 0)], "strict floor policy still permits explicit guest stairs and floor exit")
_expect(context.path.find_path(middle, bottom, [0], []) == [middle, bottom], "descending stairs is valid")
_expect(context.projector.project_facility(&"build.second", GUEST, _instance(GUEST, &"second", upper)).is_success(), "stacked upper landings connect consecutive floors")
_expect(context.path.find_path(bottom, Vector3i(1, 2, 0)) == [bottom, middle, upper, Vector3i(1, 2, 0)], "two modules reach second floor without overlapping footprints")
_expect(context.path.get_vertical_walk_blockers().is_empty(), "stairs do not create elevator shaft blockers")
var queried_cells: Array[Vector3i] = [middle, bottom, bottom]
var dependents: Array[StringName] = context.projector.get_stair_dependents(queried_cells)
_expect(dependents == [&"first", &"second"], "lower-landing dependency query is unique and deterministic")
dependents.clear()
queried_cells.assign([bottom])
_expect(context.projector.get_stair_dependents(queried_cells) == [&"first"], "dependency report cannot mutate owned records")
_expect(context.projector.validate_invariants()[&"ok"], "stacked stair topology validates")
var before := _snapshots(context)
_expect(not context.projector.unproject_facility(&"remove.first", &"first").is_success(), "removing lower stair cannot strand upper stair")
_expect(_snapshots(context) == before, "dependent stair rejection restores all authoritative state")
_expect(context.projector.unproject_facility(&"remove.second", &"second").is_success(), "remove top stair first")
_expect(context.projector.unproject_facility(&"remove.first", &"first").is_success(), "lower stair demolition remains retryable after dependent removed")
_expect(context.path.get_connector(&"infra.connector:first").is_empty() and not context.path.has_cell(middle), "demolition removes connector and landing together")
func _test_service_permissions() -> void:
var bottom := Vector3i(0, 0, 1)
var upper := Vector3i(0, 1, 1)
var context := _context([bottom, Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 1, 1)])
_expect(context.projector.project_facility(&"build.service", SERVICE, _instance(SERVICE, &"service", upper)).is_success(), "service stairs project on BACK landings")
_expect(context.path.find_path(bottom, upper, [0, 1], []).is_empty(), "guest without staff permission cannot climb service stairs")
_expect(context.path.find_path(bottom, upper, [0, 1], [&"staff"]) == [bottom, upper], "staff can climb service stairs")
_expect(context.path.find_path(upper, bottom, [1], [&"staff"]) == [upper, bottom], "staff can descend service stairs")
_expect(context.path.find_path(Vector3i(0, 0, 0), upper, [0, 1], [&"staff"]).is_empty(), "service stairs do not invent a FRONT to BACK transfer")
_expect(context.path.find_path(upper, Vector3i(1, 1, 1), [1], []) == [upper, Vector3i(1, 1, 1)], "stair landing preserves existing same-floor geometry")
_expect(context.path.get_connector(&"infra.connector:service")[&"access_id"] == &"staff", "service access is stored in authoritative connector")
func _test_lower_landing_and_atomic_demolition() -> void:
var bottom := Vector3i(0, 0, 0)
var upper := Vector3i(0, 1, 0)
var context := _context([])
var before := _snapshots(context)
var stairs := _instance(GUEST, &"stairs", upper)
_expect(not context.projector.project_facility(&"build.stairs", GUEST, stairs).is_success(), "missing lower landing rejects construction projection")
_expect(_snapshots(context) == before, "missing landing failure does not consume request or create route")
_expect(context.projector.project_facility(&"build.lower", CORRIDOR, _instance(CORRIDOR, &"lower", bottom)).is_success(), "create lower corridor")
_expect(context.projector.project_facility(&"build.stairs", GUEST, stairs).is_success(), "same failed request can succeed after lower landing exists")
before = _snapshots(context)
_expect(not context.projector.unproject_facility(&"remove.lower", &"lower").is_success(), "direct projector demolition protects dependent stair landing")
_expect(_snapshots(context) == before, "lower corridor rejection has exact rollback")
_expect(context.adapter.remove_route(&"external.remove", &"infra.route:lower").is_success(), "external source mutation can remove landing outside projector boundary")
_expect(context.projector.is_projection_stale(), "externally missing lower landing invalidates stair crosslinks")
_expect(not context.projector.restore_snapshot(before.projector), "metadata restore cannot mask a missing lower landing")
_expect(context.source.restore_snapshot(before.source), "repair lower landing source")
_expect(context.path.rebuild_graph([bottom, upper]), "materialize repaired landing graph before snapshot restore")
_expect(context.path.restore_snapshot(before.path), "repair connector and floor policy")
_expect(context.adapter.restore_snapshot(before.adapter), "repair exact route projection")
_expect(context.projector.validate_invariants()[&"ok"], "landing repair restores valid crosslinks")
_expect(context.projector.unproject_facility(&"remove.stairs", &"stairs").is_success(), "remove dependent staircase")
_expect(context.projector.unproject_facility(&"remove.lower", &"lower").is_success(), "previously rejected lower demolition succeeds once safe")
var overlap := _context([bottom])
_expect(overlap.projector.project_facility(&"overlap.lower", CORRIDOR, _instance(CORRIDOR, &"lower", bottom)).is_success(), "project lower corridor over existing base landing")
_expect(overlap.projector.project_facility(&"overlap.stairs", GUEST, stairs).is_success(), "project stairs over overlapping lower source")
_expect(overlap.projector.unproject_facility(&"overlap.remove", &"lower").is_success(), "removing route is safe when base keeps lower landing")
_expect(overlap.projector.validate_invariants()[&"ok"] and overlap.path.find_path(bottom, upper) == [bottom, upper], "remaining base landing preserves usable stairs")
func _test_snapshot_and_schema_validation() -> void:
var bottom := Vector3i(0, 0, 0)
var context := _context([bottom])
var stairs := _instance(GUEST, &"stairs", Vector3i(0, 1, 0))
_expect(context.projector.project_facility(&"build", GUEST, stairs).is_success(), "create snapshot fixture")
var saved := _snapshots(context)
_expect(context.projector.restore_snapshot(saved.projector) and _snapshots(context) == saved, "snapshot roundtrip preserves exact connector and history")
for key: StringName in [&"connector_id", &"kind", &"definition_tags", &"rotation", &"footprint"]:
var malformed: Dictionary = saved.projector.duplicate(true)
match key:
&"connector_id": malformed[&"projections"][0][key] = ""
&"kind": malformed[&"projections"][0][key] = "guest_corridor"
&"definition_tags": malformed[&"projections"][0][key] = ["movement"]
&"rotation": malformed[&"projections"][0][key] = 90
&"footprint": malformed[&"projections"][0][key] = Vector2i(1, 2)
_expect(not context.projector.restore_snapshot(malformed), "reject malformed stair record %s" % key)
_expect(_snapshots(context) == saved, "malformed stair restore is atomic")
var changed_definition := GUEST.duplicate(true) as FacilityDefinition
changed_definition.allowed_rotations = [0, 90]
_expect(not context.projector.project_facility(&"bad.rotation", changed_definition, _instance(changed_definition, &"other", Vector3i(0, 1, 0))).is_success(), "definition cannot widen fixed stair orientation")
var rotated := _instance(GUEST, &"rotated", Vector3i(0, 1, 0))
rotated[&"rotation"] = 90
_expect(not context.projector.project_facility(&"bad.instance.rotation", GUEST, rotated).is_success(), "completed stair cannot use rotation with unchanged footprint")
_expect(context.path.configure_connector(&"infra.connector:orphan", bottom, Vector3i(0, 1, 0)), "create untracked managed connector fixture")
_expect(context.projector.is_projection_stale() and not context.projector.restore_snapshot(saved.projector), "managed orphan connector cannot hide behind valid stair metadata")
_expect(context.path.remove_connector(&"infra.connector:orphan"), "remove orphan")
_expect(context.path.configure_connector(&"legacy.stairs:external", bottom, Vector3i(0, 1, 0), 100, true, &"permit"), "create separately owned legacy connector")
_expect(context.projector.validate_invariants()[&"ok"], "external connector ownership and permissions remain independent")
_expect(context.projector.unproject_facility(&"remove", &"stairs").is_success(), "remove owned stairs without removing external connector")
_expect(context.path.has_connector(&"legacy.stairs:external") and context.path.get_connector(&"legacy.stairs:external")[&"access_id"] == &"permit", "demolition preserves external connector details")
func _test_injected_connector_failures() -> void:
var controlled := ControlledPath.new()
var context := _context([Vector3i(0, 0, 0)], controlled)
var stairs := _instance(GUEST, &"stairs", Vector3i(0, 1, 0))
var before := _snapshots(context)
controlled.fail_configure = true
_expect(not context.projector.project_facility(&"build", GUEST, stairs).is_success(), "failed connector write rejects whole projection")
_expect(_snapshots(context) == before, "connector write failure rolls back source, route, path and metadata")
_expect(context.projector.project_facility(&"build", GUEST, stairs).is_success(), "failed projection request remains retryable")
before = _snapshots(context)
controlled.fail_remove = true
_expect(not context.projector.unproject_facility(&"remove", &"stairs").is_success(), "failed connector removal rejects whole demolition")
_expect(_snapshots(context) == before, "connector removal failure restores complete staircase")
_expect(context.projector.unproject_facility(&"remove", &"stairs").is_success(), "failed demolition request remains retryable")
func _context(base_cells: Array[Vector3i], path_value: PathService = null) -> Dictionary:
var source := SpatialType.new()
var path := path_value if path_value != null else PathType.new()
_expect(path.set_vertical_walk_requires_connector(true), "enable explicit vertical connectors")
var adapter := AdapterType.new()
_expect(adapter.configure(source, path, base_cells), "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:
return {&"instance_id": instance_id, &"definition_id": definition.id, &"origin": origin, &"rotation": 0, &"layer": definition.layer, &"occupied_cells": [origin], &"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://cw0y2pyvn04ob