extends SceneTree const Digest = preload("res://scripts/core/simulation_digest.gd") const Reference = preload("res://tests/performance/fixtures/simulation_digest_v1_reference.gd") var _checks := 0 var _failures := 0 func _initialize() -> void: call_deferred("_run") func _run() -> void: print("Simulation digest: v1 byte compatibility and growing-history benchmark") _test_text_chunk_boundaries() _test_scalar_cache_boundaries() _test_value_types() _test_dictionary_layouts() _test_mutable_values() _test_limits_and_rejections() _test_growing_histories() print("Digest performance: %d checks, %d failures" % [_checks, _failures]) quit(_failures) func _test_value_types() -> void: # Escaped NUL was parsed as U+FFFD; keep identical values without import diagnostics. # Runtime NUL-attempt coverage remains in _test_text_chunk_boundaries. var values: Array = [ null, false, true, -9223372036854775807, 9223372036854775807, 0, 0.0, -0.0, -0.125, 1.25e100, "", "厨房" + String.chr(0xfffd) + "🌳", &"", &"運搬", Vector2(-1.5, 2.25), Vector2i(-3, 7), Rect2(-1.5, 2.25, 3.5, 4.75), Rect2i(-3, 7, 2, 8), Vector3(-1.5, 2.25, 3.5), Vector3i(-3, 7, 2), Vector4(-1.5, 2.25, 3.5, 4.75), Vector4i(-3, 7, 2, 8), Color(0.25, 0.5, 0.75, 1), [], {}, PackedByteArray([0, 127, 255]), PackedInt32Array([-3, 0, 7]), PackedInt64Array([-9223372036854775807, 9223372036854775807]), PackedFloat32Array([-1.25, 0.0, 2.5]), PackedFloat64Array([-1.25, -0.0, 1.25e100]), PackedStringArray(["", "日本語", "🌳"]), PackedVector2Array([Vector2(-1, 2)]), PackedVector3Array([Vector3(-1, 2, 3)]), PackedColorArray([Color(0.25, 0.5, 0.75, 1)]), ] var strings: Array[String] = ["", "a", "厨房"] var names: Array[StringName] = [&"", &"a", &"厨房"] values.append(strings) values.append(names) for index in values.size(): _expect_bytes(values[index], "canonical bytes for value type %d, sample %d" % [typeof(values[index]), index]) _expect_bytes({"values": values}, "nested complete supported value matrix") _expect(Digest.compute([1, 2, 3]) == "4e32bb2921e1b879d5b8778789d3bc7e46559d4448e95f9c2fed427d7e5c7b93", "published v1 reference digest remains unchanged") func _test_dictionary_layouts() -> void: var rows: Array = [] # Native Variant equality can equate String and StringName; cache buckets must # keep their type tags apart even when the textual schemas are identical. for index in 60: var row := {} var keys: Array = ["z", "aa", "日本語", "a", String.chr(0xfffd), "", "001"] if index % 2 == 1: keys.reverse() for key: String in keys: row[StringName(key) if index % 3 == 0 else key] = {"index": index, "key": key} rows.append(row) _expect_bytes(rows, "cached String and StringName schemas preserve tags and byte order") var mixed := {} mixed["text"] = "String" mixed[&"name"] = "StringName" mixed[7] = "integer" mixed[1.25] = "float" mixed[Vector2i(2, 3)] = "vector" mixed[["a", 1]] = "array key" mixed[{"inner": "key"}] = "dictionary key" _expect_bytes([mixed, mixed.duplicate(true)], "mixed and mutable dictionary keys use the uncached encoder") var forward := {"a": 1, "b": 2, "ccc": 3} var reverse := {"ccc": 3, "b": 2, "a": 1} _expect(Digest.canonical_bytes(forward) == Digest.canonical_bytes(reverse), "canonical order ignores dictionary insertion order") # Exceed each bound to check graceful uncached fallback, without relying on a # process-global cache or on the hash of an incomplete key signature. var many_schemas: Array = [] for index in 600: many_schemas.append({"schema_%d" % index: index, "nested": {"value": index}}) _expect_bytes(many_schemas, "more than the bounded layout capacity remains byte-identical") # These two complete key arrays collide under Godot 4.7's native hash. The # cache must still compare complete contents, never index by hash alone. var collision_left := {"schema.96ac180f": 1, "value": 2} var collision_right := {"schema.c5015a12": 3, "value": 4} if hash(collision_left.keys()) == hash(collision_right.keys()): print(" Native layout hash-collision fixture exercised") _expect_bytes([collision_left, collision_right, collision_left], "different key layouts with colliding native hashes remain distinct") var many_keys := {} for index in 80: many_keys["key_%d" % index] = index _expect_bytes(many_keys, "large dictionaries remain byte-identical") _expect_bytes({"長".repeat(200): 1}, "long keys bypass the cache") func _test_mutable_values() -> void: var nested := {"history": [{"minute": 1, "amount": 3}], "queue": [1, 2]} var before := Digest.compute(nested) nested["history"][0]["amount"] = 4 nested["queue"].append(3) _expect_bytes(nested, "nested value mutations cannot reuse stale encoded values") _expect(Digest.compute(nested) != before, "nested mutation changes the hash") nested.erase("queue") nested[&"queue"] = [1, 2] _expect_bytes(nested, "key type mutation between digest calls is observed") var left := {"key": {"value": 1}} var right := {"key": {"value": 2}} _expect_bytes([left, right], "reused schema reads each dictionary's current values") var canonical := Digest.canonical_bytes(nested) canonical.fill(0) _expect_bytes(nested, "caller mutation of previous output cannot corrupt another call") func _test_limits_and_rejections() -> void: var typed_leaf: Array[String] = ["leaf"] _expect_bytes(_nested(typed_leaf, 127), "typed string item at exact nesting limit is valid") _expect_bytes(_nested({"leaf": 1}, 127), "cached dictionary key at exact nesting limit is valid") _expect_bytes(_nested([], 128), "empty container at nesting limit is valid") var rejected: Array = [_nested(typed_leaf, 128), _nested({"leaf": 1}, 128), _nested(null, 129), NAN, INF, -INF, Vector2(NAN, 1), PackedFloat64Array([INF]), {"nested": NAN}, NodePath("runtime/object")] var object := RefCounted.new() rejected.append(object) var errors_enabled := Engine.print_error_messages Engine.print_error_messages = false for index in rejected.size(): var legacy: PackedByteArray = Reference.canonical_bytes(rejected[index]) var current: PackedByteArray = Digest.canonical_bytes(rejected[index]) _expect(legacy.is_empty() and current.is_empty(), "unsupported or over-depth sample %d still rejects" % index) Engine.print_error_messages = errors_enabled func _test_growing_histories() -> void: var reports: Array[Dictionary] = [] for history_count in [100, 1000, 5000]: var snapshot := _history_snapshot(history_count) _expect_bytes(snapshot, "growing-history snapshot with %d records" % history_count) _expect(Digest.compute(snapshot) == Reference.compute(snapshot), "growing-history SHA-256 with %d records" % history_count) # Warm both encoders, then alternate execution order. Timing is reported, # never asserted: CPU load and editor/preview activity vary across machines. Reference.compute(snapshot) Digest.compute(snapshot) var reference_samples: Array[float] = [] var optimized_samples: Array[float] = [] for sample in 5: if sample % 2 == 0: reference_samples.append(_measure(Reference.compute, snapshot)) optimized_samples.append(_measure(Digest.compute, snapshot)) else: optimized_samples.append(_measure(Digest.compute, snapshot)) reference_samples.append(_measure(Reference.compute, snapshot)) reference_samples.sort() optimized_samples.sort() var reference_ms: float = reference_samples[2] var optimized_ms: float = optimized_samples[2] var report := { "history_records": history_count, "canonical_bytes": Digest.canonical_bytes(snapshot).size(), "baseline_median_ms": snappedf(reference_ms, 0.001), "optimized_median_ms": snappedf(optimized_ms, 0.001), "speedup": snappedf(reference_ms / optimized_ms, 0.001), } reports.append(report) print("DIGEST_BENCHMARK %s" % JSON.stringify(report)) print("DIGEST_PERFORMANCE_REPORT %s" % JSON.stringify({"samples_per_encoder": 5, "metric": "median wall time including SHA-256", "fixture": "synthetic event, ledger, and service histories; complete histories retained", "results": reports})) var ids: Array[String] = [] for index in 10000: ids.append("facility.operation.finished:%08d:reception.1:guest.%08d" % [index, index / 4]) var id_snapshot := {"version": 1, "published_ids": ids, "recent_events": []} _expect_bytes(id_snapshot, "append-only published event ID history remains complete") var id_reference_samples: Array[float] = [] var id_optimized_samples: Array[float] = [] for sample in 5: id_reference_samples.append(_measure(Reference.compute, id_snapshot)) id_optimized_samples.append(_measure(Digest.compute, id_snapshot)) id_reference_samples.sort() id_optimized_samples.sort() print("DIGEST_EVENT_HISTORY_BENCHMARK %s" % JSON.stringify({"published_ids": ids.size(), "canonical_bytes": Digest.canonical_bytes(id_snapshot).size(), "baseline_median_ms": snappedf(id_reference_samples[2], 0.001), "optimized_median_ms": snappedf(id_optimized_samples[2], 0.001), "speedup": snappedf(id_reference_samples[2] / id_optimized_samples[2], 0.001)})) func _history_snapshot(count: int) -> Dictionary: var published_ids: Array[String] = [] var recent_events: Array[Dictionary] = [] var entries: Array[Dictionary] = [] var receipts: Array[Dictionary] = [] for index in count: var event_id := "event.economy.ledger.%08d" % index published_ids.append(event_id) entries.append({ "entry_id": "entry.%08d" % index, "absolute_minute": index * 3, "entry_type": "operating_expense" if index % 3 == 0 else "service_revenue", "amount": -12 if index % 3 == 0 else 35, "account_id": "cash", "counterparty_id": "guest.%05d" % (index % 250), "reference_id": event_id, "metadata": {"facility_id": "cafeteria.1", "quantity": 1, "description": "基本の食事"}, }) receipts.append({"receipt_id": "receipt.%08d" % index, "minute": index * 3, "guest_id": "guest.%05d" % (index % 250), "facility_id": "cafeteria.1", "satisfied": true, "price": 35}) if index >= maxi(count - 128, 0): recent_events.append({"event_id": event_id, "event_type": "ledger.posted", "absolute_minute": index * 3, "source_service": "economy", "entity_ids": ["cafeteria.1"], "payload": {"amount": 35, "quantity": 1}, "causation_command_id": index}) return {"version": 7, "minute": count * 3, "domain_events": {"version": 1, "published_ids": published_ids, "recent_events": recent_events}, "economy": {"version": 1, "balance": 5000 + count * 12, "entries": entries}, "operations": {"version": 1, "receipts": receipts}} func _nested(leaf: Variant, levels: int) -> Variant: var result: Variant = leaf for index in levels: result = [result] return result func _measure(encoder: Callable, snapshot: Dictionary) -> float: var started := Time.get_ticks_usec() encoder.call(snapshot) return float(Time.get_ticks_usec() - started) / 1000.0 func _expect_bytes(value: Variant, label: String) -> void: var expected: PackedByteArray = Reference.canonical_bytes(value) var actual: PackedByteArray = Digest.canonical_bytes(value) _expect(not expected.is_empty() and actual == expected, label) func _expect(condition: bool, label: String) -> void: _checks += 1 if condition: print(" PASS: %s" % label) else: _failures += 1 push_error(" FAIL: %s" % label) func _test_scalar_cache_boundaries() -> void: var values: Array = [] for i in 700: values.append(i-350) values.append("entry_%d" % i) values.append(StringName("entry_%d" % i)) values.append("x".repeat(129)) values.append(StringName("x".repeat(129))) values.append("utf8_" + String.chr(0x1f333)) values.append(values.duplicate()) _expect_bytes(values, "bounded scalar cache saturation preserves exact canonical bytes") _expect_bytes([1,"1",&"1",1,"1",&"1"], "scalar cache preserves int string and name type tags") var previous := Digest.compute(values) values[0] = -999999 _expect_bytes(values, "new digest reads changed input after prior cache lifetime") _expect(Digest.compute(values) != previous, "per-call scalar caches never retain previous input") func _test_text_chunk_boundaries() -> void: for count: int in [63,64,65,129]: var strings: Array[String] = [] var names: Array[StringName] = [] for index in count: var text := "field_%d:" % index + String.chr(0x1f333) + String.chr(0) + "end" strings.append(text) names.append(StringName(text)) _expect_bytes(strings, "UTF8 and NUL string chunk boundary %d" % count) _expect_bytes(names, "StringName chunk boundary %d" % count)