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) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-16 18:29:19 +01:00
parent cf43ef9a98
commit 2f76ae1639

View file

@ -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