Files
TheTree_mock/tests/performance/test_mvp_manifest_cache.gd
T
Takeshi 5dde0c5ec2 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.
2026-09-30 23:52:24 +09:00

223 lines
13 KiB
GDScript

extends SceneTree
## Regression for catalog manifest ownership, invalidation, legacy saves and restore.
class CountingCatalog:
extends MvpContentCatalog
var factory_calls := 0
func build_all_guest_definitions() -> Array[GuestDefinition]:
factory_calls += 1
return super.build_all_guest_definitions()
var checks := 0
var failures := 0
var runtime: Variant
var clock: Variant
var state: Variant
func _initialize() -> void:
call_deferred("run")
func expect(ok: bool, label: String) -> void:
checks += 1
if not ok:
failures += 1
push_error("MANIFEST: " + label)
func manifest(catalog: MvpContentCatalog) -> Dictionary:
return runtime.call("_create_mvp_content_manifest", catalog)
# Independent original construction, including the existing full factory.
# No ID list, original definition field, or class/species value is omitted.
func original_manifest(catalog: MvpContentCatalog) -> Dictionary:
var guest_ids: Array[String] = []
for definition: GuestDefinition in catalog.build_all_guest_definitions():
guest_ids.append(String(definition.guest_type_id))
return {
&"manifest_version": runtime.MVP_CONTENT_MANIFEST_VERSION,
&"schema_version": MvpContentCatalog.SCHEMA_VERSION,
&"guest_class_ids": strings(catalog.get_guest_class_ids()),
&"species_ids": strings(catalog.get_species_ids()),
&"staff_candidate_ids": strings(catalog.get_staff_candidate_ids()),
&"unique_character_ids": strings(catalog.get_unique_character_ids()),
&"guest_definition_ids": guest_ids,
}
func strings(values: Array[StringName]) -> Array[String]:
var result: Array[String] = []
for value: StringName in values:
result.append(String(value))
return result
func documents() -> Dictionary:
var result: Dictionary = {}
for content_type: StringName in MvpContentCatalog.DEFAULT_PATHS:
var file := FileAccess.open(MvpContentCatalog.DEFAULT_PATHS[content_type], FileAccess.READ)
if file == null:
return {}
result[content_type] = JSON.parse_string(file.get_as_text())
file.close()
return result
func same(left: Variant, right: Variant) -> bool:
return runtime.call("_strict_variants_equal", left, right)
func check_manifest_shape(expected: Dictionary, actual: Dictionary, label: String) -> void:
expect(actual.keys() == expected.keys(), label + " insertion order of manifest keys")
for key: Variant in actual:
expect(typeof(key) == TYPE_STRING_NAME, label + " key Variant type " + String(key))
if expected[key] is Array:
var values: Array = actual[key]
expect(values.get_typed_builtin() == TYPE_STRING, label + " declared typed string Array " + String(key))
var bytes := SimulationDigest.canonical_bytes(actual)
expect(not bytes.is_empty() and bytes == SimulationDigest.canonical_bytes(expected), label + " nonempty canonical bytes")
var digest := SimulationDigest.compute(actual)
expect(not digest.is_empty() and digest == SimulationDigest.compute(expected), label + " nonempty exact digest")
func complete_live_guard() -> Dictionary:
return {
&"snapshot": runtime.create_snapshot(),
&"clock_paused": clock.paused,
&"save_policy": runtime.get_save_policy(),
&"pending_checkpoint_count": runtime.get_pending_checkpoint_count(),
&"last_daily_checkpoint_day": runtime.get_last_daily_checkpoint_day(),
&"content_filter": runtime.event_service.get_content_filter_profile(),
}
func run() -> void:
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()
expect(runtime.initialize(clock,state,840_101).get(&"valid", false), "canonical initialization")
clock.set_paused(true)
var cached: bool = runtime.has_method("_clear_mvp_content_manifest_cache")
expect(cached, "production manifest cache is installed")
var catalog := CountingCatalog.new()
expect(catalog.rebuild().get(&"valid", false), "counting fixture uses unchanged real catalog")
runtime.mvp_content_catalog = catalog
var expected := original_manifest(catalog)
catalog.factory_calls = 0
var first := manifest(catalog)
expect(same(expected, first), "first read preserves all values and generated ID ordering")
check_manifest_shape(expected, first, "first fill")
first[&"manifest_version"] = -1
first[&"staff_candidate_ids"].clear()
first[&"guest_definition_ids"][0] = "poisoned"
var second := manifest(catalog)
expect(same(expected, second), "first returned manifest never aliases live/private data")
check_manifest_shape(expected, second, "second read")
expect(catalog.factory_calls == (1 if cached else 2), "candidate fills once; reference rebuilds each read")
second[&"species_ids"].clear()
expect(same(expected, manifest(catalog)), "cache hit has independently owned nested arrays")
var entry := catalog.get_entry(&"guest_classes", &"cleric")
entry[&"need_rates"][&"hunger"] = 99
var ids := catalog.get_species_ids()
ids.clear()
var generated := catalog.build_guest_definition(&"cleric", &"human")
generated.guest_type_id = &"poisoned_returned_definition"
expect(same(expected, manifest(catalog)), "entry/ID/factory getters cannot mutate cached membership")
var prior_generation: int = int(catalog.call("get_manifest_generation")) if cached else -1
var builds_before := catalog.factory_calls
var docs := documents()
docs[&"guest_classes"]["entries"][0]["base_patience"] = 61
expect(catalog.rebuild_from_documents(docs).get(&"valid", false), "same IDs with changed real fields rebuild validly")
if cached:
expect(int(catalog.call("get_manifest_generation")) > prior_generation, "rebuild advances generation even when IDs do not change")
expect(same(expected, manifest(catalog)) and catalog.factory_calls == builds_before+1, "changed definitions force original factory despite unchanged membership")
docs[&"guest_classes"]["entries"][0]["unknown_definition_field"] = 1
expect(not catalog.rebuild_from_documents(docs).get(&"valid", true), "unknown original fields remain fail-closed")
var empty_expected := original_manifest(catalog)
expect(same(empty_expected, manifest(catalog)), "invalid rebuild cannot expose earlier valid manifest")
expect(catalog.rebuild().get(&"valid", false), "valid recovery uses normal file rebuild")
expect(same(expected, manifest(catalog)), "valid recovery invalidates empty generation")
expect(not catalog.rebuild({&"unknown_content_type":"res://missing.json"}).get(&"valid", true), "invalid path set is rejected")
expect(same(original_manifest(catalog), manifest(catalog)), "failed path validation invalidates manifest")
expect(catalog.rebuild().get(&"valid", false), "recover after path validation failure")
var missing_paths: Dictionary = MvpContentCatalog.DEFAULT_PATHS.duplicate(true)
missing_paths[&"guest_classes"] = "res://missing_manifest_cache_content.json"
expect(not catalog.rebuild(missing_paths).get(&"valid", true), "read failure remains fail-closed")
expect(same(original_manifest(catalog), manifest(catalog)), "read failure cannot retain prior successful generation")
expect(catalog.rebuild().get(&"valid", false), "recover after read failure")
var replacement := CountingCatalog.new()
expect(replacement.rebuild().get(&"valid", false), "new catalog identity is valid")
runtime.mvp_content_catalog = replacement
expect(same(expected, manifest(replacement)) and replacement.factory_calls == 1, "different identity cannot hit previous template")
var same_generation := CountingCatalog.new()
expect(same_generation.rebuild().get(&"valid", false), "second fresh identity has equivalent content")
if cached:
expect(replacement.call("get_manifest_generation") == same_generation.call("get_manifest_generation"), "identity test deliberately uses equal generations")
runtime.mvp_content_catalog = same_generation
expect(same(expected, manifest(same_generation)) and same_generation.factory_calls == 1, "equal generation on a new identity must still rebuild")
replacement = same_generation
# Temporary restore catalogs must be fully built without changing live cache.
var temporary := CountingCatalog.new()
temporary.rebuild()
var live_calls := replacement.factory_calls
manifest(temporary)
manifest(temporary)
expect(temporary.factory_calls == 2, "temporary catalog remains uncached for explicit restore validation")
expect(same(expected, manifest(replacement)), "temporary validation preserves current content")
if cached:
expect(replacement.factory_calls == live_calls, "temporary validation does not replace live cache")
var old := expected.duplicate(true)
old[&"manifest_version"] = 1
old[&"staff_candidate_ids"].erase("staff_uma")
var old_data := {&"catalog_manifest":old, &"hired_staff_candidate_ids":[], &"staff":{&"staff":[]}}
if cached:
runtime.call("_clear_mvp_content_manifest_cache")
expect(runtime.call("_saved_content_manifest_matches", replacement, old_data), "sole prior chapel manifest remains accepted on first fill")
expect(same(expected, manifest(replacement)), "legacy first-fill edits cannot poison current v2 template")
expect(runtime.call("_saved_content_manifest_matches", replacement, old_data), "legacy prior manifest remains accepted on a hit")
expect(same(expected, manifest(replacement)), "legacy hit edits leave staff_uma and manifest version intact")
var guard_before := complete_live_guard()
var before: Dictionary = guard_before[&"snapshot"]
var tampered := before.duplicate(true)
for definition: Dictionary in tampered[&"systems"][&"runtime"][&"guests"][&"definitions"]:
if String(definition[&"guest_type_id"]) == String(expected[&"guest_definition_ids"][0]):
definition[&"wealth_min"] = int(definition[&"wealth_min"])+1
break
var catalog_before: MvpContentCatalog = runtime.mvp_content_catalog
expect(not runtime.restore_snapshot(tampered), "cached manifest does not bypass full GuestDefinition value validation")
expect(runtime.mvp_content_catalog == catalog_before and same(complete_live_guard(), guard_before), "rejected restore preserves identity and all full save-lifecycle guard fields")
expect(runtime.restore_snapshot(before), "canonical save restores unchanged")
expect(runtime.mvp_content_catalog != catalog_before and same(runtime.create_snapshot(), before), "successful restore replaces identity while preserving complete saved state")
var custom: GuestDefinition = runtime.guest_service.create_definition(&"manifest_late_guest", {&"adult_verified":true, &"wealth_min":10, &"wealth_max":20})
expect(custom != null, "late custom registration remains supported")
if custom != null:
custom.base_patience = 42
expect(runtime.guest_service.get_definition(&"manifest_late_guest").base_patience == 42, "live GuestService definition semantics remain intact")
expect(same(expected, manifest(runtime.mvp_content_catalog)), "custom registered definitions do not replace catalog membership")
var public_snapshot: Dictionary = runtime.create_snapshot()
public_snapshot[&"systems"][&"runtime"][&"catalog_manifest"][&"guest_definition_ids"].clear()
expect(same(expected, runtime.create_snapshot()[&"systems"][&"runtime"][&"catalog_manifest"]), "public full snapshot cannot mutate next snapshot manifest")
runtime.call("_publish_snapshot", clock.absolute_minute)
expect(same(runtime.latest_snapshot, runtime.create_snapshot()), "publication still eagerly contains all current components")
var published_digest: String = runtime.latest_digest
expect(published_digest == SimulationDigest.compute(runtime.latest_snapshot), "latest_digest remains exact")
var replay_start: Dictionary = runtime.create_snapshot()
clock.advance_minutes(7)
var replay_end: Dictionary = runtime.create_snapshot()
var retained_start := replay_start.duplicate(true)
expect(runtime.restore_snapshot(replay_start), "retained checkpoint restores for tick replay")
clock.advance_minutes(7)
var repeated: Dictionary = runtime.create_snapshot()
expect(same(replay_start, retained_start), "live ticks and restore never mutate retained public checkpoint")
expect(same(replay_end, repeated), "full tick replay preserves every stored event/history/counter and component")
expect(not SimulationDigest.canonical_bytes(repeated).is_empty() and SimulationDigest.canonical_bytes(repeated) == SimulationDigest.canonical_bytes(replay_end), "tick replay compares complete nonempty canonical bytes without event exclusions")
if cached:
var previous_identity: MvpContentCatalog = runtime.mvp_content_catalog
expect(runtime.initialize(clock,state,840_102).get(&"valid", false), "repeat initialization remains valid")
expect(runtime.mvp_content_catalog != previous_identity and same(expected, manifest(runtime.mvp_content_catalog)), "initialization clears old identity while preserving manifest values")
runtime.free()
clock.free()
state.free()
print("MANIFEST_CACHE_CORRECTNESS checks=%d failures=%d candidate=%s" % [checks, failures, cached])
quit(failures)