P08-03: 配置の通常操作受入
This commit is contained in:
+36
-5
@@ -117,10 +117,17 @@ def make(base, source, checker):
|
||||
folder.mkdir()
|
||||
runtime = {k: {} for k in sorted(checker.RUNTIME_KEYS)}
|
||||
runtime.update(world={'occupancies': [], 'bounds':{'min_x':-10,'max_y':10}}, construction={'facilities': [], 'jobs': []},
|
||||
id_generator={'version':1,'counters':{'domain_event':7}},
|
||||
domain_events={'version':1,'published_ids':[],'recent_events':[]},
|
||||
facilities={'instances': []}, next_command_id=1,
|
||||
command_queue={'processed': [], 'pending': [], 'in_flight': [], 'accepted': [], 'idempotency': {}})
|
||||
model = {'schema_version': 1, 'clock': {'absolute_minute': 360, 'paused': True},
|
||||
'game_state': {'resources': {'gold': 5000, 'materials': 50}}, 'systems': {'runtime': runtime}}
|
||||
runtime['domain_events']['published_ids'] = ['domain_event.%012d' % n for n in range(1,8)]
|
||||
runtime['domain_events']['recent_events'] = [
|
||||
{'event_id':'domain_event.%012d' % n, 'event_type':'SYNTHETIC_HISTORY',
|
||||
'absolute_minute':360, 'source_service':'synthetic', 'entity_ids':[],
|
||||
'payload':{'ordinal':n}, 'causation_command_id':0} for n in range(1,8)]
|
||||
target = [-8, 0, 0]
|
||||
cost = {'reception': {'gold': 500, 'materials': 8}, 'guest_stairs': {'gold': 40, 'materials': 0}, 'standard_room':{'gold':350,'materials':5}}
|
||||
rooms = 0
|
||||
@@ -236,8 +243,10 @@ def make(base, source, checker):
|
||||
budget = {'setup': 'diagnostic', 'costs': [{'definition': d, 'cost': cost[d]} for d in ('reception', 'reception', 'guest_stairs', 'guest_stairs')],
|
||||
'needed': {'gold': 1080, 'materials': 16}, 'initial': {'gold': 5000, 'materials': 50}, 'deltas': {}}
|
||||
put(folder/'writer-actions.json', {'setup': 'diagnostic', 'actions': actions, 'budget': budget})
|
||||
snap('writer-checkpoint')
|
||||
snap('reader-checkpoint')
|
||||
snap('writer-presave')
|
||||
snap('writer-formal-model')
|
||||
snap('reader-formal-model')
|
||||
formal_model = typed(model)
|
||||
formal = folder/'formal'
|
||||
formal.mkdir()
|
||||
for member in checker.MEMBERS: (formal/member).write_text('synthetic '+member+'\n')
|
||||
@@ -251,14 +260,36 @@ def make(base, source, checker):
|
||||
'members':{n:{'sha256':sha(folder/name/n),'bytes':(folder/name/n).stat().st_size} for n in checker.MEMBERS}}
|
||||
for name,suffix in [('formal',''),('formal.bak','.bak')]},
|
||||
'slot_metadata':{'file':'formal-slot-metadata.json','sha256':sha(formal/'manifest.json')}})
|
||||
for role,pid,op in [('writer',pids[0],'save'),('reader',pids[1],'load')]:
|
||||
for role,pid,op in [('writer',pids[0],'save'),('reader',pids[1],'recovery')]:
|
||||
returned = {'ok':True,'operation':op,'save_path':'user://saves/manual_ui',
|
||||
'code':'OK','message':'synthetic result','duration_us':13,'runtime_duration_us':17,
|
||||
'manifest':{'save_id':'synthetic-save','absolute_minute':360}}
|
||||
if role == 'reader':
|
||||
returned['selected'] = {'id':'formal','kind':'formal','generation':0}
|
||||
expected = checker.expected_operation(formal_model,returned,
|
||||
'SAVE_CHECKPOINT_SUCCEEDED' if role == 'writer' else 'SAVE_RECOVERY_LOADED')
|
||||
for label in ('checkpoint','expected-operation','operation-actual'):
|
||||
put(folder/(role+'-'+label+'.json'),expected)
|
||||
(folder/(role+'-'+label+'.var')).write_bytes(json.dumps(expected,sort_keys=True).encode())
|
||||
count = len(model['systems']['runtime']['domain_events']['recent_events'])
|
||||
put(folder/(role+'-operation-comparison.json'), {'pid':pid,'delta_kind':'source_defined_notification',
|
||||
'formal_model':role+'-formal-model','actual_model':role+'-checkpoint',
|
||||
'expected_model':role+'-expected-operation','returned_result':typed(returned),
|
||||
'saved_recent_count':count,'expected_recent_count':count+1,'actual_recent_count':count+1,
|
||||
'whole_raw_equal':True})
|
||||
stem = role+'-save-result-0001'
|
||||
(folder/(stem+'.var')).write_bytes(b'synthetic raw Dictionary stand-in')
|
||||
put(folder/(stem+'.json'), {'pid':pid,'operation':'runtime_operation',
|
||||
'requested_path':'user://saves/manual_ui','absolute_path':absolute,'user_data_dir':profile,
|
||||
'result':typed({'ok':True,'operation':op,'save_path':'user://saves/manual_ui'}),
|
||||
'result':typed(returned),
|
||||
'raw_sha256':sha(folder/(stem+'.var'))})
|
||||
put(folder/(role+'-save-results.json'), {'pid':pid,'records':[stem]})
|
||||
inspect_stem = role+'-save-result-0002'
|
||||
(folder/(inspect_stem+'.var')).write_bytes(b'synthetic inspection Dictionary stand-in')
|
||||
put(folder/(inspect_stem+'.json'), {'pid':pid,'operation':'formal_inspect',
|
||||
'requested_path':'user://saves/manual_ui','absolute_path':absolute,'user_data_dir':profile,
|
||||
'result':typed({'ok':True,'read_only':True,'snapshot':copy.deepcopy(model)}),
|
||||
'raw_sha256':sha(folder/(inspect_stem+'.var'))})
|
||||
put(folder/(role+'-save-results.json'), {'pid':pid,'records':[stem,inspect_stem]})
|
||||
put(folder/'save.json', {'members': {n: sha(formal/n) for n in checker.MEMBERS},
|
||||
'pid': pids[0], 'cell': target, 'instance_id': 'f4'})
|
||||
row = {'id': 'resume_overlap', 'kind': 'build', 'position': position(target),
|
||||
|
||||
Vendored
+74
-3
@@ -20,6 +20,7 @@ var input_method := "handler"
|
||||
var observed_cell: Variant = null
|
||||
var save_serial := 0
|
||||
var save_records: Array = []
|
||||
var operation_results: Array[Dictionary] = []
|
||||
const INITIAL_SAVE := "user://saves/p08_initial"
|
||||
|
||||
func annotate(row: Dictionary) -> void:
|
||||
@@ -43,8 +44,10 @@ func json_file(name: String, data: Variant) -> void:
|
||||
file.close()
|
||||
|
||||
func capture(label: String) -> String:
|
||||
return capture_model(label, runtime.create_snapshot())
|
||||
|
||||
func capture_model(label: String, snapshot: Dictionary) -> String:
|
||||
var name := role + "-" + label
|
||||
var snapshot: Dictionary = runtime.create_snapshot()
|
||||
json_file(name + ".json", Codec.new().typed(snapshot))
|
||||
var file := FileAccess.open(directory.path_join(name + ".var"), FileAccess.WRITE)
|
||||
if file != null:
|
||||
@@ -226,10 +229,15 @@ func writer() -> void:
|
||||
await demolish(target, "demolition")
|
||||
await build(&"reception", target, "rebuild")
|
||||
await camera()
|
||||
capture("presave")
|
||||
await button(main.find_child("ManualSaveButton", true, false))
|
||||
expect(main.build_status_label.text == "手動セーブを保存しました。", "normal save")
|
||||
capture("checkpoint")
|
||||
await archive_formal()
|
||||
var formal := inspect_formal()
|
||||
var presave: Dictionary = bytes_to_var(FileAccess.get_file_as_bytes(directory.path_join("writer-presave.var")))
|
||||
expect(var_to_bytes(presave) == var_to_bytes(formal), "whole presave formal model")
|
||||
verify_operation(formal, operation_results.back(), &"SAVE_CHECKPOINT_SUCCEEDED")
|
||||
var members := {}
|
||||
for member: String in MEMBERS:
|
||||
members[member] = FileAccess.get_sha256(directory.path_join("formal/" + member))
|
||||
@@ -245,8 +253,10 @@ func reader(distinct: bool) -> void:
|
||||
await frames()
|
||||
expect(main.build_status_label.text == "手動セーブを読み込みました。", "normal resume")
|
||||
capture("checkpoint")
|
||||
var original := FileAccess.get_file_as_bytes(directory.path_join("writer-checkpoint.var"))
|
||||
expect(original == var_to_bytes(runtime.create_snapshot()), "whole formal resume model")
|
||||
var formal := inspect_formal()
|
||||
var original := FileAccess.get_file_as_bytes(directory.path_join("writer-formal-model.var"))
|
||||
expect(original == var_to_bytes(formal), "whole formal resume model")
|
||||
verify_operation(formal, operation_results.back(), &"SAVE_RECOVERY_LOADED")
|
||||
expect(String(runtime.world_service.get_occupant(target)) == saved.instance_id, "same resumed instance/cell")
|
||||
for member: String in MEMBERS:
|
||||
expect(FileAccess.get_sha256(directory.path_join("formal/" + member)) == saved.members[member], "reader preserves formal member " + member)
|
||||
@@ -325,7 +335,68 @@ func save_initial_fixture() -> void:
|
||||
func archive_formal() -> void:
|
||||
export_package(String(main.manual_save_path), "formal")
|
||||
|
||||
func inspect_formal() -> Dictionary:
|
||||
var stored := SaveService.inspect_snapshot(String(main.manual_save_path))
|
||||
record_save_result("formal_inspect", String(main.manual_save_path), stored)
|
||||
expect(bool(stored.get(&"ok", false)), "read-only formal inspection")
|
||||
var formal: Dictionary = stored.get(&"snapshot", {})
|
||||
capture_model("formal-model", formal)
|
||||
return formal
|
||||
|
||||
func verify_operation(formal: Dictionary, result: Dictionary, event_type: StringName) -> void:
|
||||
var expected := formal.duplicate(true)
|
||||
var model: Dictionary = expected[&"systems"][&"runtime"]
|
||||
var counters: Dictionary = model[&"id_generator"]["counters"]
|
||||
var next: int = int(counters.get("domain_event", 0)) + 1
|
||||
counters["domain_event"] = next
|
||||
var event_id := "domain_event.%012d" % next
|
||||
var payload := {
|
||||
&"ok": bool(result.get(&"ok", false)),
|
||||
&"code": StringName(result.get(&"code", &"UNKNOWN")),
|
||||
&"message": str(result.get(&"message", "")),
|
||||
&"operation": StringName(result.get(&"operation", &"unknown")),
|
||||
&"save_path": str(result.get(&"save_path", result.get(&"path", ""))),
|
||||
&"reason": StringName(result.get(&"reason", &"")),
|
||||
&"request_id": int(result.get(&"request_id", 0)),
|
||||
&"checkpoint_absolute_minute": int(result.get(&"checkpoint_absolute_minute", formal[&"clock"][&"absolute_minute"])),
|
||||
&"duration_us": int(result.get(&"duration_us", 0)),
|
||||
&"runtime_duration_us": int(result.get(&"runtime_duration_us", 0)),
|
||||
}
|
||||
if result.get(&"selected") is Dictionary:
|
||||
var selected: Dictionary = result[&"selected"]
|
||||
payload[&"selected_candidate_id"] = StringName(selected.get(&"id", &""))
|
||||
payload[&"selected_candidate_kind"] = StringName(selected.get(&"kind", &""))
|
||||
payload[&"selected_generation"] = int(selected.get(&"generation", -1))
|
||||
if result.get(&"manifest") is Dictionary:
|
||||
var manifest: Dictionary = result[&"manifest"]
|
||||
payload[&"save_id"] = str(manifest.get("save_id", ""))
|
||||
payload[&"saved_absolute_minute"] = int(manifest.get("absolute_minute", -1))
|
||||
var stream: Dictionary = model[&"domain_events"]
|
||||
stream["published_ids"].append(event_id)
|
||||
stream["published_ids"].sort()
|
||||
var notification := SimulationDomainEvent.new(StringName(event_id), event_type,
|
||||
int(formal[&"clock"][&"absolute_minute"]), &"save_service", [], payload, 0)
|
||||
stream["recent_events"].append(notification.to_dictionary())
|
||||
if stream["recent_events"].size() > DomainEventStream.RECENT_EVENT_LIMIT:
|
||||
stream["recent_events"].pop_front()
|
||||
capture_model("expected-operation", expected)
|
||||
capture("operation-actual")
|
||||
var actual: Dictionary = bytes_to_var(FileAccess.get_file_as_bytes(directory.path_join(role + "-checkpoint.var")))
|
||||
json_file(role + "-operation-comparison.json", {
|
||||
"pid": OS.get_process_id(), "delta_kind": "source_defined_notification",
|
||||
"formal_model": role + "-formal-model", "actual_model": role + "-checkpoint",
|
||||
"expected_model": role + "-expected-operation", "returned_result": Codec.new().typed(result),
|
||||
"saved_recent_count": formal[&"systems"][&"runtime"][&"domain_events"]["recent_events"].size(),
|
||||
"expected_recent_count": stream["recent_events"].size(),
|
||||
"actual_recent_count": actual[&"systems"][&"runtime"][&"domain_events"]["recent_events"].size(),
|
||||
"whole_raw_equal": var_to_bytes(expected) == var_to_bytes(actual),
|
||||
})
|
||||
# Complete raw actual/expected snapshots remain available on unknown drift.
|
||||
expect(var_to_bytes(expected) == var_to_bytes(actual), "whole source-defined save operation model; see " + role + "-operation-comparison.json")
|
||||
expect(var_to_bytes(actual) == var_to_bytes(runtime.create_snapshot()), "read-only inspection preserves live model")
|
||||
|
||||
func record_save_operation(result: Dictionary) -> void:
|
||||
operation_results.append(result.duplicate(true))
|
||||
record_save_result("runtime_operation", String(main.manual_save_path), result)
|
||||
|
||||
func record_save_result(operation: String, path: String, result: Dictionary) -> void:
|
||||
|
||||
Reference in New Issue
Block a user