From a3ad8d8a0798f6e0edfe1293f000228b698ab7e6 Mon Sep 17 00:00:00 2001
From: Rijad Zuzo <rijad.zuzo@matriq.ch>
Date: Thu, 18 Jun 2026 11:12:08 +0200
Subject: [PATCH] feat: add npc simulation, instancing and visual scene

---
 scenes/main.tscn => main.tscn                 | 24 ++++++++-----
 {scenes/player => player}/camera_rig.gd       |  0
 {scenes/player => player}/camera_rig.gd.uid   |  0
 player/npc/NpcVisual.gd                       | 11 ++++++
 player/npc/NpcVisual.gd.uid                   |  1 +
 player/npc/NpcVisual.tscn                     | 28 +++++++++++++++
 {scenes/player => player}/player.gd           |  0
 {scenes/player => player}/player.gd.uid       |  0
 {scripts/simulation => simulation}/SimNPC.gd  |  3 ++
 .../simulation => simulation}/SimNPC.gd.uid   |  0
 .../SimulationManager.gd                      |  0
 .../SimulationManager.gd.uid                  |  0
 world/world_view_manager.gd                   | 34 +++++++++++++++++++
 world/world_view_manager.gd.uid               |  1 +
 14 files changed, 94 insertions(+), 8 deletions(-)
 rename scenes/main.tscn => main.tscn (75%)
 rename {scenes/player => player}/camera_rig.gd (100%)
 rename {scenes/player => player}/camera_rig.gd.uid (100%)
 create mode 100644 player/npc/NpcVisual.gd
 create mode 100644 player/npc/NpcVisual.gd.uid
 create mode 100644 player/npc/NpcVisual.tscn
 rename {scenes/player => player}/player.gd (100%)
 rename {scenes/player => player}/player.gd.uid (100%)
 rename {scripts/simulation => simulation}/SimNPC.gd (90%)
 rename {scripts/simulation => simulation}/SimNPC.gd.uid (100%)
 rename {scripts/simulation => simulation}/SimulationManager.gd (100%)
 rename {scripts/simulation => simulation}/SimulationManager.gd.uid (100%)
 create mode 100644 world/world_view_manager.gd
 create mode 100644 world/world_view_manager.gd.uid

diff --git a/scenes/main.tscn b/main.tscn
similarity index 75%
rename from scenes/main.tscn
rename to main.tscn
index 37a129f..9bae6a7 100644
--- a/scenes/main.tscn
+++ b/main.tscn
@@ -1,8 +1,10 @@
-[gd_scene load_steps=10 format=3 uid="uid://rs3hv73svbpa"]
+[gd_scene load_steps=12 format=3 uid="uid://rs3hv73svbpa"]
 
-[ext_resource type="Script" uid="uid://4mg67crlim53" path="res://scenes/player/player.gd" id="1_o5qli"]
-[ext_resource type="Script" uid="uid://ii8ai801jur2" path="res://scenes/player/camera_rig.gd" id="2_0wfyh"]
-[ext_resource type="Script" uid="uid://dbb25ttlyogqr" path="res://scripts/simulation/SimulationManager.gd" id="3_sugp2"]
+[ext_resource type="Script" uid="uid://4mg67crlim53" path="res://player/player.gd" id="1_h2yge"]
+[ext_resource type="Script" uid="uid://ii8ai801jur2" path="res://player/camera_rig.gd" id="2_1bvp3"]
+[ext_resource type="Script" uid="uid://dbb25ttlyogqr" path="res://simulation/SimulationManager.gd" id="3_lquwl"]
+[ext_resource type="Script" uid="uid://c1f3b26n4yntf" path="res://world/world_view_manager.gd" id="4_1bvp3"]
+[ext_resource type="PackedScene" uid="uid://dhxxyprqflotq" path="res://player/npc/NpcVisual.tscn" id="5_lquwl"]
 
 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_o5qli"]
 albedo_color = Color(0.16862746, 0.52156866, 0.30588236, 1)
