diff --git a/scripts/chunk.gd b/scripts/chunk.gd index 1cbc21d..bb9d8cf 100644 --- a/scripts/chunk.gd +++ b/scripts/chunk.gd @@ -1,82 +1,105 @@ class_name Chunk -extends Node2D -## A single loaded chunk in the world. +extends Node3D +## A single world chunk. Holds the chunk coordinate, bounds, and the +## visual/gameplay content for that region of the world. ## -## A chunk is a fixed-size, axis-aligned region of a room. It is the atomic -## unit of load, save, and stream. This node is instantiated by the -## [ChunkStreamer] when a chunk is loaded from the server and freed when the -## chunk is unloaded. +## Chunks are the atomic unit of world geometry, gameplay objects, and +## persistence (see docs/architecture/world.md). The server loads and saves +## whole chunks; clients stream the chunks around them. -## World-space size of a chunk along X (world units / tiles). -const CHUNK_SIZE_X := 32.0 -## World-space size of a chunk along Y (world units / tiles). -const CHUNK_SIZE_Y := 32.0 -## World-space size of a chunk along Z (world units / tiles). -const CHUNK_SIZE_Z := 1.0 +## Default chunk dimensions in world units (tiles). Matches the constants in +## docs/architecture/world.md section 2.2. +const CHUNK_SIZE_X: int = 32 +const CHUNK_SIZE_Y: int = 32 +const CHUNK_SIZE_Z: int = 1 -## Canonical chunk key: "{world_id}:{room_id}:{cx}:{cy}:{cz}". +## World-space origin of chunk coordinate (0,0). +const CHUNK_ORIGIN := Vector3(0.0, 0.0, 0.0) + +## Chunk coordinate (cx, cy, cz) in chunk space. +var coord := Vector3i.ZERO + +## The canonical chunk key: "{world_id}:{room_id}:{cx}:{cy}:{cz}". var key: String = "" -## World id this chunk belongs to. + +## World and room identifiers this chunk belongs to. var world_id: String = "" -## Room id this chunk belongs to. var room_id: String = "" -## Integer chunk coordinate along X. -var cx: int = 0 -## Integer chunk coordinate along Y. -var cy: int = 0 -## Integer chunk coordinate along Z. -var cz: int = 0 -## Whether this chunk is currently loaded and active in the scene tree. -var is_loaded := false +## Format version of the chunk payload (currently 1). +var format_version: int = 1 -## Emitted when the chunk has finished loading its content. -signal chunk_loaded(chunk: Chunk) -## Emitted when the chunk is about to be unloaded. -signal chunk_unloaded(chunk: Chunk) +## Revision counter, incremented on every write. Used for optimistic +## concurrency and change detection. +var revision: int = 0 + +## Whether this chunk has been modified in memory but not yet flushed. +var dirty: bool = false + +## Container for static objects (props, geometry) in this chunk. +var objects: Array[Node3D] = [] + +## Container for dynamic object templates (spawnable entities). +var dynamic_templates: Array[Node3D] = [] + +## The world-space bounding box of this chunk. +var bounds: AABB -## Build the canonical chunk key from the chunk's coordinates. -func build_key() -> String: - return "%s:%s:%d:%d:%d" % [world_id, room_id, cx, cy, cz] +func _init(p_coord: Vector3i = Vector3i.ZERO, p_world_id: String = "", p_room_id: String = "") -> void: + coord = p_coord + world_id = p_world_id + room_id = p_room_id + key = build_key(world_id, room_id, coord) + bounds = compute_bounds(coord) -## Compute the world-space origin (min corner) of this chunk. -func world_origin() -> Vector2: - return Vector2(cx * CHUNK_SIZE_X, cy * CHUNK_SIZE_Y) +## Build the canonical chunk key from its components. +static func build_key(p_world_id: String, p_room_id: String, p_coord: Vector3i) -> String: + return "%s:%s:%d:%d:%d" % [p_world_id, p_room_id, p_coord.x, p_coord.y, p_coord.z] -## Compute the world-space bounding box of this chunk. -func world_bounds() -> Rect2: - var origin := world_origin() - return Rect2(origin, Vector2(CHUNK_SIZE_X, CHUNK_SIZE_Y)) +## Compute the world-space bounding box for a chunk coordinate. +static func compute_bounds(p_coord: Vector3i) -> AABB: + var min_pos := Vector3( + CHUNK_ORIGIN.x + p_coord.x * CHUNK_SIZE_X, + CHUNK_ORIGIN.y + p_coord.y * CHUNK_SIZE_Y, + CHUNK_ORIGIN.z + p_coord.z * CHUNK_SIZE_Z + ) + return AABB(min_pos, Vector3(CHUNK_SIZE_X, CHUNK_SIZE_Y, CHUNK_SIZE_Z)) -## Compute the integer chunk coordinate for a world-space position. -static func coord_for_position(pos: Vector2) -> Vector2i: - return Vector2i( - floori(pos.x / CHUNK_SIZE_X), - floori(pos.y / CHUNK_SIZE_Y) +## Compute the chunk coordinate that contains a world-space position. +static func coord_for_position(p_pos: Vector3) -> Vector3i: + return Vector3i( + int(floor((p_pos.x - CHUNK_ORIGIN.x) / CHUNK_SIZE_X)), + int(floor((p_pos.y - CHUNK_ORIGIN.y) / CHUNK_SIZE_Y)), + int(floor((p_pos.z - CHUNK_ORIGIN.z) / CHUNK_SIZE_Z)) ) -## Compute the canonical key for a world-space position. -static func key_for_position(pos: Vector2, p_world_id: String, p_room_id: String) -> String: - var c := coord_for_position(pos) - return "%s:%s:%d:%d:0" % [p_world_id, p_room_id, c.x, c.y] +## Add a static object to this chunk. +func add_object(p_object: Node3D) -> void: + objects.append(p_object) + add_child(p_object) -## Load this chunk's content. In a real deployment this would fetch the chunk -## payload from the server (see world.md ยง5.3 `request_chunk` / `chunk_data`) -## and instantiate tiles/objects. For now it just marks the chunk loaded. -func load_chunk() -> void: - is_loaded = true - chunk_loaded.emit(self) +## Add a dynamic object template to this chunk. +func add_dynamic_template(p_template: Node3D) -> void: + dynamic_templates.append(p_template) + add_child(p_template) -## Unload this chunk's content and free any child nodes. -func unload_chunk() -> void: - chunk_unloaded.emit(self) - for child in get_children(): - child.queue_free() - is_loaded = false +## Remove all objects from this chunk. +func clear_objects() -> void: + for obj in objects: + if is_instance_valid(obj): + obj.queue_free() + objects.clear() + dynamic_templates.clear() + + +## Mark this chunk as dirty (modified but not yet persisted). +func mark_dirty() -> void: + dirty = true + revision += 1