Files
TheTree_mock/tests/s3/test_runtime_actor_cycle.gd
T
Takeshi b84bade045 Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
2026-10-11 15:58:00 +09:00

247 lines
11 KiB
GDScript

extends SceneTree
var _checks := 0
var _failures := 0
var _clock: Variant
var _state: Variant
var _runtime: Variant
var _facility_id: StringName
var _facility_inventory_id: StringName
var _kitchen_fixture_gold := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("S3: integrated Guest and Staff runtime cycle")
_reset_runtime()
_build_cafeteria()
_register_staff_and_run_task()
_run_autonomous_guest_service_with_replay()
_run_long_horizon_replay()
_cleanup()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
quit(_failures)
func _reset_runtime() -> void:
_cleanup()
_clock = load("res://autoload/game_clock.gd").new()
_state = load("res://autoload/game_state.gd").new()
_runtime = load("res://autoload/simulation_runtime.gd").new()
_clock.reset(1, 6, 0)
_state.reset()
var report: Dictionary = _runtime.initialize(_clock, _state, 3303)
_kitchen_fixture_gold = 0
_clock.set_paused(true)
_expect(report[&"valid"], "runtime initializes S3 actor services")
_expect(_runtime.guest_service.get_definitions().size() == 35, "runtime preserves three legacy archetypes and registers 32 MVP catalog archetypes")
_expect(_runtime.guest_service.current_minute == _clock.absolute_minute, "Guest cadence starts aligned to the kernel")
_expect(_runtime.staff_service.current_minute == _clock.absolute_minute, "Staff cadence starts aligned to the kernel")
func _cleanup() -> void:
if _runtime != null:
_runtime.free()
if _clock != null:
_clock.free()
if _state != null:
_state.free()
_runtime = null
_clock = null
_state = null
func _build_cafeteria() -> void:
var build = _runtime.submit_command(
&"build_facility",
{&"definition_id": &"cafeteria", &"origin": Vector3i(-5, 0, WorldService.LAYER_FRONT)},
&"player", &"", 50, -1, &"s3.runtime.build"
)
_expect(build.is_success(), "cafeteria construction command succeeds")
_clock.advance_minutes(180)
var facilities: Array[Dictionary] = _runtime.construction_service.get_facilities()
_expect(facilities.size() == 1, "construction completes before actor integration")
_facility_id = StringName(facilities[0][&"instance_id"])
var facility = _runtime.facility_service.get_instance(_facility_id)
_facility_inventory_id = facility.local_inventory_id
_expect(facility.state == FacilityInstance.State.NO_STAFF, "unassigned S3 facility starts in NO_STAFF")
var kitchen_setup: Dictionary = load("res://tests/fixtures/required_kitchen_setup.gd").ensure(_runtime, _clock, _facility_id)
_expect(bool(kitchen_setup.get(&"ok", false)), "public paid kitchen setup/link with actual FLEX shift for S3")
_kitchen_fixture_gold = int(kitchen_setup.get(&"gold_cost", -1))
_expect(_kitchen_fixture_gold > 0 and _runtime.economy_service.get_balance(&"CONSTRUCTION_EXPENSE") == 800 + _kitchen_fixture_gold and _runtime.economy_service.get_balance(&"PLAYER_CASH") == 4200 - _kitchen_fixture_gold and _state.get_resource(&"gold") == 4200 - _kitchen_fixture_gold, "actual additional paid kitchen/door cost matches both ledger accounts and Gold mirror")
func _register_staff_and_run_task() -> void:
var common_options := {
&"current_cell": Vector3i(0, 0, WorldService.LAYER_FRONT),
&"state": StaffRuntime.State.OFF_DUTY,
&"permitted_task_tags": [&"service", &"work"],
&"equipment_tags": [&"uniform"],
}
var cook_options: Dictionary = common_options.duplicate(true)
cook_options[&"role_tags"] = [&"cook"]
cook_options[&"skills"] = {&"cooking": 80, &"operation": 30}
var server_options: Dictionary = common_options.duplicate(true)
server_options[&"role_tags"] = [&"server"]
server_options[&"skills"] = {&"service": 75, &"operation": 20}
var cook = _runtime.submit_command(
&"register_staff",
{&"staff_id": &"cook_aya", &"role_id": &"cook", &"options": cook_options},
&"manager", &"cook_aya", 20, -1, &"s3.runtime.staff.cook"
)
var server = _runtime.submit_command(
&"register_staff",
{&"staff_id": &"server_ren", &"role_id": &"server", &"options": server_options},
&"manager", &"server_ren", 20, -1, &"s3.runtime.staff.server"
)
_expect(cook.is_success() and server.is_success(), "two StaffRuntime records register through commands")
var start_minute := posmod(_clock.absolute_minute, 1_440)
for staff_id: StringName in [&"cook_aya", &"server_ren"]:
var shift = _runtime.submit_command(
&"create_staff_shift",
{
&"staff_id": staff_id,
&"template_id": &"FLEX",
&"start_day": _clock.absolute_minute / 1_440,
&"start_minute_of_day": start_minute,
&"end_minute_of_day": start_minute + 600,
&"facility_id": _facility_id,
},
&"manager", staff_id, 20, -1, StringName("s3.runtime.shift.%s" % staff_id)
)
_expect(shift.is_success(), "Flex Shift registers for %s" % staff_id)
_clock.advance_one_minute()
var facility = _runtime.facility_service.get_instance(_facility_id)
_expect(facility.active_staff_ids == [&"cook_aya", &"server_ren"], "on-duty Staff project into FacilityService in stable ID order")
_expect(facility.state == FacilityInstance.State.OPEN, "required S3 staffing opens the cafeteria")
var tool = _runtime.submit_command(
&"add_inventory_stack",
{
&"owner_inventory_id": _facility_inventory_id,
&"item_id": &"tool/cleaning_kit",
&"quantity": 1,
},
&"manager", _facility_id, 10, -1, &"s3.runtime.tool"
)
_expect(tool.is_success(), "facility receives a reservable Staff task tool")
var task_result = _runtime.submit_command(
&"create_staff_task",
{
&"task_id": &"clean_cafeteria",
&"task_type": &"clean",
&"priority": 70,
&"options": {
&"target_entity_id": _facility_id,
&"target_cell": Vector3i(-1, 0, WorldService.LAYER_FRONT),
&"required_role_tags": [&"server"],
&"required_skill_min": {&"service": 10},
&"required_items": {&"tool/cleaning_kit": 1},
&"task_tags": [&"service"],
&"estimated_minutes": 2,
},
},
&"manager", _facility_id, 10, -1, &"s3.runtime.task"
)
_expect(task_result.is_success(), "Staff task enters the runtime dispatcher")
_clock.advance_minutes(8)
var task = _runtime.staff_service.get_task(&"clean_cafeteria")
_expect(task.state == StaffTask.State.COMPLETED, "runtime automatically dispatches, begins and completes the Staff task")
_expect(_runtime.inventory_service.available_quantity(_facility_inventory_id, &"tool/cleaning_kit") == 1, "completed task releases its atomic tool Reservation")
_expect(_runtime.staff_service.validate_invariants()[&"ok"], "integrated Staff task graph preserves bidirectional invariants")
func _run_autonomous_guest_service_with_replay() -> void:
var ingredient = _runtime.submit_command(
&"add_inventory_stack",
{
&"owner_inventory_id": _facility_inventory_id,
&"item_id": &"ingredient/basic_meal",
&"quantity": 1,
&"quality": 80,
},
&"manager", _facility_id, 10, -1, &"s3.runtime.ingredient"
)
_expect(ingredient.is_success(), "one service recipe is stocked")
var spawn = _runtime.submit_command(
&"spawn_guest",
{
&"guest_id": &"guest_mio",
&"guest_type_id": &"traveler",
&"gold": 150,
&"options": {
&"current_cell": Vector3i(0, 0, WorldService.LAYER_FRONT),
&"needs": {&"hunger": 90, &"sleep": 20, &"hygiene": 20, &"medical": 0, &"leisure": 20, &"intimacy": 0},
&"planned_exit_at": _clock.absolute_minute + 240,
},
},
&"world", &"guest_mio", 10, -1, &"s3.runtime.guest"
)
_expect(spawn.is_success(), "spawn_guest atomically creates GuestRuntime and ledger wallet")
_expect(_runtime.economy_service.get_balance(&"GUEST_WALLET:guest_mio") == 150, "Guest wallet is authoritative in EconomyService")
var active_service: ServiceInstance = null
for _minute in range(10):
_clock.advance_one_minute()
var active: Array = _runtime.facility_service.get_active_services(_facility_id)
if not active.is_empty():
active_service = active[0]
break
_expect(active_service != null, "autonomous Guest reaches the facility queue and starts service")
if active_service == null:
return
var guest = _runtime.guest_service.get_guest(&"guest_mio")
_expect(guest.state == GuestRuntime.State.RECEIVING_SERVICE, "Facility service start atomically updates Guest state")
_expect(guest.current_service_id == active_service.service_id, "Guest and Facility reference the same active service")
var mid_snapshot: Dictionary = _runtime.create_snapshot()
var mid_digest := SimulationDigest.compute(mid_snapshot)
var duration := active_service.duration_minutes
_clock.advance_minutes(duration)
var completed = _runtime.facility_service.get_service(active_service.service_id)
guest = _runtime.guest_service.get_guest(&"guest_mio")
_expect(completed != null and completed.state == ServiceInstance.State.COMPLETED, "service completes after its immutable duration")
_expect(guest != null and guest.satisfaction_samples == 1, "completion records exactly one Guest satisfaction sample")
_expect(guest != null and int(guest.needs[&"hunger"]) < 40, "cafeteria completion applies its Need relief")
_expect(_runtime.economy_service.get_balance(&"GUEST_WALLET:guest_mio") == 125, "Guest pays the quoted service price once")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4225 - _kitchen_fixture_gold, "service revenue reaches authoritative player cash")
_expect(_runtime.inventory_service.total_quantity(_facility_inventory_id, &"ingredient/basic_meal") == 0, "service consumes exactly one reserved recipe input")
var final_digest: String = _runtime.latest_digest
_expect(_runtime.restore_snapshot(mid_snapshot), "mid-service S3 snapshot restores")
_expect(_runtime.latest_digest == mid_digest, "S3 restore reproduces the exact mid-service digest")
_clock.advance_minutes(duration)
_expect(_runtime.latest_digest == final_digest, "replayed Guest, Staff, ledger and event transitions reproduce exactly")
_expect(_runtime.economy_service.get_entries().filter(
func(entry: LedgerEntry) -> bool: return entry.category == &"SERVICE_SALE"
).size() == 1, "replay records the service sale exactly once")
func _run_long_horizon_replay() -> void:
var horizon_snapshot: Dictionary = _runtime.create_snapshot()
_clock.advance_minutes(1_440)
var first_digest: String = _runtime.latest_digest
var first_snapshot: Dictionary = _runtime.create_snapshot()
_expect(_runtime.guest_service.validate_invariants()[&"valid"], "24-hour Guest graph has no orphan or illegal state")
_expect(_runtime.staff_service.validate_invariants()[&"ok"], "24-hour Staff graph has no orphan reservation")
_expect(_runtime.inventory_service.validate_invariants()[&"ok"], "24-hour actor simulation preserves Inventory invariants")
_expect(_runtime.economy_service.validate_ledger()[&"ok"], "24-hour actor simulation keeps the ledger balanced")
_expect(_runtime.restore_snapshot(horizon_snapshot), "long-horizon starting point restores")
_clock.advance_minutes(1_440)
_expect(_runtime.latest_digest == first_digest, "24-hour actor simulation is deterministic after restore")
_expect(_runtime.create_snapshot() == first_snapshot, "24-hour replay reproduces the complete authoritative snapshot")
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
print(" ✓ %s" % label)
return
_failures += 1
push_error(" ✗ %s" % label)