feat(client): initialize Godot 4 project boilerplate (epic #277)

Set up the complete Godot 4.6 client foundation as a pure renderer (D-020):
- project.godot with 2D rendering, autoloads, input actions
- Main scene: Game > World (TileMapLayer, Entities, FogOverlay) + Camera2D + UILayer
- Autoloads: SimBridge (connection state machine + test mode), GameState, InputMapper
- Rendering stubs: WorldRenderer, EntityRenderer, FogRenderer
- UI stubs: HUD (health/perception/time), Minimap, MonologueDisplay
- Input mapping: WASD/arrows, E (interact), Tab (perception), Esc (menu), Space (pause)
- gdUnit4 smoke tests: scene loads, autoloads registered (2/2 passing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 16:52:27 +01:00
co-authored by Claude Opus 4.6
parent 61e0762a84
commit 603c24e177
34 changed files with 656 additions and 0 deletions
View File
+24
View File
@@ -0,0 +1,24 @@
extends Control
# HUD — displays player health, perception mode, time, etc.
@onready var health_label: Label = $MarginContainer/VBoxContainer/HealthLabel
@onready var perception_label: Label = $MarginContainer/VBoxContainer/PerceptionLabel
@onready var time_label: Label = $MarginContainer/VBoxContainer/TimeLabel
func _ready() -> void:
print("HUD: Initialized")
# Update HUD from snapshot data
func update_from_hud_data(data: Dictionary) -> void:
# Update perception mode display
if data.has("perception_mode"):
perception_label.text = "Mode: " + data.perception_mode.capitalize()
# Update time display (per D-031)
if data.has("time"):
time_label.text = "Time: " + data.time
# Update player health (from player data, not hud_data)
func update_health(health: int) -> void:
health_label.text = "Health: " + str(health)
+1
View File
@@ -0,0 +1 @@
uid://dgy4mkke84c70
+38
View File
@@ -0,0 +1,38 @@
[gd_scene load_steps=2 format=3 uid="uid://cq1y5w3hmxr8b"]
[ext_resource type="Script" path="res://ui/hud.gd" id="1_hud"]
[node name="HUD" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("1_hud")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 100.0
grow_horizontal = 2
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 16
theme_override_constants/margin_right = 16
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="HealthLabel" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Health: 100"
[node name="PerceptionLabel" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Mode: Baseline"
[node name="TimeLabel" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Time: 08:00"
+15
View File
@@ -0,0 +1,15 @@
extends Control
# Minimap — small viewport showing local area
# Placeholder implementation for now
@onready var viewport_container: SubViewportContainer = $SubViewportContainer
func _ready() -> void:
print("Minimap: Initialized")
# Update minimap view (stub)
func update_minimap(player_pos: Vector2, entities: Array) -> void:
# TODO: Render minimap view
# This will show a top-down view of the local area
pass
+1
View File
@@ -0,0 +1 @@
uid://b6bqe4ydk8evo
+32
View File
@@ -0,0 +1,32 @@
[gd_scene load_steps=2 format=3 uid="uid://b5x7k2mwpq3vc"]
[ext_resource type="Script" path="res://ui/minimap.gd" id="1_minimap"]
[node name="Minimap" type="Control"]
layout_mode = 3
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -200.0
offset_bottom = 200.0
grow_horizontal = 0
mouse_filter = 2
script = ExtResource("1_minimap")
[node name="SubViewportContainer" type="SubViewportContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Background" type="ColorRect" parent="SubViewportContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = 200.0
offset_bottom = 200.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.1, 0.1, 0.1, 0.7)
+43
View File
@@ -0,0 +1,43 @@
extends Control
# Internal monologue display (per D-015)
# Shows character's internal thoughts as text overlay
@onready var text_panel: PanelContainer = $PanelContainer
@onready var text_label: RichTextLabel = $PanelContainer/MarginContainer/RichTextLabel
var fade_timer: float = 0.0
var fade_duration: float = 5.0 # Display duration before fade
var is_visible: bool = false
func _ready() -> void:
print("MonologueDisplay: Initialized")
text_panel.modulate.a = 0.0
is_visible = false
func _process(delta: float) -> void:
# Auto-fade after display
if is_visible:
fade_timer += delta
if fade_timer >= fade_duration:
_fade_out()
# Show internal monologue text
func show_monologue(text: String, duration: float = 5.0) -> void:
text_label.text = text
fade_duration = duration
fade_timer = 0.0
is_visible = true
# Fade in
var tween = create_tween()
tween.tween_property(text_panel, "modulate:a", 1.0, 0.3)
# Fade out the monologue
func _fade_out() -> void:
if not is_visible:
return
is_visible = false
var tween = create_tween()
tween.tween_property(text_panel, "modulate:a", 0.0, 0.5)
+1
View File
@@ -0,0 +1 @@
uid://befq2r47mmw08
+38
View File
@@ -0,0 +1,38 @@
[gd_scene load_steps=2 format=3 uid="uid://bj8y4v2nx7k5m"]
[ext_resource type="Script" path="res://ui/monologue_display.gd" id="1_monologue"]
[node name="MonologueDisplay" type="Control"]
layout_mode = 3
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -150.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 2
script = ExtResource("1_monologue")
[node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_left = 100.0
offset_right = -100.0
offset_bottom = 120.0
grow_horizontal = 2
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
layout_mode = 2
theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 12
theme_override_constants/margin_right = 16
theme_override_constants/margin_bottom = 12
[node name="RichTextLabel" type="RichTextLabel" parent="PanelContainer/MarginContainer"]
layout_mode = 2
bbcode_enabled = true
text = "Internal monologue will appear here..."
fit_content = true
scroll_active = false