P18-01-CODE: 原本関係モデル・旧値保全・Runtime保存と読取経路

This commit is contained in:
Agent Control Runner
2026-10-13 14:25:06 +09:00
parent d2a1f80a61
commit f76ad96990
12 changed files with 702 additions and 11 deletions
@@ -0,0 +1,110 @@
extends SceneTree
const Model = preload("res://scripts/relationship/original_relationship_state.gd")
const Service = preload("res://scripts/relationship/relationship_service.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _expect(value: bool, message: String) -> void:
_checks += 1
if not value:
_failures += 1
push_error(message)
static func fixture() -> Dictionary:
# Validation fixtures, never approved initialization or range limits.
return {"subject_id": "heroine_anri", "protagonist_id": "player", "rival_id": "rival_rai",
"affection": 142.5, "lust": 23.25, "netorare": 11, "shield_current": 90.5, "shield_max": 142.5}
func _service() -> RelationshipService:
return Service.new().configure_original_identity_validator(
func(p: Dictionary) -> bool:
return p["subject_id"] == "heroine_anri" and p["protagonist_id"] == "player" and p["rival_id"] == "rival_rai"
)
func _run() -> void:
var service := _service()
var before := var_to_bytes(service.create_snapshot())
var unset := service.get_original_relationship(&"heroine_anri")
_expect(not unset[&"configured"] and unset[&"affection"] == null, "missing record is unset")
unset[&"affection"] = 999
_expect(var_to_bytes(service.create_snapshot()) == before, "reads own their dictionaries")
var payload := fixture()
var payload_bytes := var_to_bytes(payload)
_expect(service.register_original_relationship(payload).is_success(), "complete directional fixture registers")
payload["affection"] = 0
_expect(service.get_original_relationship(&"heroine_anri")["affection"] == 142.5, "caller cannot mutate stored facts")
_expect(not service.register_original_relationship(fixture()).is_success(), "duplicate subject rejected")
var saved := service.create_snapshot()
var restored := _service()
_expect(restored.restore_snapshot(saved), "new-only v2 restores")
_expect(var_to_bytes(restored.create_snapshot()) == var_to_bytes(saved), "new-only exact bytes")
_expect(var_to_bytes(fixture()) == payload_bytes, "fixture is explicit and stable")
for field: String in Model.FIELDS:
var missing := fixture()
missing.erase(field)
_expect(Model.from_snapshot(missing) == null, "missing required field " + field)
for value: Variant in [null, true, "12", INF, NAN, {}, []]:
var wrong := fixture()
wrong["lust"] = value
_expect(Model.from_snapshot(wrong) == null, "wrong/nonfinite numeric value rejected")
for patch: Dictionary in [{"shield_max": 1}, {"shield_current": -1}, {"shield_current": 143}, {"rival_id": "player"}, {"subject_id": "player"}]:
var wrong := fixture()
wrong.merge(patch, true)
_expect(Model.from_snapshot(wrong) == null, "value/identity invariant rejected")
var zero := fixture()
for field: String in ["affection", "lust", "netorare", "shield_current", "shield_max"]:
zero[field] = 0
_expect(Model.from_snapshot(zero) != null, "explicit zero is configured")
var legacy := {"version": 1, "relationships": [{"relationship_id": "old", "actor_a_id": "player", "actor_b_id": "heroine_anri",
"metrics": {"affinity": 117, "dependence": 29, "extension": {"opaque": [3, "keep"]}}, "extra": ["raw", 2.5]}],
"threshold_candidates": [], "next_candidate_sequence": 1, "extension": {"version": 70}}
var legacy_bytes := var_to_bytes(legacy)
var migrated := _service()
_expect(migrated.restore_snapshot(legacy), "legacy aliases still usable")
_expect(migrated.get_relationship(&"old").get_metric(&"affection") == 100, "legacy normalization behavior retained")
_expect(not migrated.get_original_relationship(&"heroine_anri")[&"configured"], "legacy never configures original facts")
_expect(var_to_bytes(migrated.create_snapshot()[&"legacy"]) == legacy_bytes, "all raw legacy values and extensions preserved")
_expect(var_to_bytes(legacy) == legacy_bytes, "legacy input bytes untouched")
_expect(migrated.register_original_relationship(fixture()).is_success(), "mixed state registers independently")
var mixed := migrated.create_snapshot()
_expect(restored.restore_snapshot(mixed), "mixed snapshot restores")
_expect(var_to_bytes(restored.create_snapshot()) == var_to_bytes(mixed), "mixed exact bytes")
_expect(migrated.apply_value_change(&"old", &"trust", 3, 0).is_success(), "legacy changes still apply")
var changed: Dictionary = migrated.create_snapshot()[&"legacy"]
_expect(changed["relationships"][0]["metrics"]["affinity"] == 117 and changed["relationships"][0]["metrics"]["trust"] == 3, "changed metric preserves raw alias")
for version: Variant in [0, 3, "2", 2.0, true]:
var bad := mixed.duplicate(true)
bad[&"version"] = version
_rejected(restored, bad)
var duplicate := mixed.duplicate(true)
duplicate[&"original_relationships"].append(fixture())
_rejected(restored, duplicate)
for patch: Dictionary in [{"rival_id": "unknown"}, {"protagonist_id": "unknown"}, {"lust": false}, {"shield_max": 10}]:
var bad := mixed.duplicate(true)
bad[&"original_relationships"][0].merge(patch, true)
_rejected(restored, bad)
var bad_legacy := legacy.duplicate(true)
bad_legacy["relationships"][0]["metrics"]["affinity"] = "117"
_rejected(restored, bad_legacy)
bad_legacy = legacy.duplicate(true)
bad_legacy["relationships"].append(bad_legacy["relationships"][0].duplicate(true))
_rejected(restored, bad_legacy)
print("Original relationship model: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
func _rejected(service: RelationshipService, payload: Dictionary) -> void:
var before := var_to_bytes(service.create_snapshot())
var input := var_to_bytes(payload)
_expect(not service.restore_snapshot(payload), "corrupt snapshot rejected")
_expect(var_to_bytes(service.create_snapshot()) == before, "rejection preserves prior state")
_expect(var_to_bytes(payload) == input, "rejection preserves input bytes")
@@ -0,0 +1 @@
uid://cqxwpbkgjtnsb
@@ -0,0 +1,78 @@
extends SceneTree
const Fixtures = preload("res://tests/s4/test_original_relationship_model.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _expect(value: bool, message: String) -> void:
_checks += 1
if not value:
_failures += 1
push_error(message)
func _run() -> void:
var clock = load("res://autoload/game_clock.gd").new()
var state = load("res://autoload/game_state.gd").new()
var runtime = load("res://autoload/simulation_runtime.gd").new()
clock.reset(1, 6, 0)
state.reset()
_expect(runtime.initialize(clock, state, 18001)[&"valid"], "Runtime initialized")
clock.set_paused(true)
var fixture := Fixtures.fixture()
_expect(not runtime.relationship_service.register_original_relationship(fixture).is_success(), "unhired subject rejected by service boundary")
_expect(runtime.submit_command(&"hire_staff_candidate", {&"candidate_id": &"heroine_anri"}).is_success(), "actual approved Anri hire")
var read_before := var_to_bytes(runtime.create_snapshot())
var report: Dictionary = runtime.get_staff_management_report()
var found := false
for row: Dictionary in report[&"staff"]:
if row[&"staff_id"] == &"heroine_anri":
found = true
_expect(not row[&"original_relationship"][&"configured"] and row[&"original_relationship"][&"affection"] == null, "actual report exposes unset")
_expect(found, "hired staff appears in actual report")
_expect(var_to_bytes(runtime.create_snapshot()) == read_before, "unset report read has no mutation")
var old_events: Array = runtime.domain_events.create_snapshot()["recent_events"]
_expect(runtime.submit_command(&"register_original_relationship", fixture).is_success(), "registered Runtime command stores explicit fixture")
var new_events: Array = runtime.domain_events.create_snapshot()["recent_events"]
_expect(new_events.size() == old_events.size() + 1 and new_events[-1][&"event_type"] == "COMMAND_COMPLETED", "only existing command completion event is emitted")
_expect(runtime.relationship_service.create_relationship(&"legacy", &"heroine_anri", &"player", {&"affinity": 44}).is_success(), "legacy service remains usable")
var saved: Dictionary = runtime.create_snapshot()
var input := var_to_bytes(saved)
_expect(runtime.restore_snapshot(saved), "mixed Runtime snapshot restores")
_expect(var_to_bytes(runtime.create_snapshot()) == input, "Runtime snapshot exact round trip")
_expect(var_to_bytes(saved) == input, "restore preserves caller bytes")
read_before = var_to_bytes(runtime.create_snapshot())
report = runtime.get_staff_management_report()
for row: Dictionary in report[&"staff"]:
if row[&"staff_id"] == &"heroine_anri":
var original: Dictionary = row[&"original_relationship"]
_expect(original[&"configured"] and original["affection"] == 142.5 and original["shield_max"] == 142.5, "restored report equals stored original values")
original["affection"] = -99
_expect(var_to_bytes(runtime.create_snapshot()) == read_before, "configured report owns projection containers")
for patch: Dictionary in [{"subject_id": "missing"}, {"protagonist_id": "rival_rai"}, {"rival_id": "missing"}, {"shield_current": 999}, {"lust": "bad"}]:
var corrupt := saved.duplicate(true)
corrupt[&"systems"][&"runtime"][&"relationships"][&"original_relationships"][0].merge(patch, true)
var bad_bytes := var_to_bytes(corrupt)
var service_before: Variant = runtime.relationship_service
_expect(not runtime.restore_snapshot(corrupt), "bad original Runtime restore rejected")
_expect(runtime.relationship_service == service_before, "failed restore preserves service identity")
_expect(var_to_bytes(runtime.create_snapshot()) == read_before, "failed restore preserves whole Runtime")
_expect(var_to_bytes(corrupt) == bad_bytes, "failed restore preserves input")
var v1 := saved.duplicate(true)
v1[&"systems"][&"runtime"][&"relationships"] = saved[&"systems"][&"runtime"][&"relationships"][&"legacy"].duplicate(true)
_expect(runtime.restore_snapshot(v1), "Runtime accepts genuine v1 relationship payload")
_expect(not runtime.relationship_service.get_original_relationship(&"heroine_anri")[&"configured"], "v1 restore does not map legacy affection")
var invalid := fixture.duplicate(true)
invalid["rival_id"] = "missing"
_expect(not runtime.relationship_service.register_original_relationship(invalid).is_success(), "restore rebind keeps identity validator")
_expect(runtime.relationship_service.register_original_relationship(fixture).is_success(), "restored service accepts correct identities")
runtime.free()
clock.free()
state.free()
print("Original relationship runtime: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
@@ -0,0 +1 @@
uid://cqxwpbkgjtnsc