From 7ddee15f0661e0e118127609e193d35db6047752 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 21 Feb 2026 14:09:53 +0100 Subject: [PATCH] feat(client): tilemap z-filtering, entity 24x32 footprint, follow stub (#71, #72) tile_renderer: only render z=0 tiles on FloorTiles layer (D-049 z-stack). entity_renderer: fix footprint from 24x24 to 24x32 per D-044, split ENTITY_SIZE into ENTITY_WIDTH/ENTITY_HEIGHT with separate offsets. game_state: add follow_target_id stub for server ticket #241. Co-Authored-By: Claude Opus 4.6 --- client/scripts/autoloads/game_state.gd | 4 ++++ client/scripts/rendering/entity_renderer.gd | 21 ++++++++++++--------- client/scripts/rendering/tile_renderer.gd | 8 ++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/client/scripts/autoloads/game_state.gd b/client/scripts/autoloads/game_state.gd index 9c1f82920..50318fad5 100644 --- a/client/scripts/autoloads/game_state.gd +++ b/client/scripts/autoloads/game_state.gd @@ -19,6 +19,10 @@ var visibility_sectors: Dictionary = {} # Vector2i -> "Forward"/"Peripheral" # refined when the server assigns explicit player entity IDs). var player_entity_id: int = 1 +# #241: Follow target — entity_id of the NPC the player is following, -1 when not following. +# Stub for server ticket #241 (Follow verb). Client reads this for camera/UI behavior. +var follow_target_id: int = -1 + # v4 fields (#404/#405) var nearby_interactions: Array = [] # [{entity_id, entity_type, distance, verbs: [{kind, label, priority, available}]}] diff --git a/client/scripts/rendering/entity_renderer.gd b/client/scripts/rendering/entity_renderer.gd index 61ff8e0d2..1bbdb7529 100644 --- a/client/scripts/rendering/entity_renderer.gd +++ b/client/scripts/rendering/entity_renderer.gd @@ -13,8 +13,11 @@ extends Node2D # color from RelationshipState via the knowledge graph. const TILE_SIZE: int = Constants.TILE_SIZE -const ENTITY_SIZE: int = 24 -const ENTITY_OFFSET: float = (TILE_SIZE - ENTITY_SIZE) / 2.0 # center within tile +# D-044: 24x32 entity footprint within 32x32 visual tile (64x64 source scaled to 32px runtime) +const ENTITY_WIDTH: int = 24 +const ENTITY_HEIGHT: int = 32 +const ENTITY_OFFSET_X: float = (TILE_SIZE - ENTITY_WIDTH) / 2.0 # center horizontally +const ENTITY_OFFSET_Y: float = (TILE_SIZE - ENTITY_HEIGHT) / 2.0 # center vertically (bottom-aligned for y-sort would use TILE_SIZE - ENTITY_HEIGHT, but center is correct for placeholder) # Lerp speed — framerate-independent exponential smoothing. # At 12.0: ~70% there after 0.1s, ~95% after 0.25s. @@ -93,8 +96,8 @@ func update_entities(entities: Array) -> void: func _create_entity_node(entity_id: int, entity_data: Dictionary) -> void: var entity_node = ColorRect.new() entity_node.name = "Entity_" + str(entity_id) - entity_node.size = Vector2(ENTITY_SIZE, ENTITY_SIZE) - entity_node.pivot_offset = Vector2(ENTITY_SIZE / 2.0, ENTITY_SIZE / 2.0) + entity_node.size = Vector2(ENTITY_WIDTH, ENTITY_HEIGHT) + entity_node.pivot_offset = Vector2(ENTITY_WIDTH / 2.0, ENTITY_HEIGHT / 2.0) # D-033 color by relationship (#521) entity_node.color = _color_for_kind(entity_data) @@ -110,8 +113,8 @@ func _create_entity_node(entity_id: int, entity_data: Dictionary) -> void: # Snap to initial position (no lerp on first appearance) if entity_data.has("x") and entity_data.has("y"): var target := Vector2( - floorf(entity_data.x) * TILE_SIZE + ENTITY_OFFSET, - floorf(entity_data.y) * TILE_SIZE + ENTITY_OFFSET + floorf(entity_data.x) * TILE_SIZE + ENTITY_OFFSET_X, + floorf(entity_data.y) * TILE_SIZE + ENTITY_OFFSET_Y ) entity_node.position = target _entity_targets[entity_id] = target @@ -129,8 +132,8 @@ func _update_entity_node(entity_id: int, entity_data: Dictionary) -> void: # Server sends tile-center coords (tile 16 → 16.5), floor to get tile index. if entity_data.has("x") and entity_data.has("y"): _entity_targets[entity_id] = Vector2( - floorf(entity_data.x) * TILE_SIZE + ENTITY_OFFSET, - floorf(entity_data.y) * TILE_SIZE + ENTITY_OFFSET + floorf(entity_data.x) * TILE_SIZE + ENTITY_OFFSET_X, + floorf(entity_data.y) * TILE_SIZE + ENTITY_OFFSET_Y ) # #521: Detect relationship change → fade D-033 color (0.5s via _process) @@ -195,5 +198,5 @@ func _add_facing_indicator(parent_node: Control) -> void: ]) indicator.color = Constants.ENTITY_COLOR_PLAYER # Position at center of parent ColorRect — rotation around this point - indicator.position = Vector2(ENTITY_SIZE / 2.0, ENTITY_SIZE / 2.0) + indicator.position = Vector2(ENTITY_WIDTH / 2.0, ENTITY_HEIGHT / 2.0) parent_node.add_child(indicator) diff --git a/client/scripts/rendering/tile_renderer.gd b/client/scripts/rendering/tile_renderer.gd index 94a8453fd..88cfb336a 100644 --- a/client/scripts/rendering/tile_renderer.gd +++ b/client/scripts/rendering/tile_renderer.gd @@ -64,6 +64,8 @@ func _setup_tileset() -> void: # Update tiles from snapshot data # tiles: Array of {x: int, y: int, z: int, type: String} +# Only renders z=0 tiles (ground floor). z=1 (FloorObjects) and z>1 (upper floors) +# are handled by separate nodes — skipped here until those layers are implemented. func update_tiles(tiles: Array) -> void: if not _initialized: return @@ -74,6 +76,12 @@ func update_tiles(tiles: Array) -> void: if not tile_data.has("x") or not tile_data.has("y") or not tile_data.has("type"): continue + # Multi-layer support: FloorTiles only renders z=0 (ground floor). + # z=1 → FloorObjects node, z=2 → YSortGroup furniture (future layers). + var tile_z: int = tile_data.get("z", 0) + if tile_z != 0: + continue + var tile_type_str: String = tile_data.type if not TILE_TYPE_MAP.has(tile_type_str): push_warning("TileRenderer: unknown tile type '%s' at (%d, %d)" % [