@@ -38,7 +40,7 @@ material_override = SubResource("StandardMaterial3D_o5qli")
 mesh = SubResource("BoxMesh_sgp6g")
 
 [node name="Player" type="CharacterBody3D" parent="." node_paths=PackedStringArray("camera_rig")]
-script = ExtResource("1_o5qli")
+script = ExtResource("1_h2yge")
 camera_rig = NodePath("../CameraRig")
 
 [node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
@@ -55,7 +57,7 @@ mesh = SubResource("BoxMesh_0wfyh")
 
 [node name="CameraRig" type="Node3D" parent="." node_paths=PackedStringArray("target")]
 transform = Transform3D(1, 0, 0, 0, 0.57357645, 0.81915206, 0, -0.81915206, 0.57357645, 0, 10, 8)
-script = ExtResource("2_0wfyh")
+script = ExtResource("2_1bvp3")
 target = NodePath("../Player")
 follow_speed = 8.0
 deadzone_right = 2.0
@@ -66,11 +68,17 @@ current = true
 fov = 65.0
 
 [node name="SimulationManager" type="Node" parent="."]
-script = ExtResource("3_sugp2")
+script = ExtResource("3_lquwl")
 
-[node name="WorldViewManager" type="Node" parent="."]
+[node name="WorldViewManager" type="Node" parent="." node_paths=PackedStringArray("simulation_manager", "active_npcs_parent")]
+script = ExtResource("4_1bvp3")
+npc_visual_scene = ExtResource("5_lquwl")
+simulation_manager = NodePath("../SimulationManager")
+active_npcs_parent = NodePath("../ActiveNPCs")
 
 [node name="EventBus" type="Node" parent="."]
 
 [node name="WorldEnvironment" type="WorldEnvironment" parent="."]
 environment = SubResource("Environment_o5qli")
+
+[node name="ActiveNPCs" type="Node3D" parent="."]
diff --git a/scenes/player/camera_rig.gd b/player/camera_rig.gd
similarity index 100%
rename from scenes/player/camera_rig.gd
rename to player/camera_rig.gd
diff --git a/scenes/player/camera_rig.gd.uid b/player/camera_rig.gd.uid
similarity index 100%
rename from scenes/player/camera_rig.gd.uid
rename to player/camera_rig.gd.uid
diff --git a/player/npc/NpcVisual.gd b/player/npc/NpcVisual.gd
new file mode 100644
index 0000000..8b18950
--- /dev/null
+++ b/player/npc/NpcVisual.gd
@@ -0,0 +1,11 @@
+extends CharacterBody3D
+
+var sim_id: int = -1
+var npc_name: String = ""
+var profession: String = ""
+
+func setup_from_sim(npc: SimNPC) -> void:
+	sim_id = npc.id
+	npc_name = npc.npc_name
+	profession = npc.profession
+	name = "NPC_%s_%s" % [sim_id, npc_name]
diff --git a/player/npc/NpcVisual.gd.uid b/player/npc/NpcVisual.gd.uid
new file mode 100644
index 0000000..219cf4e
--- /dev/null
+++ b/player/npc/NpcVisual.gd.uid
@@ -0,0 +1 @@
+uid://bha8uf40p3sa0
diff --git a/player/npc/NpcVisual.tscn b/player/npc/NpcVisual.tscn
new file mode 100644
index 0000000..055da55
--- /dev/null
+++ b/player/npc/NpcVisual.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=6 format=3 uid="uid://dhxxyprqflotq"]
+
+[ext_resource type="Script" uid="uid://bha8uf40p3sa0" path="res://player/npc/NpcVisual.gd" id="1_ixfoq"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_d844k"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_d844k"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_d844k"]
+albedo_color = Color(1, 0.22352941, 1, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_d844k"]
+material = SubResource("StandardMaterial3D_d844k")
+
+[node name="NpcVisual" type="CharacterBody3D"]
+script = ExtResource("1_ixfoq")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
+shape = SubResource("CapsuleShape3D_d844k")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0)
+mesh = SubResource("CapsuleMesh_d844k")
+
+[node name="FaceMarker" type="MeshInstance3D" parent="."]
+transform = Transform3D(0.15, 0, 0, 0, 0.15, 0, 0, 0, 0.35, 0, 1.2, -0.5)
+mesh = SubResource("BoxMesh_d844k")
diff --git a/scenes/player/player.gd b/player/player.gd
similarity index 100%
rename from scenes/player/player.gd
rename to player/player.gd
diff --git a/scenes/player/player.gd.uid b/player/player.gd.uid
similarity index 100%
rename from scenes/player/player.gd.uid
rename to player/player.gd.uid
diff --git a/scripts/simulation/SimNPC.gd b/simulation/SimNPC.gd
similarity index 90%
rename from scripts/simulation/SimNPC.gd
rename to simulation/SimNPC.gd
index 2f0bdd4..abbb929 100644
--- a/scripts/simulation/SimNPC.gd
+++ b/simulation/SimNPC.gd
@@ -9,6 +9,7 @@ var energy: float
 var strength: float
 var intelligence: float
 var current_task: String = "idle"
+var position: Vector3
 
 func _init(
 	_id: int,
@@ -25,6 +26,8 @@ func _init(
 	hunger = randf_range(20.0, 80.0)
 	energy = randf_range(40.0, 100.0)
 
+	position = Vector3(randf_range(-8.0, 8.0), 0.0, randf_range(-8.0, 8.0))
+
 func simulate_tick() -> void:
 	hunger += 5.0
 	energy -= 2.0
diff --git a/scripts/simulation/SimNPC.gd.uid b/simulation/SimNPC.gd.uid
similarity index 100%
rename from scripts/simulation/SimNPC.gd.uid
rename to simulation/SimNPC.gd.uid
diff --git a/scripts/simulation/SimulationManager.gd b/simulation/SimulationManager.gd
similarity index 100%
rename from scripts/simulation/SimulationManager.gd
rename to simulation/SimulationManager.gd
diff --git a/scripts/simulation/SimulationManager.gd.uid b/simulation/SimulationManager.gd.uid
similarity index 100%
rename from scripts/simulation/SimulationManager.gd.uid
rename to simulation/SimulationManager.gd.uid
diff --git a/world/world_view_manager.gd b/world/world_view_manager.gd
new file mode 100644
index 0000000..61a5be4
--- /dev/null
+++ b/world/world_view_manager.gd
@@ -0,0 +1,34 @@
+extends Node
+
+@export var npc_visual_scene: PackedScene
+@export var simulation_manager: Node
+@export var active_npcs_parent: Node3D
+
+var active_npc_visuals := {} # sim_id -> NPCVisual
+
+func _ready() -> void:
+	call_deferred("spawn_initial_npcs")
+
+func spawn_initial_npcs() -> void:
+	if simulation_manager == null:
+		push_error("WorldViewManager: simulation_manager is missing")
+		return
+
+	if npc_visual_scene == null:
+		push_error("WorldViewManager: npc_visual_scene is missing")
+		return
+
+	for npc in simulation_manager.npcs:
+		spawn_npc_visual(npc)
+
+func spawn_npc_visual(npc: SimNPC) -> void:
+	if active_npc_visuals.has(npc.id):
+		return
+
+	var visual := npc_visual_scene.instantiate()
+	active_npcs_parent.add_child(visual)
+
+	visual.global_position = npc.position
+	visual.setup_from_sim(npc)
+
+	active_npc_visuals[npc.id] = visual
diff --git a/world/world_view_manager.gd.uid b/world/world_view_manager.gd.uid
new file mode 100644
index 0000000..50a61b6
--- /dev/null
+++ b/world/world_view_manager.gd.uid
@@ -0,0 +1 @@
+uid://c1f3b26n4yntf
