Files
TheTree_mock/tests/s1/test_spatial_subslot_service.gd

313 lines
18 KiB
GDScript

extends SceneTree
const SpatialSubslotServiceType = preload("res://scripts/world/spatial_subslot_service.gd")
const WorldServiceType = preload("res://scripts/world/world_service.gd")
const DigestType = preload("res://scripts/core/simulation_digest.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("S1: Spatial subslots")
_test_three_independent_slots_and_world_coexistence()
_test_revision_dirty_and_replacement()
_test_strict_input_limits()
_test_compatibility_callback_atomicity_and_reentry()
_test_snapshot_round_trip_and_corruption_rejection()
_test_deterministic_insertion_order()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
quit(_failures)
func _test_three_independent_slots_and_world_coexistence() -> void:
var world := WorldServiceType.new()
world.reset({&"min_x": -3, &"max_x": 3, &"min_y": -3, &"max_y": 3})
var shared := Vector3i(0, 0, 0)
_expect(world.occupy(&"major_facility", [shared], &"facility_large"), "major World occupant registers first")
var service := SpatialSubslotServiceType.new()
var route_cells := [Vector3i(1, 0, 0), shared, Vector3i(0, -1, 1)]
_expect(service.place(&"route_main", service.CATEGORY_ROUTE, route_cells).is_success(), "multi-cell ROUTE shares a primary World cell")
_expect(world.get_occupant(shared) == &"major_facility", "subslot placement never mutates primary World occupancy")
_expect(service.place(&"decor_banner", service.CATEGORY_DECORATION, [shared]).is_success(), "DECORATION shares the ROUTE cell")
_expect(service.place(&"utility_pipe", service.CATEGORY_UTILITY, [shared]).is_success(), "UTILITY shares ROUTE and DECORATION")
_expect(
service.get_cell_slots(shared) == {
&"ROUTE": &"route_main",
&"DECORATION": &"decor_banner",
&"UTILITY": &"utility_pipe",
},
"one layer cell exposes exactly three independent category slots"
)
_expect(
service.get_entity_cells(&"route_main") == [
Vector3i(0, -1, 1), shared, Vector3i(1, 0, 0),
],
"one entity owns multiple cells in canonical y/x/layer order"
)
_expect(
service.get_route_cells() == [Vector3i(0, -1, 1), shared, Vector3i(1, 0, 0)],
"route query is ready for deterministic Path graph input"
)
var before_overlap := service.create_snapshot()
_expect(not service.place(&"route_overlap", service.CATEGORY_ROUTE, [shared]).is_success(), "second ROUTE in one cell is rejected")
_expect(service.create_snapshot() == before_overlap, "same-category overlap rejection is atomic")
_expect(not service.place(&"route_main", service.CATEGORY_UTILITY, [Vector3i(2, 0, 0)]).is_success(), "one entity ID cannot own a second category")
var returned_cells := service.get_entity_cells(&"route_main")
returned_cells.clear()
var returned_entities := service.get_entities()
returned_entities[0][&"cells"].clear()
var returned_slots := service.get_cell_slots(shared)
returned_slots.clear()
_expect(service.get_entity_cells(&"route_main").size() == 3, "entity cell query is defensive")
_expect(service.get_entities().size() == 3, "entity records are defensive")
_expect(service.get_cell_slots(shared).size() == 3, "cell slot query is defensive")
_expect(service.validate_invariants()[&"ok"], "three-way coexistence preserves all crosslinks")
func _test_revision_dirty_and_replacement() -> void:
var service := SpatialSubslotServiceType.new()
var a := Vector3i(1, 1, 0)
var b := Vector3i(-1, 0, 1)
var c := Vector3i(2, -1, 0)
_expect(service.place(&"route", service.CATEGORY_ROUTE, [a, b]).is_success(), "first mutation succeeds")
_expect(service.revision == 1 and service.get_revision_for_category(service.CATEGORY_ROUTE) == 1, "ROUTE mutation advances global and category revisions once")
_expect(service.get_revision_for_category(service.CATEGORY_DECORATION) == 0, "unmodified category revision remains zero")
_expect(service.place(&"decor", service.CATEGORY_DECORATION, [a]).is_success(), "second category mutation succeeds")
_expect(service.revision == 2 and service.get_revision_for_category(service.CATEGORY_DECORATION) == 2, "DECORATION records its exact last revision")
_expect(service.get_dirty_cells() == [b, a], "dirty cells are canonical and deduplicated")
var consumed := service.consume_dirty_cells()
consumed.clear()
_expect(service.get_dirty_cells().is_empty() and service.revision == 2, "dirty consumption is defensive and does not advance revision")
var replacement := service.replace_entity_cells(&"route", [c, a])
_expect(replacement.is_success() and replacement.details[&"changed"], "entity cell set can be atomically replaced")
_expect(service.revision == 3 and service.get_revision_for_category(service.CATEGORY_ROUTE) == 3, "replacement advances only its category revision")
_expect(service.get_dirty_cells() == [c, b, a], "replacement dirties removed, retained and added cells once")
var no_op_revision := service.revision
_expect(service.replace_entity_cells(&"route", [a, c]).is_success(), "canonical-equivalent replacement is a successful no-op")
_expect(service.revision == no_op_revision, "replacement no-op does not change revision")
_expect(service.remove(&"decor").is_success(), "entity removal succeeds")
_expect(service.revision == 4 and service.get_revision_for_category(service.CATEGORY_DECORATION) == 4, "removal advances exact category revision")
_expect(service.get_slot_entity(a, service.CATEGORY_ROUTE) == &"route", "removal preserves another category in the same cell")
_expect(service.get_slot_entity(a, service.CATEGORY_DECORATION).is_empty(), "removal clears only the owned category slot")
_expect(not service.remove(&"decor").is_success(), "double removal is rejected")
_expect(service.validate_invariants()[&"ok"], "replacement and removal preserve strict crosslinks")
func _test_strict_input_limits() -> void:
var service := SpatialSubslotServiceType.new()
var valid_snapshot := service.create_snapshot()
_expect(not service.place(12, service.CATEGORY_ROUTE, [Vector3i.ZERO]).is_success(), "integer entity ID is rejected")
_expect(not service.place(&"", service.CATEGORY_ROUTE, [Vector3i.ZERO]).is_success(), "empty entity ID is rejected")
_expect(not service.place(StringName("x".repeat(service.MAX_ENTITY_ID_LENGTH + 1)), service.CATEGORY_ROUTE, [Vector3i.ZERO]).is_success(), "oversized entity ID is rejected")
_expect(not service.place(&"bad id", service.CATEGORY_ROUTE, [Vector3i.ZERO]).is_success(), "unstable entity ID is rejected")
_expect(not service.place(&"lower_category", &"route", [Vector3i.ZERO]).is_success(), "non-canonical category is rejected")
_expect(not service.place(&"numeric_category", 0, [Vector3i.ZERO]).is_success(), "numeric category is rejected rather than coerced")
_expect(not service.place(&"not_array", service.CATEGORY_ROUTE, Vector3i.ZERO).is_success(), "non-Array cells are rejected")
_expect(not service.place(&"empty_cells", service.CATEGORY_ROUTE, []).is_success(), "empty cell set is rejected")
_expect(not service.place(&"duplicate_cells", service.CATEGORY_ROUTE, [Vector3i.ZERO, Vector3i.ZERO]).is_success(), "duplicate cells are rejected")
_expect(not service.place(&"wrong_cell_type", service.CATEGORY_ROUTE, [Vector2i.ZERO]).is_success(), "non-Vector3i cell is rejected")
_expect(not service.place(&"wrong_layer", service.CATEGORY_ROUTE, [Vector3i(0, 0, 2)]).is_success(), "unknown layer is rejected")
_expect(not service.place(&"deep_coordinate", service.CATEGORY_ROUTE, [Vector3i(0, service.MAX_COORDINATE_ABS + 1, 0)]).is_success(), "coordinate depth limit is enforced")
var too_many_cells: Array[Vector3i] = []
for x: int in range(service.MAX_CELLS_PER_ENTITY + 1):
too_many_cells.append(Vector3i(x, 0, 0))
_expect(not service.place(&"too_many_cells", service.CATEGORY_ROUTE, too_many_cells).is_success(), "per-entity cell capacity is enforced")
_expect(service.create_snapshot() == valid_snapshot and service.revision == 0, "all malformed input failures preserve the empty state")
_expect(service.get_entity_ids(&"route").is_empty(), "invalid category query fails closed")
_expect(service.get_revision_for_category(&"route") == -1, "invalid category revision query fails closed")
func _test_compatibility_callback_atomicity_and_reentry() -> void:
var service := SpatialSubslotServiceType.new()
var callback_payloads: Array[Dictionary] = []
service.configure_compatibility_validator(
func(payload: Dictionary) -> Dictionary:
callback_payloads.append(payload.duplicate(true))
payload[&"after_cells"].clear()
return {&"ok": true, &"details": {&"pass_through": true}}
)
_expect(service.place(&"pass_route", service.CATEGORY_ROUTE, [Vector3i.ZERO]).is_success(), "pass-through validator can approve placement")
_expect(callback_payloads[0][&"operation"] == &"PLACE", "validator receives explicit operation context")
_expect(callback_payloads[0][&"after_cells"] == [Vector3i.ZERO], "validator payload captured by caller retains exact candidate")
_expect(service.get_entity_cells(&"pass_route") == [Vector3i.ZERO], "callback payload mutation cannot alter committed cells")
var callback_report := service.get_last_callback_report()
callback_report[&"details"].clear()
_expect(service.get_last_callback_report()[&"details"] == {&"pass_through": true}, "callback report is a defensive deep copy")
service.configure_compatibility_validator(
func(_payload: Dictionary) -> Dictionary:
return {&"ok": false, &"reason": &"PRIMARY_BLOCKS_CATEGORY"}
)
var before_rejection := service.create_snapshot()
var rejected = service.replace_entity_cells(&"pass_route", [Vector3i(1, 0, 0)])
_expect(not rejected.is_success() and rejected.details[&"rollback_ok"], "callback rejection reports successful pre-commit rollback")
_expect(service.create_snapshot() == before_rejection, "callback failure preserves snapshot and revision exactly")
service.configure_compatibility_validator(
func(_payload: Dictionary) -> Variant:
return true
)
_expect(not service.place(&"invalid_response", service.CATEGORY_UTILITY, [Vector3i(2, 0, 0)]).is_success(), "non-Dictionary callback response is rejected")
_expect(service.create_snapshot() == before_rejection, "invalid callback response is atomic")
var nested_result: Array = [null]
var configure_during_callback: Array = [true]
service.configure_compatibility_validator(
func(_payload: Dictionary) -> Dictionary:
nested_result[0] = service.place(&"nested", service.CATEGORY_DECORATION, [Vector3i(3, 0, 0)])
configure_during_callback[0] = service.configure_compatibility_validator(Callable())
return {&"ok": true}
)
_expect(service.place(&"outer", service.CATEGORY_DECORATION, [Vector3i(2, 0, 0)]).is_success(), "outer callback mutation commits")
_expect(nested_result[0] != null and not nested_result[0].is_success(), "callback reentry is rejected")
_expect(not configure_during_callback[0], "callback cannot replace its own boundary during mutation")
_expect(service.has_entity(&"outer") and not service.has_entity(&"nested"), "reentry rejection commits only the outer entity")
service.configure_compatibility_validator(
func(_payload: Dictionary) -> Dictionary:
service._entity_cells[&"tampered"] = [Vector3i(9, 9, 0)]
return {&"ok": true}
)
var before_tamper := service.create_snapshot()
var tamper_result = service.place(&"tamper_probe", service.CATEGORY_UTILITY, [Vector3i(4, 0, 0)])
_expect(not tamper_result.is_success() and tamper_result.details[&"rollback_ok"], "direct callback tamper is detected and rolled back")
_expect(service.create_snapshot() == before_tamper and not service.has_entity(&"tampered"), "callback tamper rollback restores every internal crosslink")
_expect(service.get_last_callback_report()[&"tampered"], "callback report records tamper detection")
service.configure_compatibility_validator(
func(_payload: Dictionary) -> Dictionary:
service._compatibility_validator = Callable()
return {&"ok": true}
)
var before_boundary_tamper := service.create_snapshot()
var boundary_tamper_result = service.place(&"boundary_tamper", service.CATEGORY_UTILITY, [Vector3i(5, 0, 0)])
_expect(not boundary_tamper_result.is_success() and boundary_tamper_result.details[&"rollback_ok"], "callback cannot replace its validator boundary directly")
_expect(service.create_snapshot() == before_boundary_tamper, "validator-boundary tamper rollback is atomic")
service.configure_compatibility_validator(Callable())
func _test_snapshot_round_trip_and_corruption_rejection() -> void:
var source := SpatialSubslotServiceType.new()
source.place(&"z_route", source.CATEGORY_ROUTE, [Vector3i(2, 0, 0), Vector3i(1, -1, 1)])
source.place(&"a_decor", source.CATEGORY_DECORATION, [Vector3i(2, 0, 0)])
source.place(&"m_utility", source.CATEGORY_UTILITY, [Vector3i(-1, 2, 1)])
source.consume_dirty_cells()
source.replace_entity_cells(&"z_route", [Vector3i(3, 0, 0), Vector3i(1, -1, 1)])
var snapshot := source.create_snapshot()
_expect(snapshot[&"entities"].map(func(row: Dictionary) -> String: return row[&"entity_id"]) == ["a_decor", "m_utility", "z_route"], "snapshot entities are lexical")
var restored := SpatialSubslotServiceType.new()
_expect(restored.restore_snapshot(snapshot), "strict current snapshot restores")
_expect(restored.create_snapshot() == snapshot, "snapshot round trip is exact")
_expect(DigestType.compute(restored.create_snapshot()) == DigestType.compute(snapshot), "snapshot digest round trip is deterministic")
_expect(restored.validate_invariants()[&"ok"], "restored entity/category/cell graph is valid")
var before := restored.create_snapshot()
var version_type := snapshot.duplicate(true)
version_type[&"schema_version"] = "1"
_atomic_reject(restored, version_type, before, "string schema version")
var unknown_field := snapshot.duplicate(true)
unknown_field[&"unknown"] = true
_atomic_reject(restored, unknown_field, before, "unknown top-level field")
var revision_type := snapshot.duplicate(true)
revision_type[&"revision"] = 4.0
_atomic_reject(restored, revision_type, before, "floating revision")
var revision_bound := snapshot.duplicate(true)
revision_bound[&"revision"] = source.MAX_REVISION + 1
_atomic_reject(restored, revision_bound, before, "oversized revision")
var category_keys := snapshot.duplicate(true)
category_keys[&"category_revisions"].erase(&"UTILITY")
_atomic_reject(restored, category_keys, before, "missing category revision")
var category_revision := snapshot.duplicate(true)
category_revision[&"category_revisions"][&"ROUTE"] = int(category_revision[&"revision"]) + 1
_atomic_reject(restored, category_revision, before, "category revision above global revision")
var missing_latest_category := snapshot.duplicate(true)
missing_latest_category[&"category_revisions"][&"ROUTE"] = int(missing_latest_category[&"revision"]) - 1
_atomic_reject(restored, missing_latest_category, before, "no category at global revision")
var entity_order := snapshot.duplicate(true)
entity_order[&"entities"].reverse()
_atomic_reject(restored, entity_order, before, "unordered entities")
var entity_extra := snapshot.duplicate(true)
entity_extra[&"entities"][0][&"extra"] = 1
_atomic_reject(restored, entity_extra, before, "unknown entity field")
var entity_id_type := snapshot.duplicate(true)
entity_id_type[&"entities"][0][&"entity_id"] = 1
_atomic_reject(restored, entity_id_type, before, "non-string entity ID")
var invalid_category := snapshot.duplicate(true)
invalid_category[&"entities"][0][&"category"] = "DECOR"
_atomic_reject(restored, invalid_category, before, "unknown category")
var unsorted_cells := snapshot.duplicate(true)
unsorted_cells[&"entities"][2][&"cells"].reverse()
_atomic_reject(restored, unsorted_cells, before, "unordered entity cells")
var duplicate_category_slot := snapshot.duplicate(true)
duplicate_category_slot[&"entities"].insert(2, {
&"entity_id": "n_route",
&"category": "ROUTE",
&"cells": [Vector3i(3, 0, 0)],
})
_atomic_reject(restored, duplicate_category_slot, before, "duplicate category slot")
var invalid_cell := snapshot.duplicate(true)
invalid_cell[&"entities"][0][&"cells"] = [Vector3i(0, 0, 2)]
_atomic_reject(restored, invalid_cell, before, "invalid entity layer")
var dirty_order := snapshot.duplicate(true)
dirty_order[&"dirty_cells"].reverse()
_atomic_reject(restored, dirty_order, before, "unordered dirty cells")
var dirty_duplicate := snapshot.duplicate(true)
dirty_duplicate[&"dirty_cells"].append(dirty_duplicate[&"dirty_cells"][0])
_atomic_reject(restored, dirty_duplicate, before, "duplicate dirty cell")
var depth := snapshot.duplicate(true)
var deep_value: Variant = []
for _index: int in range(source.MAX_SNAPSHOT_DEPTH + 2):
deep_value = [deep_value]
depth[&"entities"] = deep_value
_atomic_reject(restored, depth, before, "excessive snapshot depth")
var entity_capacity := snapshot.duplicate(true)
var oversized_entities: Array = []
oversized_entities.resize(source.MAX_ENTITIES + 1)
entity_capacity[&"entities"] = oversized_entities
_atomic_reject(restored, entity_capacity, before, "entity collection capacity")
var internal_before := restored.create_snapshot()
restored._slots[Vector3i(99, 99, 0)] = {restored.CATEGORY_ROUTE: &"ghost"}
_expect(not restored.validate_invariants()[&"ok"], "live invariant audit detects an orphan cell-side slot")
_expect(restored.restore_snapshot(internal_before), "valid snapshot repairs audited private corruption")
func _test_deterministic_insertion_order() -> void:
var first := SpatialSubslotServiceType.new()
var second := SpatialSubslotServiceType.new()
first.place(&"z", first.CATEGORY_ROUTE, [Vector3i(2, 0, 1), Vector3i(-2, 0, 0)])
first.place(&"a", first.CATEGORY_ROUTE, [Vector3i(0, 1, 0)])
second.place(&"a", second.CATEGORY_ROUTE, [Vector3i(0, 1, 0)])
second.place(&"z", second.CATEGORY_ROUTE, [Vector3i(-2, 0, 0), Vector3i(2, 0, 1)])
_expect(first.create_snapshot() == second.create_snapshot(), "snapshot ignores Dictionary and input-cell insertion order")
_expect(first.get_route_cells() == second.get_route_cells(), "route projection order is deterministic")
func _atomic_reject(
service: SpatialSubslotService,
candidate: Dictionary,
before: Dictionary,
label: String
) -> void:
_expect(not service.restore_snapshot(candidate), "%s is rejected" % label)
_expect(service.create_snapshot() == before, "%s rejection is atomic" % label)
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
print(" ✓ %s" % label)
return
_failures += 1
push_error(" ✗ %s" % label)