class_name WorldService extends RefCounted ## Deterministic logical-world storage for S1. ## ## Vector3i uses (x, y, layer), where layer 0 is FRONT and layer 1 is BACK. ## All collection-returning APIs use a stable y/x/layer or entity-id order. const SNAPSHOT_SCHEMA_VERSION := 1 const LAYER_FRONT := 0 const LAYER_BACK := 1 const ZONE_INVALID := &"INVALID" const ZONE_ABOVE_GROUND := &"ABOVE_GROUND" const ZONE_BOUNDARY := &"BOUNDARY" const ZONE_UNDERGROUND := &"UNDERGROUND" const ERROR_EMPTY_CELLS := &"EMPTY_CELLS" const ERROR_INVALID_CELL_TYPE := &"INVALID_CELL_TYPE" const ERROR_OUT_OF_BOUNDS := &"OUT_OF_BOUNDS" const ERROR_DUPLICATE_CELL := &"DUPLICATE_CELL" const ERROR_LAYER_MISMATCH := &"LAYER_MISMATCH" const ERROR_OCCUPIED := &"OCCUPIED" const ERROR_ENTITY_EXISTS := &"ENTITY_EXISTS" const ERROR_INVALID_ENTITY_ID := &"INVALID_ENTITY_ID" const ERROR_INVALID_KIND := &"INVALID_KIND" const ERROR_INVALID_WEIGHT := &"INVALID_WEIGHT" const ERROR_UNSUPPORTED := &"UNSUPPORTED" var revision := 0 var dirty_revision: int: get: return revision var occupancy_revision := 0 var walkability_revision := 0 var excavation_revision := 0 var support_revision := 0 var _bounds := { &"min_x": 0, &"max_x": -1, &"min_y": 0, &"max_y": -1, } var _occupants: Dictionary = {} var _entity_cells: Dictionary = {} var _entity_kinds: Dictionary = {} var _walkable: Dictionary = {} var _excavated: Dictionary = {} var _support_capacity: Dictionary = {} var _support_anchors: Dictionary = {} var _dirty_cells: Dictionary = {} var _snapshot_geometry: Dictionary = {} var _snapshot_geometry_revision := -1 func reset(bounds: Variant) -> bool: var normalized := _normalize_bounds(bounds) if normalized.is_empty(): return false _snapshot_geometry.clear() _snapshot_geometry_revision = -1 _bounds = normalized _occupants.clear() _entity_cells.clear() _entity_kinds.clear() _walkable.clear() _excavated.clear() _support_capacity.clear() _support_anchors.clear() _dirty_cells.clear() revision = 0 occupancy_revision = 0 walkability_revision = 0 excavation_revision = 0 support_revision = 0 return true func get_bounds() -> Dictionary: return _bounds.duplicate(true) func expand_bounds(bounds: Variant) -> bool: var normalized := _normalize_bounds(bounds) if normalized.is_empty(): return false if ( int(normalized[&"min_x"]) > int(_bounds[&"min_x"]) or int(normalized[&"max_x"]) < int(_bounds[&"max_x"]) or int(normalized[&"min_y"]) > int(_bounds[&"min_y"]) or int(normalized[&"max_y"]) < int(_bounds[&"max_y"]) ): return false if normalized == _bounds: return true _bounds = normalized revision += 1 return true func is_in_bounds(cell: Variant) -> bool: return cell is Vector3i and _is_in_bounds_with(cell, _bounds) func get_zone(cell_or_y: Variant) -> StringName: var y: int if cell_or_y is Vector3i or cell_or_y is Vector2i: y = cell_or_y.y elif cell_or_y is int: y = int(cell_or_y) else: return ZONE_INVALID if y > 0: return ZONE_ABOVE_GROUND if y < 0: return ZONE_UNDERGROUND return ZONE_BOUNDARY func cells_for_footprint(origin: Vector3i, footprint: Variant) -> Array[Vector3i]: var result: Array[Vector3i] = [] if origin.z not in [LAYER_FRONT, LAYER_BACK]: return result if footprint is Vector2i: if footprint.x <= 0 or footprint.y <= 0: return result for y_offset in range(footprint.y): for x_offset in range(footprint.x): result.append(origin + Vector3i(x_offset, y_offset, 0)) return result if footprint is Array: for offset: Variant in footprint: if not offset is Vector2i: return [] result.append(origin + Vector3i(offset.x, offset.y, 0)) result.sort_custom(_cell_less) return result func validate_occupancy(cells: Array, entity_id: StringName = &"") -> Dictionary: var report := _validate_cells(cells) var occupied_cells: Array[Vector3i] = [] for cell: Vector3i in report[&"valid_cells"]: if _occupants.has(cell): occupied_cells.append(cell) if not occupied_cells.is_empty(): _add_error(report[&"errors"], ERROR_OCCUPIED) if not entity_id.is_empty() and _entity_cells.has(entity_id): _add_error(report[&"errors"], ERROR_ENTITY_EXISTS) report[&"occupied_cells"] = occupied_cells report[&"existing_entity"] = not entity_id.is_empty() and _entity_cells.has(entity_id) report[&"ok"] = report[&"errors"].is_empty() return report func occupy(entity_id: StringName, cells: Array, kind: StringName = &"facility") -> bool: if entity_id.is_empty() or kind.is_empty(): return false var report := validate_occupancy(cells, entity_id) if not report[&"ok"]: return false var canonical_cells: Array[Vector3i] = report[&"valid_cells"].duplicate() canonical_cells.sort_custom(_cell_less) for cell: Vector3i in canonical_cells: _occupants[cell] = { &"entity_id": entity_id, &"kind": kind, } _entity_cells[entity_id] = canonical_cells _entity_kinds[entity_id] = kind var structural := _kind_support_capacity(kind) > 0 or _kind_weight(kind) > 0 or _kind_is_anchor(kind) _mark_dirty(canonical_cells, true, false, false, structural) return true func release(entity_id: StringName) -> bool: if entity_id.is_empty() or not _entity_cells.has(entity_id): return false var cells: Array[Vector3i] = _copy_cell_array(_entity_cells[entity_id]) var kind: StringName = _entity_kinds.get(entity_id, &"") for cell: Vector3i in cells: var occupant: Dictionary = _occupants.get(cell, {}) if occupant.get(&"entity_id", &"") == entity_id: _occupants.erase(cell) _entity_cells.erase(entity_id) _entity_kinds.erase(entity_id) var structural := _kind_support_capacity(kind) > 0 or _kind_weight(kind) > 0 or _kind_is_anchor(kind) _mark_dirty(cells, true, false, false, structural) return true func get_occupant(cell: Vector3i) -> StringName: var occupant: Dictionary = _occupants.get(cell, {}) return occupant.get(&"entity_id", &"") func get_occupant_kind(cell: Vector3i) -> StringName: var occupant: Dictionary = _occupants.get(cell, {}) return occupant.get(&"kind", &"") func get_entity_cells(entity_id: StringName) -> Array[Vector3i]: if not _entity_cells.has(entity_id): return [] return _copy_cell_array(_entity_cells[entity_id]) func get_entity_kind(entity_id: StringName) -> StringName: return _entity_kinds.get(entity_id, &"") func set_walkable(cell: Vector3i, value: bool) -> bool: if not is_in_bounds(cell): return false var previous := bool(_walkable.get(cell, false)) if previous == value: return true if value: _walkable[cell] = true else: _walkable.erase(cell) _mark_dirty([cell], false, true, false, false) return true func is_walkable(cell: Vector3i) -> bool: return is_in_bounds(cell) and bool(_walkable.get(cell, false)) func get_walkable_cells(sort_result := true) -> Array[Vector3i]: return _sorted_dictionary_cells(_walkable, sort_result) func set_excavated(cell: Vector3i, value: bool) -> bool: if not is_in_bounds(cell): return false var spatial_key := Vector2i(cell.x, cell.y) var previous := is_excavated(cell) if previous == value: return true var default_value := cell.y >= 0 if value == default_value: _excavated.erase(spatial_key) else: _excavated[spatial_key] = value var affected: Array[Vector3i] = [ Vector3i(cell.x, cell.y, LAYER_FRONT), Vector3i(cell.x, cell.y, LAYER_BACK), ] _mark_dirty(affected, false, false, true, false) return true func is_excavated(cell: Vector3i) -> bool: if not is_in_bounds(cell): return false var spatial_key := Vector2i(cell.x, cell.y) return bool(_excavated.get(spatial_key, cell.y >= 0)) ## Sets explicit support on a layer cell. Anchors have unlimited capacity. ## A capacity of zero with anchor=false removes the explicit support record. func set_support(cell: Vector3i, capacity: int, anchor := false) -> bool: if not is_in_bounds(cell) or capacity < 0: return false var previous_capacity := int(_support_capacity.get(cell, 0)) var previous_anchor := bool(_support_anchors.get(cell, false)) if previous_capacity == capacity and previous_anchor == anchor: return true if capacity == 0: _support_capacity.erase(cell) else: _support_capacity[cell] = capacity if anchor: _support_anchors[cell] = true else: _support_anchors.erase(cell) _mark_dirty([cell], false, false, false, true) return true func get_support_capacity(cell: Vector3i) -> int: if not is_in_bounds(cell): return 0 var capacity := int(_support_capacity.get(cell, 0)) if _occupants.has(cell): capacity += _kind_support_capacity(get_occupant_kind(cell)) return capacity func is_support_anchor(cell: Vector3i) -> bool: return ( is_in_bounds(cell) and ( bool(_support_anchors.get(cell, false)) or (_occupants.has(cell) and _kind_is_anchor(get_occupant_kind(cell))) ) ) ## Validates connected structural components touched by the proposed cells. ## Existing structural occupant kinds contribute their configured capacity/load. func validate_support(cells: Array, weight_per_cell := 1, entity_weights: Dictionary = {}) -> Dictionary: var base_report := _validate_cells(cells) var errors: Array[StringName] = base_report[&"errors"].duplicate() if weight_per_cell <= 0: _add_error(errors, ERROR_INVALID_WEIGHT) # Optional definition weights are read-only and apply to existing occupants. for id: Variant in entity_weights: if typeof(id) not in [TYPE_STRING, TYPE_STRING_NAME] or not _entity_cells.has(id) or typeof(entity_weights[id]) != TYPE_INT or int(entity_weights[id]) <= 0: _add_error(errors, ERROR_INVALID_WEIGHT) var proposed: Array[Vector3i] = base_report[&"valid_cells"].duplicate() proposed.sort_custom(_cell_less) var result := { &"ok": false, &"errors": errors, &"unsupported_cells": [] as Array[Vector3i], &"required_weight": 0, &"available_capacity": 0, &"components": [] as Array[Dictionary], } if not errors.is_empty(): return result var structural_set: Dictionary = {} for proposed_cell: Vector3i in proposed: structural_set[proposed_cell] = true for cell: Variant in _sorted_dictionary_cells(_support_capacity): structural_set[cell] = true for cell: Variant in _sorted_dictionary_cells(_support_anchors): structural_set[cell] = true for entity_id: StringName in _sorted_entity_ids(): var kind: StringName = _entity_kinds[entity_id] if _kind_support_capacity(kind) <= 0 and int(entity_weights.get(entity_id, _kind_weight(kind))) <= 0 and not _kind_is_anchor(kind): continue for occupied_cell: Vector3i in _entity_cells[entity_id]: structural_set[occupied_cell] = true var proposed_set: Dictionary = {} for proposed_cell: Vector3i in proposed: proposed_set[proposed_cell] = true var visited: Dictionary = {} var unsupported: Array[Vector3i] = [] var total_required := 0 var total_capacity := 0 var all_components_anchored := true for proposed_cell: Vector3i in proposed: if visited.has(proposed_cell): continue var component := _collect_component(proposed_cell, structural_set, visited) var component_proposed: Array[Vector3i] = [] var required := 0 var capacity := 0 var anchored := false for component_cell: Vector3i in component: if proposed_set.has(component_cell): component_proposed.append(component_cell) required += weight_per_cell if _occupants.has(component_cell) and not proposed_set.has(component_cell): required += int(entity_weights.get(get_occupant(component_cell), _kind_weight(get_occupant_kind(component_cell)))) capacity += get_support_capacity(component_cell) anchored = anchored or is_support_anchor(component_cell) component_proposed.sort_custom(_cell_less) var supported := anchored or capacity >= required if not supported: unsupported.append_array(component_proposed) result[&"components"].append({ &"cells": component_proposed, &"required_weight": required, &"available_capacity": -1 if anchored else capacity, &"anchored": anchored, &"supported": supported, }) total_required += required total_capacity += capacity all_components_anchored = all_components_anchored and anchored unsupported.sort_custom(_cell_less) if not unsupported.is_empty(): _add_error(result[&"errors"], ERROR_UNSUPPORTED) result[&"unsupported_cells"] = unsupported result[&"required_weight"] = total_required result[&"available_capacity"] = -1 if all_components_anchored else total_capacity result[&"ok"] = result[&"errors"].is_empty() return result func get_dirty_revision() -> int: return revision func get_dirty_cells() -> Array[Vector3i]: return _sorted_dictionary_cells(_dirty_cells) func consume_dirty_cells() -> Array[Vector3i]: var result := get_dirty_cells() _dirty_cells.clear() return result func create_snapshot() -> Dictionary: # Geometry only changes through world mutations. Dirty-cell consumption and # public revision counters are sampled separately on every read. if _snapshot_geometry_revision != revision or _snapshot_geometry.is_empty(): _snapshot_geometry = _create_geometry_snapshot() _snapshot_geometry_revision = revision var snapshot := _snapshot_geometry.duplicate(true) snapshot[&"dirty_cells"] = get_dirty_cells() snapshot[&"revisions"] = { &"world": revision, &"occupancy": occupancy_revision, &"walkability": walkability_revision, &"excavation": excavation_revision, &"support": support_revision, } return snapshot func _create_geometry_snapshot() -> Dictionary: var occupancies: Array[Dictionary] = [] for entity_id: StringName in _sorted_entity_ids(): occupancies.append({ &"entity_id": entity_id, &"kind": _entity_kinds[entity_id], &"cells": get_entity_cells(entity_id), }) var excavation_overrides: Array[Dictionary] = [] var spatial_keys: Array[Vector2i] = [] for key: Variant in _excavated: spatial_keys.append(key) spatial_keys.sort_custom(_spatial_less) for key: Vector2i in spatial_keys: excavation_overrides.append({&"position": key, &"value": bool(_excavated[key])}) var support_entries: Array[Dictionary] = [] var support_cell_set: Dictionary = {} for cell: Variant in _support_capacity: support_cell_set[cell] = true for cell: Variant in _support_anchors: support_cell_set[cell] = true for cell: Vector3i in _sorted_dictionary_cells(support_cell_set): support_entries.append({ &"cell": cell, &"capacity": int(_support_capacity.get(cell, 0)), &"anchor": bool(_support_anchors.get(cell, false)), }) return { &"schema_version": SNAPSHOT_SCHEMA_VERSION, &"bounds": get_bounds(), &"occupancies": occupancies, &"walkable_cells": _sorted_dictionary_cells(_walkable), &"excavation_overrides": excavation_overrides, &"support": support_entries, } func restore_snapshot(snapshot: Dictionary) -> bool: if int(snapshot.get(&"schema_version", -1)) != SNAPSHOT_SCHEMA_VERSION: return false var new_bounds := _normalize_bounds(snapshot.get(&"bounds", {})) if new_bounds.is_empty(): return false var new_occupants: Dictionary = {} var new_entity_cells: Dictionary = {} var new_entity_kinds: Dictionary = {} var occupancies: Variant = snapshot.get(&"occupancies", null) if not occupancies is Array: return false for entry: Variant in occupancies: if not entry is Dictionary: return false var entity_id := StringName(entry.get(&"entity_id", &"")) var kind := StringName(entry.get(&"kind", &"")) var cells: Variant = entry.get(&"cells", null) if entity_id.is_empty() or kind.is_empty() or not cells is Array or new_entity_cells.has(entity_id): return false var validation := _validate_cells_against_bounds(cells, new_bounds) if not validation[&"errors"].is_empty(): return false var canonical_cells: Array[Vector3i] = validation[&"valid_cells"].duplicate() for cell: Vector3i in canonical_cells: if new_occupants.has(cell): return false new_occupants[cell] = {&"entity_id": entity_id, &"kind": kind} new_entity_cells[entity_id] = canonical_cells new_entity_kinds[entity_id] = kind var new_walkable: Dictionary = {} var walkable_cells: Variant = snapshot.get(&"walkable_cells", null) if not walkable_cells is Array: return false for cell: Variant in walkable_cells: if not cell is Vector3i or not _is_in_bounds_with(cell, new_bounds) or new_walkable.has(cell): return false new_walkable[cell] = true var new_excavated: Dictionary = {} var excavation_entries: Variant = snapshot.get(&"excavation_overrides", null) if not excavation_entries is Array: return false for entry: Variant in excavation_entries: if not entry is Dictionary: return false var position: Variant = entry.get(&"position", null) var value: Variant = entry.get(&"value", null) if not position is Vector2i or not value is bool or new_excavated.has(position): return false if not _is_spatial_in_bounds(position, new_bounds): return false if bool(value) == (position.y >= 0): return false new_excavated[position] = value var new_support_capacity: Dictionary = {} var new_support_anchors: Dictionary = {} var support_entries: Variant = snapshot.get(&"support", null) if not support_entries is Array: return false var seen_support: Dictionary = {} for entry: Variant in support_entries: if not entry is Dictionary: return false var cell: Variant = entry.get(&"cell", null) var capacity: Variant = entry.get(&"capacity", null) var anchor: Variant = entry.get(&"anchor", null) if ( not cell is Vector3i or not _is_in_bounds_with(cell, new_bounds) or not capacity is int or int(capacity) < 0 or not anchor is bool or (int(capacity) == 0 and not bool(anchor)) or seen_support.has(cell) ): return false seen_support[cell] = true if int(capacity) > 0: new_support_capacity[cell] = int(capacity) if bool(anchor): new_support_anchors[cell] = true var new_dirty: Dictionary = {} var dirty_cells: Variant = snapshot.get(&"dirty_cells", null) if not dirty_cells is Array: return false for cell: Variant in dirty_cells: if not cell is Vector3i or not _is_in_bounds_with(cell, new_bounds) or new_dirty.has(cell): return false new_dirty[cell] = true var revisions: Variant = snapshot.get(&"revisions", null) if not revisions is Dictionary: return false var revision_names: Array[StringName] = [ &"world", &"occupancy", &"walkability", &"excavation", &"support", ] for revision_name: StringName in revision_names: if not revisions.get(revision_name, null) is int or int(revisions[revision_name]) < 0: return false if ( int(revisions[&"occupancy"]) > int(revisions[&"world"]) or int(revisions[&"walkability"]) > int(revisions[&"world"]) or int(revisions[&"excavation"]) > int(revisions[&"world"]) or int(revisions[&"support"]) > int(revisions[&"world"]) ): return false _snapshot_geometry.clear() _snapshot_geometry_revision = -1 _bounds = new_bounds _occupants = new_occupants _entity_cells = new_entity_cells _entity_kinds = new_entity_kinds _walkable = new_walkable _excavated = new_excavated _support_capacity = new_support_capacity _support_anchors = new_support_anchors _dirty_cells = new_dirty revision = int(revisions[&"world"]) occupancy_revision = int(revisions[&"occupancy"]) walkability_revision = int(revisions[&"walkability"]) excavation_revision = int(revisions[&"excavation"]) support_revision = int(revisions[&"support"]) return true func _validate_cells(cells: Array) -> Dictionary: return _validate_cells_against_bounds(cells, _bounds) func _validate_cells_against_bounds(cells: Array, bounds: Dictionary) -> Dictionary: var errors: Array[StringName] = [] var valid_cells: Array[Vector3i] = [] var invalid_cells: Array[Vector3i] = [] var duplicate_cells: Array[Vector3i] = [] var seen: Dictionary = {} var layers: Dictionary = {} if cells.is_empty(): _add_error(errors, ERROR_EMPTY_CELLS) for value: Variant in cells: if not value is Vector3i: _add_error(errors, ERROR_INVALID_CELL_TYPE) continue var cell: Vector3i = value layers[cell.z] = true if seen.has(cell): duplicate_cells.append(cell) _add_error(errors, ERROR_DUPLICATE_CELL) continue seen[cell] = true valid_cells.append(cell) if not _is_in_bounds_with(cell, bounds): invalid_cells.append(cell) _add_error(errors, ERROR_OUT_OF_BOUNDS) if layers.size() > 1: _add_error(errors, ERROR_LAYER_MISMATCH) valid_cells.sort_custom(_cell_less) invalid_cells.sort_custom(_cell_less) duplicate_cells.sort_custom(_cell_less) return { &"ok": errors.is_empty(), &"errors": errors, &"valid_cells": valid_cells, &"invalid_cells": invalid_cells, &"duplicate_cells": duplicate_cells, &"layer_mismatch": layers.size() > 1, } func _collect_component(start: Vector3i, structural_set: Dictionary, visited: Dictionary) -> Array[Vector3i]: var queue: Array[Vector3i] = [start] var component: Array[Vector3i] = [] visited[start] = true var index := 0 while index < queue.size(): var current := queue[index] index += 1 component.append(current) var neighbors: Array[Vector3i] = [ current + Vector3i(-1, 0, 0), current + Vector3i(0, -1, 0), current + Vector3i(0, 1, 0), current + Vector3i(1, 0, 0), ] for neighbor: Vector3i in neighbors: if structural_set.has(neighbor) and not visited.has(neighbor): visited[neighbor] = true queue.append(neighbor) component.sort_custom(_cell_less) return component func _mark_dirty( cells: Array, occupancy_changed: bool, walkability_changed: bool, excavation_changed: bool, support_changed: bool ) -> void: revision += 1 if occupancy_changed: occupancy_revision = revision if walkability_changed: walkability_revision = revision if excavation_changed: excavation_revision = revision if support_changed: support_revision = revision for cell: Variant in cells: if cell is Vector3i and is_in_bounds(cell): _dirty_cells[cell] = true func _normalize_bounds(value: Variant) -> Dictionary: var min_x: int var max_x: int var min_y: int var max_y: int if value is Rect2i: if value.size.x <= 0 or value.size.y <= 0: return {} min_x = value.position.x max_x = value.position.x + value.size.x - 1 min_y = value.position.y max_y = value.position.y + value.size.y - 1 elif value is Dictionary: if value.has(&"min") and value.has(&"max"): var minimum: Variant = value[&"min"] var maximum: Variant = value[&"max"] if not minimum is Vector2i or not maximum is Vector2i: return {} min_x = minimum.x max_x = maximum.x min_y = minimum.y max_y = maximum.y else: var names: Array[StringName] = [&"min_x", &"max_x", &"min_y", &"max_y"] for name: StringName in names: if not value.get(name, null) is int: return {} min_x = int(value[&"min_x"]) max_x = int(value[&"max_x"]) min_y = int(value[&"min_y"]) max_y = int(value[&"max_y"]) elif value is Vector4i: min_x = value.x max_x = value.y min_y = value.z max_y = value.w else: return {} if min_x > max_x or min_y > max_y: return {} return { &"min_x": min_x, &"max_x": max_x, &"min_y": min_y, &"max_y": max_y, } func _sorted_entity_ids() -> Array[StringName]: var result: Array[StringName] = [] for entity_id: Variant in _entity_cells: result.append(StringName(entity_id)) result.sort_custom(func(left: StringName, right: StringName) -> bool: return String(left) < String(right) ) return result func _sorted_dictionary_cells(source: Dictionary, sort_result := true) -> Array[Vector3i]: var result: Array[Vector3i] = [] for cell: Variant in source: if cell is Vector3i: result.append(cell) if sort_result: result.sort_custom(_cell_less) return result func _copy_cell_array(source: Array) -> Array[Vector3i]: var result: Array[Vector3i] = [] for cell: Variant in source: result.append(cell) result.sort_custom(_cell_less) return result func _kind_support_capacity(kind: StringName) -> int: match String(kind).to_lower(): "artificial_floor", "floor": return 2 "reinforced_floor": return 5 "support_column", "column": return 10 return 0 func _kind_weight(kind: StringName) -> int: match String(kind).to_lower(): "facility", "facility_small", "small": return 1 "facility_medium", "medium": return 2 "facility_large", "facility_heavy", "large", "heavy": return 3 return 0 func _kind_is_anchor(kind: StringName) -> bool: return String(kind).to_lower() in [ "anchor", "structural_anchor", "natural_branch", "world_tree", "world_tree_anchor", ] static func _is_in_bounds_with(cell: Vector3i, bounds: Dictionary) -> bool: return ( cell.z in [LAYER_FRONT, LAYER_BACK] and cell.x >= int(bounds.get(&"min_x", 0)) and cell.x <= int(bounds.get(&"max_x", -1)) and cell.y >= int(bounds.get(&"min_y", 0)) and cell.y <= int(bounds.get(&"max_y", -1)) ) static func _is_spatial_in_bounds(position: Vector2i, bounds: Dictionary) -> bool: return ( position.x >= int(bounds.get(&"min_x", 0)) and position.x <= int(bounds.get(&"max_x", -1)) and position.y >= int(bounds.get(&"min_y", 0)) and position.y <= int(bounds.get(&"max_y", -1)) ) static func _cell_less(left: Vector3i, right: Vector3i) -> bool: if left.y != right.y: return left.y < right.y if left.x != right.x: return left.x < right.x return left.z < right.z static func _spatial_less(left: Vector2i, right: Vector2i) -> bool: if left.y != right.y: return left.y < right.y return left.x < right.x static func _add_error(errors: Array[StringName], error_code: StringName) -> void: if not errors.has(error_code): errors.append(error_code)