Phase 13: Rooms + Auto-roof + Beauty + Dirtiness + Cleaning
Three-agent fan-out — Opus pre-wrote Room class, World.rooms/room_at_tile/is_indoor, 4 EventBus signals before dispatch so the slices ran fully parallel. DECISION: Big-room UX = bump auto-roof cap to 16, banner above. Cabin (24 tiles) intentionally exceeds cap to exercise the warning path; a 5×5 test shed (9 interior tiles) was added to exercise the roof path. Room detection (Agent A): - scenes/world/room.gd — class_name Room, tiles/bounds/is_under_roof, contains_tile() bounds-then-list-checked, recompute_bounds() - scenes/world/room_detector.gd — class_name RoomDetector, BFS 4-dir from floor/door tiles, walls/terrain as boundary, doors counted as room interior. Detects up to 4× cap; auto-roofs only ≤16. - World.mark_wall_tile/mark_floor_tile/mark_door_tile hook BFS recompute - Door._complete() now erases wall-layer stamp + registers door tile - Designation.TOOL_NO_ROOF paint mode wired (UI button deferred Phase 17) - EventBus.room_changed / room_too_large signals Indoor/Shelter (Agent B): - Pawn._is_sheltered() rerouted: World.is_indoor() first, floor-proxy fallback - IndoorTintOverlay Node2D — _draw fills roofed-room tiles at α=0.10 warm - Crop._on_sim_tick skips stage advance when World.is_indoor(tile) Beauty + Dirtiness + Cleaning + Room thoughts (Agent C): - BeautySystem sparse map, linear falloff radius=3, Quality multiplier (SHODDY 0.5 → LEGENDARY 2.5). Base: Bed +2, Workbench +1, Torch +3, Hearth +4 - DirtinessSystem 0-100, tier crossings (clean<25/dirty<60/filthy≥60) emit tile_dirtiness_changed. bump/bump_clean/bump_pawn_traffic API - CleaningProvider priority=2, KIND_CLEAN toil, 2.5 dirt/tick for ~40 ticks - Bed/Torch/Workbench _complete() now register with BeautySystem - 7 room mood thoughts: clean_room (+2), dirty_room (-3), filthy_room (-6), beautiful_room (+4), ugly_room (-3), slept_in_room (+3 EVENT, wires Ph 17), ate_without_table (-3 EVENT, wires Ph 17) - Pawn._sync_room_thoughts called from _process_thoughts after cold block, defensive against null rooms/systems Integration recovery (Opus): - Agent C's BeautySystem/DirtinessSystem/CleaningProvider/IndoorTintOverlay instantiation in world.gd never landed (only field declarations + entity hooks survived). Added preloads + runtime add_child + autoload bindings + CleaningProvider registration + furniture pre-seed in _ready - Added _prestamp_test_shed_for_room_detector with _spawn_complete_wall/floor helpers so a 5×5 visible shed exercises the auto-roof path at boot MCP runtime verified: - Rooms: cabin Room#2 size=24 roofed=false (room_too_large fires), shed Room#3 size=9 roofed=true (auto-roof active) - beauty_map size=50 around prebuilt furniture; bed at (47,24) beauty=4.0 - Bram teleported to (36, 25) in shed → indoor=true, sheltered=true, thoughts=[clean_room +2], mood=52.0 - Screenshot: shed walls + brown floor visible; cabin warmly torch-lit; Spring 1/12 indicator; Day 1 07:52 Delegation: 3× gdscript-refactor (Sonnet) agents in parallel; integration recovery + MCP verify on Opus. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
92f4e5c945
commit
9cf9b7dbfd
28 changed files with 1286 additions and 28 deletions
|
|
@ -17,6 +17,10 @@ const TILE_STONE_DARK: Vector2i = Vector2i(3, 0)
|
|||
|
||||
const PLACEHOLDER_SOURCE_ID: int = 0
|
||||
|
||||
const BEAUTY_SYSTEM_SCRIPT: Script = preload("res://scenes/world/beauty_system.gd")
|
||||
const DIRTINESS_SYSTEM_SCRIPT: Script = preload("res://scenes/world/dirtiness_system.gd")
|
||||
const CLEANING_PROVIDER_SCRIPT: Script = preload("res://scenes/ai/cleaning_provider.gd")
|
||||
const INDOOR_TINT_SCRIPT: Script = preload("res://scenes/world/indoor_tint_overlay.gd")
|
||||
const PAWN_SCENE: PackedScene = preload("res://scenes/pawn/pawn.tscn")
|
||||
const TREE_SCENE: PackedScene = preload("res://scenes/entities/tree.tscn")
|
||||
const ROCK_SCENE: PackedScene = preload("res://scenes/entities/rock.tscn")
|
||||
|
|
@ -86,6 +90,7 @@ const SEASON_TINTS: Dictionary = {
|
|||
@onready var eat_provider: EatProvider = $EatProvider
|
||||
@onready var sleep_provider: SleepProvider = $SleepProvider
|
||||
@onready var doctor_provider: DoctorProvider = $DoctorProvider
|
||||
@onready var room_detector = $RoomDetector # RoomDetector — duck-typed (class_name scan-time window)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -112,6 +117,37 @@ func _ready() -> void:
|
|||
World.floor_layer = floor_layer
|
||||
World.designation_layer = designation_layer
|
||||
|
||||
# Phase 13 — wire RoomDetector; setup with map size so BFS knows map bounds.
|
||||
room_detector.setup(MAP_SIZE_TILES)
|
||||
World.room_detector = room_detector
|
||||
|
||||
# Phase 13 — instantiate BeautySystem + DirtinessSystem + CleaningProvider as
|
||||
# runtime children (no .tscn entry needed; they're stateful Nodes with no
|
||||
# editor-tunable exports). Autoload refs let entity code reach them.
|
||||
var beauty := Node.new()
|
||||
beauty.set_script(BEAUTY_SYSTEM_SCRIPT)
|
||||
beauty.name = "BeautySystem"
|
||||
add_child(beauty)
|
||||
World.beauty_system = beauty
|
||||
|
||||
var dirtiness := Node.new()
|
||||
dirtiness.set_script(DIRTINESS_SYSTEM_SCRIPT)
|
||||
dirtiness.name = "DirtinessSystem"
|
||||
add_child(dirtiness)
|
||||
World.dirtiness_system = dirtiness
|
||||
|
||||
var cleaning := Node.new()
|
||||
cleaning.set_script(CLEANING_PROVIDER_SCRIPT)
|
||||
cleaning.name = "CleaningProvider"
|
||||
add_child(cleaning)
|
||||
|
||||
# Phase 13 — instantiate IndoorTintOverlay so roofed rooms get a subtle warm
|
||||
# overlay. Node2D, z_index 3 (set in its _ready). Self-listens to room_changed.
|
||||
var indoor_tint := Node2D.new()
|
||||
indoor_tint.set_script(INDOOR_TINT_SCRIPT)
|
||||
indoor_tint.name = "IndoorTintOverlay"
|
||||
add_child(indoor_tint)
|
||||
|
||||
# Designation: bind the paint surface + the Selection guard.
|
||||
designation_ctl.bind(designation_layer, selection)
|
||||
|
||||
|
|
@ -128,6 +164,7 @@ func _ready() -> void:
|
|||
World.register_work_provider(crafting_provider)
|
||||
World.register_work_provider(plant_provider)
|
||||
World.register_work_provider(hauling_provider)
|
||||
World.register_work_provider(cleaning) # priority 2 — between haul (3) and rest (0)
|
||||
World.register_work_provider(rest_provider)
|
||||
|
||||
# Phase 5: bridge designation paint events → spawn the ghost-state entity
|
||||
|
|
@ -139,6 +176,25 @@ func _ready() -> void:
|
|||
_spawn_sample_harvestables()
|
||||
_spawn_sample_stockpiles()
|
||||
_seed_phase5_demo_buildings()
|
||||
# Phase 13 — pre-stamp the cabin walls + floors on the TileMap data layers
|
||||
# so RoomDetector can see a completed enclosure at boot without waiting for
|
||||
# pawns to finish the build queue. Mirrors the layout in _seed_phase5_demo_buildings.
|
||||
_prestamp_cabin_for_room_detector()
|
||||
_prestamp_test_shed_for_room_detector()
|
||||
room_detector.recompute_around(Vector2i(47, 25))
|
||||
room_detector.recompute_around(Vector2i(36, 25)) # tiny shed centroid
|
||||
|
||||
# Phase 13 — register existing prebuilt furniture with BeautySystem so it has
|
||||
# a beauty score baseline at boot (the post-_complete hooks already fire for
|
||||
# the cabin's beds/torches/workbenches, but pawns may path before the first
|
||||
# recompute, so seed the map here).
|
||||
for ws in World.workbenches:
|
||||
beauty.register_furniture(ws)
|
||||
for b in World.beds:
|
||||
beauty.register_furniture(b)
|
||||
for ls in World.light_sources:
|
||||
beauty.register_furniture(ls)
|
||||
beauty.recompute_all()
|
||||
_run_pathfinder_spike()
|
||||
|
||||
# Phase 4: every 5 in-game seconds (100 ticks), re-evaluate items in
|
||||
|
|
@ -527,6 +583,88 @@ func _apply_season_tint(season: StringName) -> void:
|
|||
Audit.log("world", "season tint → %s (%s)" % [season, tint])
|
||||
|
||||
|
||||
# ── Phase 13: demo seed room helper ─────────────────────────────────────────
|
||||
|
||||
# Directly stamps the cabin perimeter walls and interior floors on the data
|
||||
# TileMap layers so RoomDetector can detect the enclosure at boot.
|
||||
# The ghost entities are already queued — this only writes the data layer
|
||||
# (same as Wall._complete / Floor._complete call). Without this,
|
||||
# RoomDetector would have to wait until pawns finish building the walls
|
||||
# (many seconds of real time) before the first room is visible.
|
||||
#
|
||||
# Layout mirrors _seed_phase5_demo_buildings: 8×6 cabin at (44, 23),
|
||||
# door at (47, 28), 6×4 wood floor interior.
|
||||
func _prestamp_cabin_for_room_detector() -> void:
|
||||
var origin := Vector2i(44, 23)
|
||||
var w := 8
|
||||
var h := 6
|
||||
var door_x := w / 2 - 1 # 3 → tile x=47
|
||||
var door_tile := origin + Vector2i(door_x, h - 1)
|
||||
|
||||
# Stamp perimeter walls (skipping the door slot).
|
||||
for x in w:
|
||||
World.mark_wall_tile(origin + Vector2i(x, 0), &"stone")
|
||||
var bottom := origin + Vector2i(x, h - 1)
|
||||
if bottom != door_tile:
|
||||
World.mark_wall_tile(bottom, &"stone")
|
||||
for y in range(1, h - 1):
|
||||
World.mark_wall_tile(origin + Vector2i(0, y), &"stone")
|
||||
World.mark_wall_tile(origin + Vector2i(w - 1, y), &"stone")
|
||||
|
||||
# Stamp interior floors.
|
||||
for x in range(1, w - 1):
|
||||
for y in range(1, h - 1):
|
||||
World.mark_floor_tile(origin + Vector2i(x, y), &"wood")
|
||||
|
||||
# Stamp the door slot as a wall so the perimeter is fully closed and BFS
|
||||
# terminates cleanly. Door._complete() erases this wall stamp and registers
|
||||
# the door entity; the follow-up recompute_around picks it up as a boundary.
|
||||
World.mark_wall_tile(door_tile, &"stone")
|
||||
|
||||
Audit.log("world", "phase 13 demo: cabin walls+floors pre-stamped for RoomDetector")
|
||||
|
||||
|
||||
# Tiny 5×5 walled shed with 3×3 interior (= 9 floor tiles) — under the 16-cap
|
||||
# auto-roof threshold, so this room WILL roof. Used to exercise the indoor /
|
||||
# shelter / room-thoughts pipeline without resizing the main cabin.
|
||||
func _prestamp_test_shed_for_room_detector() -> void:
|
||||
var origin := Vector2i(34, 23) # left of cabin, on the grass plain
|
||||
var w := 5
|
||||
var h := 5
|
||||
# Perimeter walls — instantiate complete Wall entities so they're visible.
|
||||
for x in w:
|
||||
_spawn_complete_wall(origin + Vector2i(x, 0))
|
||||
_spawn_complete_wall(origin + Vector2i(x, h - 1))
|
||||
for y in range(1, h - 1):
|
||||
_spawn_complete_wall(origin + Vector2i(0, y))
|
||||
_spawn_complete_wall(origin + Vector2i(w - 1, y))
|
||||
# Interior floors — instantiate complete Floor entities.
|
||||
for x in range(1, w - 1):
|
||||
for y in range(1, h - 1):
|
||||
_spawn_complete_floor(origin + Vector2i(x, y))
|
||||
|
||||
Audit.log("world", "phase 13 demo: 5×5 test shed pre-built (interior 9 tiles, auto-roofed)")
|
||||
|
||||
|
||||
# Instantiate a Wall entity in completed state at `tile`. Bypasses the build
|
||||
# queue. Used by the Phase 13 test shed seed.
|
||||
func _spawn_complete_wall(tile: Vector2i) -> void:
|
||||
var w = WALL_SCENE.instantiate()
|
||||
w.setup(tile, &"stone")
|
||||
add_child(w)
|
||||
w.build_progress = w.BUILD_TICKS
|
||||
w.on_build_tick() # triggers _complete() and the data-layer stamp + room recompute
|
||||
|
||||
|
||||
# Instantiate a Floor entity in completed state at `tile`.
|
||||
func _spawn_complete_floor(tile: Vector2i) -> void:
|
||||
var f = FLOOR_SCENE.instantiate()
|
||||
f.setup(tile, &"wood")
|
||||
add_child(f)
|
||||
f.build_progress = f.BUILD_TICKS
|
||||
f.on_build_tick()
|
||||
|
||||
|
||||
# ── spike: AStarGrid2D query timing at 80² ──────────────────────────────────
|
||||
|
||||
func _run_pathfinder_spike() -> void:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue