feat(client): add tile rendering, fog overlay, and camera smoothing

Sprint 2 rendering pipeline: tiles (#129), fog (#131), camera (#116).

- tile_renderer.gd: programmatic TileSet with floor/wall/door/object
  placeholder tiles, renders from snapshot tile data
- fog_renderer.gd: TileMapLayer overlay with three visibility states
  (visible/fog-edge/hidden), computed from visible_positions data
- Camera2D: smoothing enabled (speed 6.0), 2x zoom, locked to player
- game_state.gd: stores visible_tiles and visible_positions from snapshots
- sim_bridge.gd: test data with 8x8 room, corridor, and Manhattan
  distance visibility for development without server
- Scene render order: Tiles -> FogOverlay -> Entities
- Background clear color set to near-black for unexplored areas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 23:48:02 +01:00
co-authored by Claude Opus 4.6
parent 55b775ee73
commit dcf887cb49
8 changed files with 296 additions and 29 deletions
+65 -1
View File
@@ -235,7 +235,7 @@ static func _action_enum_to_wire(action: int) -> String:
return ""
# Hardcoded test snapshot matching Protocol format (deterministic per D-010 principle 4).
# Uses the same {tick, entities} schema as Protocol.decode_snapshot() returns.
# Uses the same {tick, entities, tiles} schema as Protocol.decode_snapshot() returns.
func _test_snapshot() -> Dictionary:
_test_tick += 1
return {
@@ -246,7 +246,71 @@ func _test_snapshot() -> Dictionary:
"x": 10.0,
"y": 10.0,
"z": 0,
"kind": { "variant": "Player", "data": null },
},
{
"entity_id": 2,
"x": 12.0,
"y": 10.0,
"z": 0,
"kind": { "variant": "Npc", "data": null },
},
],
"tiles": _test_tiles(),
"visible_positions": _test_visible_positions(),
}
# Generate a small test room: 8x6 room with walls, a door, and floor
func _test_tiles() -> Array:
var tiles: Array = []
var room_x := 7
var room_y := 7
var room_w := 8
var room_h := 8
for x in range(room_x, room_x + room_w):
for y in range(room_y, room_y + room_h):
var is_edge := (x == room_x or x == room_x + room_w - 1
or y == room_y or y == room_y + room_h - 1)
var tile_type: String
if is_edge:
# Door on the south wall, center
if y == room_y + room_h - 1 and x == room_x + room_w / 2:
tile_type = "door"
else:
tile_type = "wall"
else:
tile_type = "floor"
tiles.append({"x": x, "y": y, "z": 0, "type": tile_type})
# Corridor south of the door
var door_x := room_x + room_w / 2
for y in range(room_y + room_h, room_y + room_h + 4):
tiles.append({"x": door_x - 1, "y": y, "z": 0, "type": "wall"})
tiles.append({"x": door_x, "y": y, "z": 0, "type": "floor"})
tiles.append({"x": door_x + 1, "y": y, "z": 0, "type": "wall"})
return tiles
# Test visibility: player at (10,10) can see tiles within radius 4, blocked by walls
func _test_visible_positions() -> Array:
var positions: Array = []
var player_x := 10
var player_y := 10
var radius := 4
# Room bounds (inner floor area)
var room_x := 7
var room_y := 7
var room_w := 8
var room_h := 8
for x in range(player_x - radius, player_x + radius + 1):
for y in range(player_y - radius, player_y + radius + 1):
var dist := absf(x - player_x) + absf(y - player_y)
if dist <= radius:
# Walls are visible but block further vision
# For test purposes, include all tiles within radius that are inside the room
if x >= room_x and x < room_x + room_w and y >= room_y and y < room_y + room_h:
positions.append({"x": x, "y": y})
return positions