rimlike/scenes/ai/cleaning_provider.gd
megaproxy 9cf9b7dbfd 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>
2026-05-11 17:19:23 +01:00

65 lines
2.2 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name CleaningProvider extends WorkProvider
## Phase 13 — WorkProvider for the Cleaning work category.
##
## Priority 2: fires when there is nothing more productive to do
## (below hauling=3, above rest=0).
##
## make_job / find_best_for logic:
## Scan DirtinessSystem.dirt_map for tiles with dirt >= DIRTY_THRESHOLD.
## Pick the nearest to the pawn by Manhattan distance.
## Build a 2-toil job: walk_to(tile) → kind_clean(tile).
##
## There is no Cleaning skill yet (Phase 17+ skill matrix). The clean toil
## takes a flat CLEAN_TICKS sim ticks to reduce dirt from any level to 0.
##
## JobRunner._tick_clean reduces DirtinessSystem.dirt_at(tile) by
## DIRT_REDUCTION_PER_TICK each sim tick until dirt <= 0.
##
## Audit.log fires on job start and on toil completion (via JobRunner).
## Dirt level at or above which a tile is worth cleaning.
const DIRTY_THRESHOLD: float = 25.0
## Base number of sim ticks to clean a tile from any level to 0.
## No skill modifier for now; Phase 17 wires skill × speed.
const CLEAN_TICKS: int = 40
func _init() -> void:
category = &"clean"
priority = 2
# ── WorkProvider override ─────────────────────────────────────────────────────
## Returns a cleaning Job for `pawn`, or null if no dirty tiles exist.
## Picks the tile closest to `pawn` (Manhattan distance) with dirt >= DIRTY_THRESHOLD.
func find_best_for(pawn) -> Job:
# Safety — dirtiness system may not be wired yet (Agent A rooms arrive slightly later).
if World.get("dirtiness_system") == null:
return null
var ds = World.dirtiness_system
if ds == null:
return null
var best_tile: Vector2i = Vector2i(-1, -1)
var best_dist: int = 999999
for tile in ds.dirt_map.keys():
var dirt_val: float = float(ds.dirt_map[tile])
if dirt_val < DIRTY_THRESHOLD:
continue
var d: int = abs(tile.x - pawn.tile.x) + abs(tile.y - pawn.tile.y)
if d < best_dist:
best_dist = d
best_tile = tile
if best_tile == Vector2i(-1, -1):
return null
var j := Job.new()
j.label = "Clean (%d,%d)" % [best_tile.x, best_tile.y]
j.toils.append(Toil.walk_to(best_tile))
j.toils.append(Toil.clean_at(best_tile))
return j