Record original-alignment implementation and traceability checkpoint
Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
extends SceneTree
|
||||
|
||||
# Regression for the observed midpoint restore order difference. No environment variables required.
|
||||
# Full Runtime saved domain-event history preservation must be verified separately by strict whole-state replay.
|
||||
const GuestDefinitionType = preload("res://scripts/guest/guest_definition.gd")
|
||||
const GuestRuntimeType = preload("res://scripts/guest/guest_runtime.gd")
|
||||
const GuestServiceType = preload("res://scripts/guest/guest_service.gd")
|
||||
const EXPECTED_NEEDS: Array[StringName] = [
|
||||
&"hunger", &"sleep", &"hygiene", &"medical", &"leisure", &"intimacy",
|
||||
]
|
||||
const VALUES := {&"hunger": 90, &"sleep": 90, &"hygiene": 10, &"medical": 20,
|
||||
&"leisure": 90, &"intimacy": 90}
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
print("Guest observation restore-order regression")
|
||||
var reversed_order := EXPECTED_NEEDS.duplicate()
|
||||
reversed_order.reverse()
|
||||
var reference_observation := PackedByteArray()
|
||||
var reference_decision := PackedByteArray()
|
||||
for index: int in range(2):
|
||||
var order: Array = EXPECTED_NEEDS if index == 0 else reversed_order
|
||||
var input: Dictionary = {}
|
||||
for position: int in range(order.size()):
|
||||
var need: StringName = order[position]
|
||||
var key: Variant = String(need) if index == 1 and position % 2 == 0 else need
|
||||
input[key] = VALUES[need]
|
||||
var service := _service(input)
|
||||
var guest := service.get_guest(&"observation_guest")
|
||||
if guest == null:
|
||||
_expect(false, "valid guest fixture registers")
|
||||
continue
|
||||
var raw_before := var_to_bytes(guest.needs)
|
||||
var snapshot := service.create_snapshot()
|
||||
var snapshot_bytes := var_to_bytes(snapshot)
|
||||
var observation := service.get_observation(guest.guest_id)
|
||||
_expect(observation[&"needs"].keys() == EXPECTED_NEEDS, "public needs order is stable for input %d" % index)
|
||||
for need: Variant in observation[&"needs"]:
|
||||
_expect(typeof(need) == TYPE_STRING_NAME and typeof(observation[&"needs"][need]) == TYPE_INT and observation[&"needs"][need] == VALUES[need],
|
||||
"public need preserves StringName key, int type and exact value")
|
||||
_expect(var_to_bytes(guest.needs) == raw_before, "observation preserves model input order and values")
|
||||
_expect(var_to_bytes(service.create_snapshot()) == snapshot_bytes, "observation preserves complete service snapshot")
|
||||
_expect(observation[&"primary_needs"] == [&"hunger", &"sleep", &"leisure", &"intimacy"], "all maximum ties retain PRIMARY_NEEDS order")
|
||||
var observation_bytes := var_to_bytes(observation)
|
||||
if index == 0:
|
||||
reference_observation = observation_bytes
|
||||
_expect(observation_bytes == reference_observation, "typed observation agrees across source order and key types")
|
||||
observation[&"needs"][&"hunger"] = 0
|
||||
observation[&"primary_needs"].clear()
|
||||
_expect(var_to_bytes(guest.needs) == raw_before and var_to_bytes(service.create_snapshot()) == snapshot_bytes,
|
||||
"caller mutation cannot change live model or saved state")
|
||||
var restored := GuestServiceType.new()
|
||||
_expect(restored.restore_snapshot(snapshot), "complete service snapshot restores")
|
||||
var resumed := restored.get_guest(guest.guest_id)
|
||||
if resumed == null:
|
||||
_expect(false, "restored guest remains present")
|
||||
continue
|
||||
var saved_model: Dictionary = snapshot[&"guests"][0][&"needs"]
|
||||
var restored_order: Array[StringName] = []
|
||||
for key: Variant in saved_model:
|
||||
restored_order.append(StringName(key))
|
||||
_expect(resumed.needs.keys() == restored_order, "restore retains the snapshot input order, without model normalization")
|
||||
var resumed_before := var_to_bytes(resumed.needs)
|
||||
_expect(var_to_bytes(restored.get_observation(guest.guest_id)) == observation_bytes, "before/after restore typed observation is exact")
|
||||
_expect(var_to_bytes(resumed.needs) == resumed_before and var_to_bytes(restored.create_snapshot()) == snapshot_bytes,
|
||||
"restored observation does not mutate model or save serialization")
|
||||
var food := _candidate(&"aa_food", &"hunger")
|
||||
var room := _candidate(&"zz_room", &"sleep")
|
||||
var food_score := service.evaluate_candidate(guest.guest_id, food)
|
||||
var room_score := service.evaluate_candidate(guest.guest_id, room)
|
||||
for field: StringName in [&"need_priority", &"utility", &"urgency", &"travel_minutes"]:
|
||||
_expect(food_score[field] == room_score[field], "fixture reaches the final facility ID tie break: " + String(field))
|
||||
var uninterrupted := service.decide(guest.guest_id, [room, food], 1)
|
||||
var replay := restored.decide(guest.guest_id, [food, room], 1)
|
||||
_expect(bool(uninterrupted.get(&"ok", false)) and uninterrupted.get(&"candidate", {}).get(&"facility_id", &"") == &"aa_food",
|
||||
"equal need scores use the stable lexical facility ID")
|
||||
_expect(var_to_bytes(uninterrupted) == var_to_bytes(replay), "restored decision and reversed candidates agree with all typed result fields")
|
||||
_expect(var_to_bytes(service.create_snapshot()) == var_to_bytes(restored.create_snapshot()), "complete post-decision service snapshots agree")
|
||||
if index == 0:
|
||||
reference_decision = var_to_bytes(uninterrupted)
|
||||
_expect(var_to_bytes(uninterrupted) == reference_decision, "all source orders and key types preserve the full decision")
|
||||
print("Guest observation restore-order diagnostic: %d checks, %d failures" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
|
||||
func _service(needs: Dictionary) -> GuestService:
|
||||
var service := GuestServiceType.new()
|
||||
var definition := GuestDefinitionType.new().configure(&"observation_type", {
|
||||
&"wealth_min": 1000, &"wealth_max": 1000, &"minimum_quality": 0,
|
||||
&"risk_tolerance": 100, &"legal_sensitivity": 0,
|
||||
})
|
||||
_expect(service.register_definition(definition), "valid definition registers")
|
||||
var guest := service.spawn_guest(&"observation_guest", &"observation_type", 0, {
|
||||
&"state": GuestRuntimeType.State.DECIDING, &"wallet_gold": 1000,
|
||||
&"planned_exit_at": 1000, &"needs": needs,
|
||||
})
|
||||
_expect(guest != null, "valid guest spawns")
|
||||
return service
|
||||
|
||||
func _candidate(id: StringName, need: StringName) -> Dictionary:
|
||||
return {&"facility_id": id, &"is_open": true, &"reachable": true,
|
||||
&"price_gold": 10, &"wait_minutes": 0, &"travel_minutes": 5,
|
||||
&"risk_level": 0, &"illegal_level": 0, &"expected_quality": 100,
|
||||
&"need_relief": {need: 80}}
|
||||
|
||||
func _expect(condition: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not condition:
|
||||
failures += 1
|
||||
push_error(label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://tdsph6wcj85t
|
||||
@@ -21,6 +21,7 @@ func _run() -> void:
|
||||
_test_batched_need_updates_match_individual_api()
|
||||
_test_candidate_filtering()
|
||||
_test_integer_utility_and_stable_tie_break()
|
||||
_test_primary_need_priority()
|
||||
_test_state_machine_and_loop_guard()
|
||||
_test_atomic_queue_abandonment()
|
||||
_test_service_interruption_is_idempotent()
|
||||
@@ -776,3 +777,54 @@ func _expect(condition: bool, label: String) -> void:
|
||||
return
|
||||
_failures += 1
|
||||
push_error(" ✗ %s" % label)
|
||||
|
||||
|
||||
func _test_primary_need_priority() -> void:
|
||||
var service := _service_with_definitions()
|
||||
var guest := service.spawn_guest(&"need_priority", &"adventurer", 0, {
|
||||
&"wallet_gold": 1000, &"planned_exit_at": 1000,
|
||||
&"state": GuestRuntime.State.DECIDING,
|
||||
&"needs": {&"hunger": 90, &"sleep": 70},
|
||||
})
|
||||
var food := _candidate(&"food", {&"need_relief": {&"hunger": 80}, &"expected_quality": 40})
|
||||
var room := _candidate(&"room", {&"need_relief": {&"sleep": 90}, &"expected_quality": 100, &"preference": 100})
|
||||
_expect(service.evaluate_candidate(guest.guest_id, room)[&"utility"] > service.evaluate_candidate(guest.guest_id, food)[&"utility"], "regression scenario has a tempting lower-need facility")
|
||||
_expect(service.select_best_candidate(guest.guest_id, [room,food])[&"facility_id"] == &"food", "highest need precedes quality and preference bonuses")
|
||||
var near_food := food.duplicate(true)
|
||||
near_food[&"facility_id"] = &"near_food"
|
||||
near_food[&"travel_minutes"] = 1
|
||||
food[&"travel_minutes"] = 20
|
||||
_expect(service.select_best_candidate(guest.guest_id, [food,near_food])[&"facility_id"] == &"near_food", "same need still compares distance")
|
||||
for field: StringName in [&"wait_minutes", &"price_burden"]:
|
||||
var costly := near_food.duplicate(true)
|
||||
costly[&"facility_id"] = &"costly"
|
||||
costly[field] = 100
|
||||
_expect(service.select_best_candidate(guest.guest_id, [costly,near_food])[&"facility_id"] == &"near_food", "same need still compares queue or price")
|
||||
var unavailable := food.duplicate(true)
|
||||
unavailable[&"reachable"] = false
|
||||
var accepted := service.extract_candidates(guest.guest_id, [unavailable,room])
|
||||
_expect(service.select_best_candidate(guest.guest_id, accepted)[&"facility_id"] == &"room", "unreachable highest-need facility does not block a usable alternative")
|
||||
unavailable[&"reachable"] = true
|
||||
unavailable[&"price_gold"] = 100000
|
||||
accepted = service.extract_candidates(guest.guest_id, [unavailable,room])
|
||||
_expect(service.select_best_candidate(guest.guest_id, accepted)[&"facility_id"] == &"room", "highest need does not bypass affordability")
|
||||
var unattractive := food.duplicate(true)
|
||||
unattractive[&"travel_minutes"] = 10000
|
||||
_expect(service.select_best_candidate(guest.guest_id, [unattractive,room])[&"facility_id"] == &"room", "below-threshold high-need candidate does not stop fallback search")
|
||||
guest.needs[&"sleep"] = 90
|
||||
_expect(service.select_best_candidate(guest.guest_id, [food,room])[&"facility_id"] == &"room", "equal highest needs use the existing utility comparison")
|
||||
guest.needs[&"sleep"] = 70
|
||||
var before := service.create_snapshot()
|
||||
var observed := service.get_observation(guest.guest_id)
|
||||
observed[&"needs"][&"hunger"] = 0
|
||||
_expect(guest.needs[&"hunger"] == 90 and service.create_snapshot() == before, "observation is read-only and owns its dictionaries")
|
||||
_expect(service.restore_snapshot(before), "pre-decision save restores")
|
||||
var replay := service.decide(guest.guest_id, [room,food], 1)
|
||||
_expect(replay[&"candidate"][&"facility_id"] == &"food", "restored runtime uses the same highest-need decision")
|
||||
var after := service.create_snapshot()
|
||||
_expect(service.restore_snapshot(before), "decision can be replayed from snapshot")
|
||||
service.decide(guest.guest_id, [food,room], 1)
|
||||
_expect(service.create_snapshot() == after, "candidate insertion order cannot change saved decision state")
|
||||
_expect(service.restore_snapshot(before), "reservation retry starts at the same state")
|
||||
var reserved := service.decide(guest.guest_id, [food,room], 1, {&"reserve_callback": func(_id: StringName, facility: StringName, _candidate: Dictionary) -> bool: return facility != &"food"})
|
||||
_expect(reserved[&"candidate"][&"facility_id"] == &"room", "failed reservation tries the next usable candidate")
|
||||
|
||||
@@ -150,12 +150,12 @@ func _test_congestion_credit_and_alternate_route() -> void:
|
||||
var after_one: Array[Vector3i] = _guest_cells_with_prefix("slow_")
|
||||
_expect(after_one.all(func(cell: Vector3i) -> bool: return cell == origin), "over-capacity speed credit prevents a first-minute burst")
|
||||
_expect(
|
||||
int(_runtime.get_actor_movement_credit(&"guest:slow_00").get(&"credit", -1)) == 250,
|
||||
int(_runtime.get_actor_movement_credit(&"guest:slow_00").get(&"credit", -1)) == 500,
|
||||
"Path speed multiplier is stored as deterministic fixed-point credit"
|
||||
)
|
||||
_clock.advance_minutes(3)
|
||||
var after_four: Array[Vector3i] = _guest_cells_with_prefix("slow_")
|
||||
_expect(after_four.all(func(cell: Vector3i) -> bool: return cell != origin), "minimum speed guarantees progress by the fourth congested minute")
|
||||
_clock.advance_minutes(1)
|
||||
var after_two: Array[Vector3i] = _guest_cells_with_prefix("slow_")
|
||||
_expect(after_two.all(func(cell: Vector3i) -> bool: return cell != origin), "half speed guarantees progress by the second congested minute")
|
||||
_expect(_runtime.path_service.get_current_density(origin) == 0, "sequential committed movement lowers source density exactly")
|
||||
|
||||
_reset_runtime(8_103)
|
||||
@@ -318,7 +318,7 @@ 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"]) == 9, "Runtime snapshot advertises schema v9")
|
||||
_expect(int(runtime_data[&"runtime_schema_version"]) == 13, "Runtime snapshot advertises schema v13")
|
||||
_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 v9 snapshot restores")
|
||||
@@ -358,6 +358,7 @@ func _test_current_snapshot_crosslinks_and_legacy_v2() -> void:
|
||||
var legacy_data: Dictionary = legacy_v2[&"systems"][&"runtime"]
|
||||
legacy_data[&"runtime_schema_version"] = 2
|
||||
_strip_post_v2_runtime_state(legacy_data)
|
||||
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(legacy_data)
|
||||
legacy_data[&"path"][&"schema_version"] = 3
|
||||
legacy_data[&"path"].erase(&"vertical_walk_blockers")
|
||||
legacy_data[&"path"].erase(&"vertical_walk_requires_connector")
|
||||
@@ -375,7 +376,7 @@ func _test_current_snapshot_crosslinks_and_legacy_v2() -> void:
|
||||
_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"]) == 9
|
||||
int(migrated[&"systems"][&"runtime"][&"runtime_schema_version"]) == 13
|
||||
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"]),
|
||||
|
||||
@@ -29,6 +29,7 @@ func _run() -> void:
|
||||
_test_automatic_task_progression_once_per_tick()
|
||||
_test_employment_and_daily_payroll_contract()
|
||||
_test_retirement_preserves_unpaid_wages()
|
||||
_test_unique_employment_protection()
|
||||
_test_daily_payroll_settlement_lifecycle()
|
||||
_test_payroll_validation_and_idempotency()
|
||||
_test_payroll_snapshot_migration_and_tamper()
|
||||
@@ -384,11 +385,11 @@ func _test_employment_and_daily_payroll_contract() -> void:
|
||||
)
|
||||
var expected_rows: Array[Dictionary] = [
|
||||
{&"staff_id": &"staff_a", &"employment_kind": &"ADULT_CONTRACT", &"daily_wage": 240},
|
||||
{&"staff_id": &"staff_m", &"employment_kind": &"UNIQUE", &"daily_wage": 75},
|
||||
{&"staff_id": &"staff_m", &"employment_kind": &"UNIQUE", &"daily_wage": 0},
|
||||
{&"staff_id": &"staff_z", &"employment_kind": &"FREE_HIRE", &"daily_wage": 0},
|
||||
]
|
||||
_expect(service.get_daily_payroll_rows() == expected_rows, "daily payroll rows have an exact shape and lexical Staff order")
|
||||
_expect(service.get_daily_payroll_total() == 315, "daily payroll total is the exact sum of active Staff wages")
|
||||
_expect(service.get_daily_payroll_total() == 240, "daily payroll total is the exact sum of active Staff wages")
|
||||
|
||||
var invalid_service := StaffServiceType.new()
|
||||
_expect(
|
||||
@@ -828,3 +829,24 @@ func _expect(condition: bool, label: String) -> void:
|
||||
return
|
||||
_failures += 1
|
||||
push_error(" ✗ %s" % label)
|
||||
|
||||
|
||||
func _test_unique_employment_protection() -> void:
|
||||
var service := StaffServiceType.new()
|
||||
var unique := _staff(&"original_unique", 10, {&"employment_kind": &"UNIQUE", &"daily_wage": 75, &"unpaid_wages": 25})
|
||||
_expect(service.register_staff(unique).is_success(), "legacy unique with stored wage remains loadable")
|
||||
var before := service.create_snapshot()
|
||||
for reason: StringName in [&"DISMISSED", &"SOLD", &"REMOVED"]:
|
||||
_expect(not service.remove_staff(unique.staff_id, reason).is_success() and service.create_snapshot() == before, "unique removal is rejected atomically for %s" % reason)
|
||||
_expect(not service.retire_staff(unique.staff_id).is_success() and not unique.is_retiring and service.create_snapshot() == before, "unique retirement rejects before setting retirement flag")
|
||||
var preview := service.preview_daily_payroll(1)
|
||||
_expect(preview[&"ok"] and preview[&"new_wages_total"] == 0 and preview[&"prior_arrears_total"] == 25 and preview[&"total_due"] == 25, "unique accrues zero wages without silently deleting historical liabilities")
|
||||
_expect(service.get_daily_payroll_total() == 0 and service.get_payroll_obligations()[0][&"daily_wage"] == 0, "all payroll views use unique zero upkeep")
|
||||
var restored := StaffServiceType.new()
|
||||
_expect(restored.restore_snapshot(before) and restored.create_snapshot() == before and restored.get_daily_payroll_total() == 0, "unique zero-upkeep rule survives exact save round trip")
|
||||
_expect(not restored.retire_staff(unique.staff_id).is_success() and restored.create_snapshot() == before, "restored unique remains protected against removal")
|
||||
_expect(restored.apply_daily_payroll_settlement(&"unique_day1", 1, {unique.staff_id: 25}).is_success(), "unique settlement pays only preserved historical arrears")
|
||||
_expect(restored.get_staff(unique.staff_id).unpaid_wages == 0 and restored.get_payroll_settlement(&"unique_day1")[&"new_wages_total"] == 0, "unique settlement adds no new upkeep")
|
||||
var settled := restored.create_snapshot()
|
||||
_expect(restored.apply_daily_payroll_settlement(&"unique_day1", 1, {unique.staff_id: 25}).is_success() and restored.create_snapshot() == settled, "unique settlement retry is idempotent")
|
||||
_expect(restored.preview_daily_payroll(2)[&"total_due"] == 0, "following day keeps unique upkeep free")
|
||||
|
||||
Reference in New Issue
Block a user