diff --git a/docs/BUILD_IN_PUBLIC_PLAN.md b/docs/BUILD_IN_PUBLIC_PLAN.md index 51088b9..307c9b5 100644 --- a/docs/BUILD_IN_PUBLIC_PLAN.md +++ b/docs/BUILD_IN_PUBLIC_PLAN.md @@ -466,8 +466,9 @@ represent a real chosen task. distinct NPC colors and props, world labels identify each villager, and the Tab-cycled inspector shows authoritative need values, task state, destination, decision branch, and utility scores. The existing carried-food prop and village -panel expose immediate consequences. A cinematic/debug toggle and repeatable -demo reset remain Phase 6 capture tooling. +panel expose immediate consequences. `F10` toggles the debug/cinematic +presentation layer, while `F12` reloads the current scene as a repeatable +simulation-garden demo reset. ### Exit condition diff --git a/docs/LEARNING_ROADMAP.md b/docs/LEARNING_ROADMAP.md index 74141b4..d826973 100644 --- a/docs/LEARNING_ROADMAP.md +++ b/docs/LEARNING_ROADMAP.md @@ -652,15 +652,21 @@ Completed after the architecture gate: The practical next sequence is: -1. Add a cinematic/debug toggle and repeatable simulation-garden demo reset. -2. Expand resource discovery with many finite `ResourceNode` instances placed +1. Expand resource discovery with many finite `ResourceNode` instances placed in meaningful foliage, animal-camp, berry, and village-stockpile contexts. The first distance/safety/comfort scoring pass and small authored resource spread exist; next, validate placement rules and grow the authored set carefully rather than reintroducing abstract resource zones. -3. Expand persistence only when schedules, player state, or a real save menu +2. Expand persistence only when schedules, player state, or a real save menu creates a concrete requirement. +Recently completed: + +- `F10` cinematic/debug presentation toggle that hides development UI, world + labels, and NPC name/profession labels without changing simulation state; +- `F12` repeatable simulation-garden demo reset by reloading the current scene + with the same authored seed and starting state. + This order strengthens the simulation while regularly producing visible progress suitable for public development updates. diff --git a/main.tscn b/main.tscn index 87bb317..c358892 100644 --- a/main.tscn +++ b/main.tscn @@ -10,6 +10,7 @@ [ext_resource type="Script" path="res://simulation/persistence/SaveSlotController.gd" id="10_save"] [ext_resource type="PackedScene" path="res://world/jajce/JajceWorld.tscn" id="11_jajce"] [ext_resource type="Script" path="res://world/ui/time_dial.gd" id="12_timedial"] +[ext_resource type="Script" path="res://world/demo/DemoController.gd" id="13_demo"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_o5qli"] radius = 0.4 @@ -83,6 +84,11 @@ active_npcs_parent = NodePath("../ActiveNPCs") script = ExtResource("6_7mycd") simulation_manager = NodePath("../SimulationManager") +[node name="DemoController" type="Node" parent="." node_paths=PackedStringArray("ui", "active_npcs_parent")] +script = ExtResource("13_demo") +ui = NodePath("../UI") +active_npcs_parent = NodePath("../ActiveNPCs") + [node name="VillagePanel" type="PanelContainer" parent="UI" unique_id=482126250] offset_left = 20.0 offset_top = 20.0 diff --git a/player/npc/NpcVisual.gd b/player/npc/NpcVisual.gd index 56ced4b..1c4dafc 100644 --- a/player/npc/NpcVisual.gd +++ b/player/npc/NpcVisual.gd @@ -32,6 +32,7 @@ var path_pending := false var is_dead_visual := false var dead_material := StandardMaterial3D.new() +var debug_overlay_visible := true func debug_log(message: String) -> void: @@ -71,6 +72,7 @@ func _apply_profession_presentation() -> void: profession_prop.mesh = _create_profession_prop_mesh() profession_prop.visible = profession_prop.mesh != null profession_label.text = "%s\n%s" % [npc_name, definition.display_name] + profession_label.visible = debug_overlay_visible func _create_profession_prop_mesh() -> PrimitiveMesh: @@ -112,6 +114,12 @@ func set_carried_food_visible(is_visible: bool) -> void: carried_food_visual.visible = is_visible and not is_dead_visual +func set_debug_overlay_visible(is_visible: bool) -> void: + debug_overlay_visible = is_visible + if profession_label != null: + profession_label.visible = is_visible and not is_dead_visual + + func set_target_position(pos: Vector3) -> void: path_request_id += 1 var request_id := path_request_id @@ -240,6 +248,7 @@ func apply_dead_visual_state() -> void: is_dead_visual = true carried_food_visual.visible = false profession_prop.visible = false + profession_label.visible = false path_request_id += 1 velocity = Vector3.ZERO current_path = PackedVector3Array() diff --git a/world/activity/ActivitySite.gd b/world/activity/ActivitySite.gd index 52964fa..42127b1 100644 --- a/world/activity/ActivitySite.gd +++ b/world/activity/ActivitySite.gd @@ -65,6 +65,11 @@ func _update_presentation() -> void: label.text = text +func set_debug_label_enabled(is_enabled: bool) -> void: + debug_label_enabled = is_enabled + _update_presentation() + + static func get_by_id(search_id: StringName): for node in _all: if node.site_id == search_id: diff --git a/world/demo/DemoController.gd b/world/demo/DemoController.gd new file mode 100644 index 0000000..0056edc --- /dev/null +++ b/world/demo/DemoController.gd @@ -0,0 +1,67 @@ +extends Node + +@export var ui: CanvasLayer +@export var active_npcs_parent: Node +@export var debug_overlay_visible := true + +var _refresh_accumulator := 0.0 + + +func _ready() -> void: + _apply_debug_overlay_visibility() + + +func _process(delta: float) -> void: + _refresh_accumulator += delta + if _refresh_accumulator < 0.5: + return + _refresh_accumulator = 0.0 + _apply_debug_overlay_visibility() + + +func _unhandled_key_input(event: InputEvent) -> void: + if not event is InputEventKey or not event.pressed or event.echo: + return + if event.keycode == KEY_F10: + toggle_debug_overlay() + get_viewport().set_input_as_handled() + elif event.keycode == KEY_F12: + reset_demo_scene() + get_viewport().set_input_as_handled() + + +func toggle_debug_overlay() -> void: + debug_overlay_visible = not debug_overlay_visible + _apply_debug_overlay_visibility() + print( + "[DemoController] %s" + % ("Debug overlay visible" if debug_overlay_visible else "Cinematic overlay hidden") + ) + + +func reset_demo_scene() -> void: + print("[DemoController] Resetting simulation garden demo") + get_tree().reload_current_scene() + + +func _apply_debug_overlay_visibility() -> void: + if ui != null and ui.has_method("set_debug_overlay_visible"): + ui.set_debug_overlay_visible(debug_overlay_visible) + + for node in ResourceNode.get_all(): + if node.has_method("set_debug_label_enabled"): + node.set_debug_label_enabled(debug_overlay_visible) + + for node in StorageNode.get_all(): + if node.has_method("set_debug_label_enabled"): + node.set_debug_label_enabled(debug_overlay_visible) + + for node in ActivitySite.get_all(): + if node.has_method("set_debug_label_enabled"): + node.set_debug_label_enabled(debug_overlay_visible) + + if active_npcs_parent == null: + return + for child in active_npcs_parent.get_children(): + if child.has_method("set_debug_overlay_visible"): + child.set_debug_overlay_visible(debug_overlay_visible) diff --git a/world/resource_nodes/ResourceNode.gd b/world/resource_nodes/ResourceNode.gd index 770c1cc..6412b0a 100644 --- a/world/resource_nodes/ResourceNode.gd +++ b/world/resource_nodes/ResourceNode.gd @@ -19,6 +19,7 @@ static var _all: Array[ResourceNode] = [] @export var comfort_distance := 18.0 @export var discovery_priority := 0.0 @export var hide_visual_when_depleted: bool = false +@export var debug_label_enabled := true @onready var interaction_point: Marker3D = $InteractionPoint @@ -121,6 +122,9 @@ func _update_presentation() -> void: if not has_node("DebugLabel"): return var label := $DebugLabel as Label3D + label.visible = debug_label_enabled + if not debug_label_enabled: + return var text := "%s\n%s %.1f" % [node_id, resource_id, get_amount_remaining()] if state != null: text += "\nrisk:%.2f comfort:%.0f" % [state.get_safety_risk(), state.get_comfort_distance()] @@ -133,6 +137,11 @@ func _update_presentation() -> void: label.text = text +func set_debug_label_enabled(is_enabled: bool) -> void: + debug_label_enabled = is_enabled + _update_presentation() + + static func get_by_id(search_id: StringName) -> ResourceNode: for node in _all: if node.node_id == search_id: diff --git a/world/storage/StorageNode.gd b/world/storage/StorageNode.gd index 6fe9c6c..1cd3c50 100644 --- a/world/storage/StorageNode.gd +++ b/world/storage/StorageNode.gd @@ -91,6 +91,11 @@ func _update_presentation() -> void: label.text = text +func set_debug_label_enabled(is_enabled: bool) -> void: + debug_label_enabled = is_enabled + _update_presentation() + + static func get_by_id(search_id: StringName): for node in _all: if node.storage_id == search_id: diff --git a/world/ui/ui.gd b/world/ui/ui.gd index df0a62e..a902cac 100644 --- a/world/ui/ui.gd +++ b/world/ui/ui.gd @@ -9,6 +9,7 @@ const DEBUG_LOGS := false var selected_npc_index := 0 var inspector_refresh_accumulator := 0.0 +var debug_overlay_visible := true func _ready() -> void: @@ -88,6 +89,8 @@ func _on_state_restored() -> void: func _refresh_npc_inspector() -> void: + if not debug_overlay_visible: + return if simulation_manager == null or simulation_manager.npcs.is_empty(): npc_inspector_label.text = "No villagers available" return @@ -122,7 +125,7 @@ func _refresh_npc_inspector() -> void: + "Hunger: %.0f Energy: %.0f\n" + "Carrying food: %.0f\n\n" + "Why\n%s%s\n\n" - + "[Tab] Next villager" + + "[Tab] Next villager [F10] Cinematic/debug [F12] Demo reset" ) % [ npc.npc_name, profession_name, @@ -146,3 +149,10 @@ func _get_action_name(action_id: StringName) -> String: if definition != null: return definition.display_name return String(action_id).capitalize() + + +func set_debug_overlay_visible(is_visible: bool) -> void: + debug_overlay_visible = is_visible + visible = is_visible + if is_visible: + _refresh_npc_inspector()