From 2f76ae163947468bbd215ee59a565f040d46b802 Mon Sep 17 00:00:00 2001 From: megaproxy Date: Sat, 16 May 2026 18:29:19 +0100 Subject: [PATCH] sleep_provider deconflicts beds against other pawns' targets L: SleepProvider.find_best_for filters bed candidates via the existing Job.is_target_taken_by_other mechanism that ConstructionProvider already uses. Sets j.target_node = best_bed on the proposed job so other pawns see the claim. Fixes the 2/3-pawns-floor-sleep symptom (memory.md 2026-05-11) caused by greedy nearest-neighbor convergence. The bed.claim() mechanism was already race-free; this just prevents simultaneous proposals on the same bed. Co-Authored-By: Claude Opus 4.7 (1M context) --- scenes/ai/sleep_provider.gd | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scenes/ai/sleep_provider.gd b/scenes/ai/sleep_provider.gd index 4921426..bd25e0e 100644 --- a/scenes/ai/sleep_provider.gd +++ b/scenes/ai/sleep_provider.gd @@ -52,6 +52,12 @@ func find_best_for(pawn) -> Job: for bed in World.beds: if not bed.is_available(): continue + # Skip beds already targeted by another pawn's active job. This is the + # same deconfliction used by ConstructionProvider (Job.is_target_taken_by_other) + # and prevents all simultaneously-tired pawns from converging on the same + # nearest bed and then losing the race at claim time. + if Job.is_target_taken_by_other(bed, pawn): + continue var d: int = abs(bed.tile.x - pawn.tile.x) + abs(bed.tile.y - pawn.tile.y) if d < best_dist: best_dist = d @@ -73,6 +79,9 @@ func find_best_for(pawn) -> Job: var j := Job.new() if best_bed != null: j.label = "Sleep at %s" % target_tile + # Tag the job's target so other pawns see this bed as taken via + # Job.is_target_taken_by_other() before it is physically claimed. + j.target_node = best_bed else: j.label = "Sleep on the floor at %s" % target_tile