286 lines
17 KiB
GDScript
286 lines
17 KiB
GDScript
extends "res://tests/s3/test_guest_service.gd"
|
|
|
|
const OriginalGuestScript = preload("/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-guest-bulk-original-oracle-r315.gd")
|
|
|
|
class CustomGuest:
|
|
extends GuestRuntime
|
|
class CustomDefinition:
|
|
extends GuestDefinition
|
|
class CustomService:
|
|
extends GuestService
|
|
class FakeCore:
|
|
extends RefCounted
|
|
var calls := 0
|
|
func guest_candidate_batch(_owner, _id, _facilities, _context) -> Dictionary:
|
|
calls += 1
|
|
return {&"supported": false}
|
|
|
|
const ProtocolHelper = preload("/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-hot-loop-local-r329/protocol_helper.gd")
|
|
|
|
var _bulk_records: Array = []
|
|
var _bulk_core: Object
|
|
var _bulk_cases := 0
|
|
|
|
func _bulk_state(service) -> Array:
|
|
var order: Array[StringName] = service._sorted_guest_ids_cache.duplicate()
|
|
var dirty: bool = service._guest_order_dirty
|
|
return [service.create_snapshot(), service.last_error, service._decision_rejections.duplicate(true), service._diagnostics.duplicate(true), order, dirty, service._pending_spawn_notifications.duplicate(true), service._external_mutation_blocked, service.current_minute]
|
|
|
|
func _bulk_prime(service) -> void:
|
|
service.get_guests()
|
|
|
|
func _bulk_original(service):
|
|
var oracle = OriginalGuestScript.new()
|
|
_expect(oracle.restore_snapshot(service.create_snapshot()), "literal original Guest independent state restored")
|
|
_bulk_prime(oracle)
|
|
return oracle
|
|
|
|
func _bulk_array_shape(rows: Array) -> Array:
|
|
var shapes: Array = [rows.is_typed(), rows.get_typed_builtin(), rows.is_read_only()]
|
|
for row: Dictionary in rows:
|
|
shapes.append([row.is_typed(), row.is_read_only()])
|
|
return shapes
|
|
|
|
func _bulk_service() -> GuestService:
|
|
var service := _service_with_definitions()
|
|
service.spawn_guest(&"bulk", &"adventurer", 0, {
|
|
&"wallet_gold": 500, &"planned_exit_at": 1000,
|
|
&"state": GuestRuntimeType.State.DECIDING,
|
|
&"needs": {&"hunger": 80, &"sleep": 30, &"hygiene": 40, &"medical": 20, &"leisure": 60, &"intimacy": 50},
|
|
})
|
|
_bulk_prime(service)
|
|
return service
|
|
|
|
func _bulk_compare(label: String, rows: Array, patch: Dictionary = {}, context: Dictionary = {}) -> void:
|
|
_bulk_cases += 1
|
|
var service := _bulk_service()
|
|
var oracle = _bulk_original(service)
|
|
var guest := service.get_guest(&"bulk")
|
|
for key: Variant in patch:
|
|
guest.set(key, patch[key])
|
|
oracle.get_guest(&"bulk").set(key, patch[key])
|
|
service.last_error = "retained-before-pure-call"
|
|
var before := var_to_bytes(_bulk_state(service))
|
|
var input_before := var_to_bytes(rows)
|
|
var batch: Dictionary = _bulk_core.call(&"guest_candidate_batch", service, &"bulk", rows, context)
|
|
_expect(bool(batch.get(&"supported", false)), label + " supported fixed pure input")
|
|
_expect(var_to_bytes(_bulk_state(service)) == before, label + " pure call preserves full Guest snapshot/errors/rejections")
|
|
_expect(var_to_bytes(rows) == input_before, label + " input ownership preserved")
|
|
if not bool(batch.get(&"supported", false)): return
|
|
var accepted: Array[Dictionary] = oracle.extract_candidates(&"bulk", rows, context)
|
|
var rejected: Array[Dictionary] = oracle.get_last_candidate_rejections(&"bulk")
|
|
var ranked: Array[Dictionary] = oracle.rank_candidates(&"bulk", accepted)
|
|
_expect(_bulk_array_shape(batch[&"accepted"]) == _bulk_array_shape(accepted), label + " accepted type/readonly shape original equality")
|
|
_expect(_bulk_array_shape(batch[&"ranked"]) == _bulk_array_shape(ranked), label + " ranked type/readonly shape original equality")
|
|
_expect(var_to_bytes(batch[&"accepted"]) == var_to_bytes(accepted), label + " entire ordered typed accepted original equality")
|
|
_expect(var_to_bytes(batch[&"rejected"]) == var_to_bytes(rejected), label + " entire ordered typed rejection original equality")
|
|
_expect(var_to_bytes(batch[&"ranked"]) == var_to_bytes(ranked), label + " entire ordered typed scoring/ranking original equality")
|
|
_bulk_records.append({&"case": label, &"input": rows.duplicate(true), &"context": context.duplicate(true), &"patch": patch.duplicate(true), &"actual": batch.duplicate(true), &"accepted_original": accepted, &"rejected_original": rejected, &"ranked_original": ranked})
|
|
if not (batch[&"ranked"] as Array).is_empty():
|
|
var accepted_before := var_to_bytes(batch[&"accepted"])
|
|
var actual: Dictionary = batch[&"ranked"][0]
|
|
actual[&"ownership_probe"] = true
|
|
if actual.get(&"path_handle") is Dictionary: actual[&"path_handle"][&"ownership_probe"] = true
|
|
_expect(var_to_bytes(rows) == input_before, label + " ranked mutation is isolated from caller input")
|
|
_expect(var_to_bytes(batch[&"accepted"]) == accepted_before, label + " ranked deep copy is isolated from accepted candidates")
|
|
|
|
func _test_bulk_candidates() -> void:
|
|
_bulk_core = ClassDB.instantiate(&"TheTreeNumericCore")
|
|
_expect(_bulk_core != null and _bulk_core.has_method(&"guest_candidate_batch"), "exact new batch binding exists")
|
|
if _bulk_core == null or not _bulk_core.has_method(&"guest_candidate_batch"): return
|
|
var base := _candidate(&"a", {&"need_relief": {&"hunger": 80, &"leisure": 20}, &"facility_tags": [&"meal", &"leisure", &"meal"], &"path_handle": {&"path": [Vector3i.ZERO, Vector3i(1, 0, 0)], &"target_cell": Vector3i(1, 0, 0)}})
|
|
_bulk_compare("ordinary", [base])
|
|
_bulk_compare("empty", [])
|
|
_bulk_compare("skip-nondictionary-and-empty-id", [null, 4, "ignored", {}, {&"facility_id": &""}, base])
|
|
var duplicates: Array = []
|
|
for i in range(64):
|
|
var row := base.duplicate(true)
|
|
row[&"facility_id"] = StringName("b" if i % 3 == 0 else "a")
|
|
row[&"probe_index"] = i
|
|
duplicates.append(row)
|
|
_bulk_compare("equal-sort-keys64", duplicates)
|
|
var branches := [
|
|
{&"need_relief": {&"rest": 1}, &"inputs_available": false},
|
|
{&"need_effects": {&"hunger": 1.0}},
|
|
{&"need_relief": null}, {&"need_relief": true},
|
|
{&"inputs_available": false, &"adult_only": true},
|
|
{&"adult_only": true}, {&"required_guest_tags": [&"missing"]},
|
|
{&"forbidden_guest_tags": [&"general"]}, {&"allowed_guest_types": [&"other"]},
|
|
{&"is_open": false, &"reachable": false}, {&"reachable": false},
|
|
{&"travel_minutes": 1001}, {&"price_gold": 10000},
|
|
{&"wait_minutes": 120}, {&"risk_level": 100}, {&"illegal_level": 100},
|
|
{&"expected_quality": 0}, {&"is_open": false, &"open_at_arrival": true},
|
|
{&"credit_allowed": true, &"price_gold": 550},
|
|
{&"discretionary": true, &"price_gold": 400},
|
|
{&"respect_survival_reserve": false, &"discretionary": true, &"price_gold": 400},
|
|
{&"risk": 90, &"risk_level": 0, &"risk_sensitivity": 3},
|
|
{&"legal_permission_override": true, &"illegal_level": 100},
|
|
{&"need_relief": -100, &"travel_minutes": -2, &"wait_minutes": -3},
|
|
{&"ok": false}, {&"ok": 0},
|
|
{&"preference": 9223372036854775807, &"group_fit": -9223372036854775808, &"repetition_penalty": -9223372036854775808},
|
|
{&"price_gold": 9223372036854775807, &"credit_allowed": true},
|
|
{&"risk": 9223372036854775807, &"risk_level": 0, &"risk_sensitivity": 9223372036854775807},
|
|
{&"need_relief": {"hunger": 9223372036854775807, "medical": -9223372036854775808}},
|
|
{&"novelty": -10, &"urgency": 77, &"price_burden": 100},
|
|
]
|
|
for i in range(branches.size()):
|
|
var row := base.duplicate(true)
|
|
for key: Variant in branches[i]: row[key] = branches[i][key]
|
|
_bulk_compare("branch-%02d" % i, [row, base])
|
|
var string_keys := {}
|
|
for key: Variant in base: string_keys[String(key)] = base[key]
|
|
_bulk_compare("string-keys", [string_keys])
|
|
_bulk_compare("visited-defaults", [base], {&"visited_facility_ids": [&"a"]})
|
|
_bulk_compare("addiction-removes-reserve", [_candidate(&"a", {&"discretionary": true, &"price_gold": 500})], {&"addiction_by_tag": {&"gambling": 75}})
|
|
_bulk_compare("wallet-wrap", [base], {&"wallet_gold": 9223372036854775807, &"credit_limit_gold": 9223372036854775807, &"debt_gold": -9223372036854775808})
|
|
_bulk_compare("context-gates", [base], {}, {&"absolute_minute": 999, &"wait_hard_limit": 1})
|
|
var readonly := base.duplicate(true)
|
|
readonly.make_read_only()
|
|
var readonly_rows: Array = [readonly]
|
|
readonly_rows.make_read_only()
|
|
_bulk_compare("readonly-input", readonly_rows)
|
|
var rank_rows: Array = [
|
|
_candidate(&"priority", {&"need_relief": {&"hunger": 1}, &"novelty": 0}),
|
|
_candidate(&"medical", {&"need_relief": {&"medical": 100}, &"preference": 100}),
|
|
_candidate(&"utility", {&"need_relief": {&"hunger": 1}, &"novelty": 10}),
|
|
_candidate(&"urgency-a", {&"need_relief": {&"hunger": 1}, &"urgency": 90}),
|
|
_candidate(&"urgency-b", {&"need_relief": {&"hunger": 1}, &"urgency": 10}),
|
|
_candidate(&"travel-a", {&"need_relief": {&"hunger": 1}, &"travel_minutes": 1, &"group_fit": 1}),
|
|
_candidate(&"travel-b", {&"need_relief": {&"hunger": 1}, &"travel_minutes": 2, &"group_fit": 2}),
|
|
]
|
|
_bulk_compare("all-rank-tuple-components", rank_rows)
|
|
_test_bulk_fallback_and_decision(base)
|
|
_test_bulk_custom_and_typed(base)
|
|
|
|
func _test_bulk_fallback_and_decision(base: Dictionary) -> void:
|
|
for label in ["ordinary", "float-conversion", "reach-callback", "reserve-callback"]:
|
|
var actual := _bulk_service()
|
|
var oracle = _bulk_original(actual)
|
|
var a_signals: Array = []
|
|
var b_signals: Array = []
|
|
actual.guest_state_changed.connect(func(id: StringName, old: int, new_state: int, reason: StringName) -> void: a_signals.append([id, old, new_state, reason]))
|
|
oracle.guest_state_changed.connect(func(id: StringName, old: int, new_state: int, reason: StringName) -> void: b_signals.append([id, old, new_state, reason]))
|
|
var a_calls: Array = []
|
|
var b_calls: Array = []
|
|
var a_context := {&"absolute_minute": 10}
|
|
var b_context := {&"absolute_minute": 10}
|
|
if label == "reach-callback":
|
|
a_context[&"reachability_callback"] = func(_guest: GuestRuntime, candidate: Dictionary, _context: Dictionary) -> bool:
|
|
a_calls.append(candidate[&"facility_id"]); candidate[&"callback_probe"] = true; return true
|
|
b_context[&"reachability_callback"] = func(_guest: GuestRuntime, candidate: Dictionary, _context: Dictionary) -> bool:
|
|
b_calls.append(candidate[&"facility_id"]); candidate[&"callback_probe"] = true; return true
|
|
if label == "reserve-callback":
|
|
a_context[&"reserve_callback"] = func(_guest_id: StringName, facility_id: StringName, candidate: Dictionary) -> bool:
|
|
a_calls.append(facility_id); candidate[&"callback_probe"] = true; return true
|
|
b_context[&"reserve_callback"] = func(_guest_id: StringName, facility_id: StringName, candidate: Dictionary) -> bool:
|
|
b_calls.append(facility_id); candidate[&"callback_probe"] = true; return true
|
|
var row := base.duplicate(true)
|
|
if label == "float-conversion": row[&"travel_minutes"] = 1.75
|
|
var input_before := var_to_bytes([row])
|
|
var state_before := var_to_bytes(_bulk_state(actual))
|
|
var batch: Dictionary = _bulk_core.call(&"guest_candidate_batch", actual, &"bulk", [row], a_context)
|
|
_expect(bool(batch[&"supported"]) == (label == "ordinary"), label + " supported or whole original fallback domain")
|
|
_expect(var_to_bytes(_bulk_state(actual)) == state_before and a_calls.is_empty(), label + " probe causes no observable state/callback effects")
|
|
var got := actual.decide_with_native_candidates(_bulk_core, &"bulk", [row], 10, a_context)
|
|
var expected: Dictionary = oracle.decide(&"bulk", [row], 10, b_context)
|
|
_expect(var_to_bytes(got) == var_to_bytes(expected), label + " entire typed decision result matches original")
|
|
_expect(var_to_bytes(_bulk_state(actual)) == var_to_bytes(_bulk_state(oracle)), label + " entire Guest state/errors/rejection matches original")
|
|
_expect(a_calls == b_calls, label + " callback count/order preserves original single dispatch")
|
|
_expect(a_signals == b_signals, label + " state notification content/order original equality")
|
|
_expect(var_to_bytes([row]) == input_before, label + " decision input ownership preserved")
|
|
_bulk_records.append({&"decision_case": label, &"actual_result": got, &"original_result": expected, &"actual_state": _bulk_state(actual), &"original_state": _bulk_state(oracle), &"actual_calls": a_calls, &"original_calls": b_calls, &"actual_signals": a_signals, &"original_signals": b_signals})
|
|
|
|
func _test_bulk_custom_and_typed(base: Dictionary) -> void:
|
|
for label in ["typed-dictionary", "custom-guest", "custom-definition", "custom-service", "fake-core", "unknown-guest", "missing-definition"]:
|
|
var actual = _bulk_service()
|
|
var oracle = _bulk_original(actual)
|
|
var row: Dictionary = base.duplicate(true)
|
|
if label == "typed-dictionary":
|
|
var typed: Dictionary[StringName, Variant] = {}
|
|
for key: Variant in row: typed[StringName(key)] = row[key]
|
|
row = typed
|
|
if label == "custom-guest":
|
|
var custom := CustomGuest.new()
|
|
custom.configure(&"bulk", &"adventurer", actual.get_guest(&"bulk").create_snapshot())
|
|
actual._guests[&"bulk"] = custom
|
|
if label == "custom-definition":
|
|
var custom := CustomDefinition.new()
|
|
custom.configure(&"adventurer", actual.get_definition(&"adventurer").create_snapshot())
|
|
actual._definitions[&"adventurer"] = custom
|
|
if label == "custom-service":
|
|
var custom := CustomService.new()
|
|
_expect(custom.restore_snapshot(actual.create_snapshot()), label + " custom owner independent restore")
|
|
actual = custom
|
|
_bulk_prime(actual)
|
|
if label == "unknown-guest":
|
|
actual._guests.clear(); actual._guest_order_dirty = true
|
|
oracle._guests.clear(); oracle._guest_order_dirty = true
|
|
if label == "missing-definition":
|
|
actual._definitions.erase(&"adventurer")
|
|
oracle._definitions.erase(&"adventurer")
|
|
_bulk_prime(actual); _bulk_prime(oracle)
|
|
var before := var_to_bytes(_bulk_state(actual))
|
|
var input_before := var_to_bytes([row])
|
|
var selected_core: Object = FakeCore.new() if label == "fake-core" else _bulk_core
|
|
var probe: Dictionary = _bulk_core.call(&"guest_candidate_batch", actual, &"bulk", [row], {})
|
|
_expect(bool(probe[&"supported"]) == (label == "fake-core"), label + " unsupported domain gate")
|
|
_expect(var_to_bytes(_bulk_state(actual)) == before, label + " unsupported returns before error/state changes")
|
|
var got: Dictionary = actual.decide_with_native_candidates(selected_core, &"bulk", [row], 10, {})
|
|
var expected: Dictionary = oracle.decide(&"bulk", [row], 10, {})
|
|
_expect(var_to_bytes(got) == var_to_bytes(expected), label + " literal original decision equality")
|
|
_expect(var_to_bytes(_bulk_state(actual)) == var_to_bytes(_bulk_state(oracle)), label + " literal original state/error/rejection equality")
|
|
_expect(var_to_bytes([row]) == input_before, label + " original fallback input ownership")
|
|
if label == "fake-core": _expect(int(selected_core.get("calls")) == 0, "non-native core does not receive public wrapper call")
|
|
_bulk_records.append({&"fallback_case": label, &"actual_result": got, &"original_result": expected, &"actual_state": _bulk_state(actual), &"original_state": _bulk_state(oracle)})
|
|
_bulk_core = null
|
|
|
|
func _bulk_sha256(bytes: PackedByteArray) -> String:
|
|
var digest := HashingContext.new()
|
|
if digest.start(HashingContext.HASH_SHA256) != OK: return ""
|
|
if digest.update(bytes) != OK: return ""
|
|
return digest.finish().hex_encode()
|
|
|
|
func _run() -> void:
|
|
_test_definition_and_runtime_contract()
|
|
_test_need_updates_and_single_clamp()
|
|
_test_need_context_and_adult_gate()
|
|
_test_shared_need_context_fast_paths()
|
|
_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()
|
|
_test_service_need_effect_schema()
|
|
_test_path_failure_safe_recovery()
|
|
_test_exit_persistence_boundary()
|
|
_test_overdue_departure_snapshot()
|
|
_test_snapshot_round_trip_and_atomic_rejection()
|
|
_test_v1_need_migration_and_schema_rejection()
|
|
_test_deterministic_decision_snapshot()
|
|
_test_bulk_candidates()
|
|
var output := OS.get_environment("THE_TREE_RUNTIME_MONITOR_OUTPUT")
|
|
var protocol = ProtocolHelper.new()
|
|
var protocol_core: Object = ClassDB.instantiate(&"TheTreeNumericCore")
|
|
var protocol_report: Dictionary = protocol.run_frozen(protocol_core,"/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-bridge-Godot-protocol-preparation-r73/pinned-inputs.json")
|
|
var core_ref: WeakRef = weakref(protocol_core)
|
|
protocol_core = null
|
|
_expect(core_ref.get_ref() == null, "combined protocol core with populated cache releases")
|
|
_checks += int(protocol_report["checks"])
|
|
_failures += protocol_report["failures"].size()
|
|
var protocol_file := FileAccess.open(output.path_join("protocol-report.json"),FileAccess.WRITE)
|
|
if protocol_file == null: quit(98); return
|
|
protocol_file.store_string(JSON.stringify(protocol_report,"\t")+"\n");protocol_file.close()
|
|
protocol = null
|
|
var bytes := var_to_bytes(_bulk_records)
|
|
var file := FileAccess.open(output.path_join("fixtures.varbytes"), FileAccess.WRITE)
|
|
if file == null: quit(98); return
|
|
file.store_buffer(bytes); file.close()
|
|
var report := {"passed": _failures == 0, "checks": _checks, "failures": _failures, "pure_case_count": _bulk_cases, "decision_case_count": 4, "custom_fallback_case_count": 7, "original_s3_invocations": 18, "protocol_cases": 50, "protocol_checks": int(protocol_report["checks"]), "pid": OS.get_process_id(), "fixture_bytes": bytes.size(), "fixture_sha256": _bulk_sha256(bytes), "performance_accepted": false, "unit_accepted": false}
|
|
file = FileAccess.open(output.path_join("report.json"), FileAccess.WRITE)
|
|
if file == null: quit(98); return
|
|
file.store_string(JSON.stringify(report, "\t") + "\n"); file.close()
|
|
print("Guest bulk local: %d checks, %d failures" % [_checks, _failures])
|
|
quit(0 if _failures == 0 else 1)
|