Bug-triage patch — fix torch builds, idle-pawn traps, floor render order
Three playtest-reported bugs fixed out-of-phase before Phase 18: * Furniture build-queue gap: Torch / Bed / Crate / Workbench / CremationPyre were missing World.register_build_site(self) in _ready, so newly-painted designations never entered ConstructionProvider's iteration. The seeded cabin pre-built everything via _spawn_complete_* helpers, masking the gap until a player painted a fresh furniture designation. * Wall-trap regression for bystanders + walk-through pawns: Wall._complete now dislodges any pawn on the tile via new Pathfinder.find_nearest_walkable BFS helper; Pawn._advance_walk re-checks next tile walkability before stepping, aborts walk + cancels job + lets Decision reroute. Phase 6's adjacent-stand fix only protected the BUILDING pawn. * Floor / Pawn Y-sort ambiguity: Floor was anchored at tile-center (same Y as Pawn), so Y-sort tiebreak fell to scene-tree order and Floor (spawned later) drew over Pawn. Moved Floor origin to top-of-tile so Floor.y < Pawn.y under Y-sort; _draw rect offsets compensate. All three verified via MCP runtime: torch built end-to-end, all 3 pawns working on different jobs with no idle traps, pawn renders over floor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d41778a483
commit
922f269a6c
9 changed files with 97 additions and 14 deletions
|
|
@ -105,11 +105,13 @@ func _ready() -> void:
|
|||
tile.y * TILE_SIZE_PX + TILE_SIZE_PX
|
||||
)
|
||||
World.register_bed(self)
|
||||
World.register_build_site(self)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
World.unregister_bed(self)
|
||||
World.unregister_build_site(self)
|
||||
|
||||
|
||||
## One-shot initialiser. Call after add_child() so _ready() has fired.
|
||||
|
|
|
|||
|
|
@ -50,10 +50,12 @@ func _exit_tree() -> void:
|
|||
func setup(p_tile: Vector2i, p_material: StringName) -> void:
|
||||
tile = p_tile
|
||||
floor_material = p_material
|
||||
# Floor renders at tile centre (not bottom-anchored).
|
||||
# Top-anchor (origin at tile's top edge) so Y-sort places the floor under
|
||||
# pawns standing on the same tile (pawns anchor at mid-tile, so pawn.y > floor.y).
|
||||
# _draw offsets compensate (rects span y=0..TILE_SIZE_PX, not -half..+half).
|
||||
position = Vector2(
|
||||
tile.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0,
|
||||
tile.y * TILE_SIZE_PX + TILE_SIZE_PX / 2.0
|
||||
tile.y * TILE_SIZE_PX
|
||||
)
|
||||
queue_redraw()
|
||||
Audit.log("floor", "%s floor ghost placed at %s" % [floor_material, tile])
|
||||
|
|
@ -124,40 +126,43 @@ func _draw() -> void:
|
|||
|
||||
|
||||
func _draw_wood_floor(alpha: float, half: float) -> void:
|
||||
# Top-anchored origin: rect spans (-half, 0) to (+half, TILE_SIZE_PX).
|
||||
var base := Color(0.58, 0.40, 0.20, alpha)
|
||||
var plank := Color(0.50, 0.34, 0.16, alpha)
|
||||
|
||||
draw_rect(Rect2(Vector2(-half, -half), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base)
|
||||
draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base)
|
||||
|
||||
# Horizontal plank lines.
|
||||
for y_offset in [-3.0, 2.0]:
|
||||
# Horizontal plank lines (offsets are from tile centre = +half from origin).
|
||||
for y_offset in [5.0, 10.0]:
|
||||
draw_line(
|
||||
Vector2(-half, y_offset),
|
||||
Vector2(half, y_offset),
|
||||
plank, 1.0
|
||||
)
|
||||
|
||||
draw_rect(Rect2(Vector2(-half, -half), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.3 * alpha), false, 1.0)
|
||||
draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.3 * alpha), false, 1.0)
|
||||
|
||||
|
||||
func _draw_stone_floor(alpha: float, half: float) -> void:
|
||||
# Top-anchored origin: rect spans (-half, 0) to (+half, TILE_SIZE_PX).
|
||||
var base := Color(0.60, 0.60, 0.57, alpha)
|
||||
var joint := Color(0.45, 0.45, 0.43, alpha)
|
||||
|
||||
draw_rect(Rect2(Vector2(-half, -half), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base)
|
||||
draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base)
|
||||
|
||||
# Stone tile grid lines.
|
||||
draw_line(Vector2(0.0, -half), Vector2(0.0, half), joint, 1.0)
|
||||
draw_line(Vector2(-half, 0.0), Vector2(half, 0.0), joint, 1.0)
|
||||
# Stone tile grid lines (cross at tile centre).
|
||||
draw_line(Vector2(0.0, 0.0), Vector2(0.0, TILE_SIZE_PX), joint, 1.0)
|
||||
draw_line(Vector2(-half, half), Vector2(half, half), joint, 1.0)
|
||||
|
||||
draw_rect(Rect2(Vector2(-half, -half), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.3 * alpha), false, 1.0)
|
||||
draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.3 * alpha), false, 1.0)
|
||||
|
||||
|
||||
func _draw_dirt_floor(alpha: float, half: float) -> void:
|
||||
# Top-anchored origin: rect spans (-half, 0) to (+half, TILE_SIZE_PX).
|
||||
var base := Color(0.42, 0.30, 0.18, alpha)
|
||||
|
||||
draw_rect(Rect2(Vector2(-half, -half), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base)
|
||||
draw_rect(Rect2(Vector2(-half, -half), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.25 * alpha), false, 1.0)
|
||||
draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base)
|
||||
draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.25 * alpha), false, 1.0)
|
||||
|
||||
|
||||
# ── internal ───────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ func _ready() -> void:
|
|||
tile.y * TILE_SIZE_PX + TILE_SIZE_PX
|
||||
)
|
||||
World.register_light_source(self)
|
||||
World.register_build_site(self)
|
||||
_light = _build_point_light_2d()
|
||||
add_child(_light)
|
||||
_light.enabled = false # dark until built
|
||||
|
|
@ -81,6 +82,7 @@ func _ready() -> void:
|
|||
|
||||
func _exit_tree() -> void:
|
||||
World.unregister_light_source(self)
|
||||
World.unregister_build_site(self)
|
||||
|
||||
|
||||
## One-shot initialiser. Call after add_child() so _ready() has already fired.
|
||||
|
|
|
|||
|
|
@ -217,6 +217,19 @@ func _complete() -> void:
|
|||
# Block pathfinding — wall is now impassable.
|
||||
if World.pathfinder != null:
|
||||
World.pathfinder.set_cell_walkable(tile, false)
|
||||
# Dislodge any pawn standing on this tile (Phase 6 wall-trap fix only
|
||||
# protected the BUILDING pawn via adjacent-stand; bystanders weren't
|
||||
# guarded, so a pawn idling on a queued build site would get stranded).
|
||||
for p in World.pawns:
|
||||
if p.tile == tile:
|
||||
var safe: Vector2i = World.pathfinder.find_nearest_walkable(tile)
|
||||
if safe != tile:
|
||||
p.tile = safe
|
||||
p.position = Vector2(safe.x * 16 + 8, safe.y * 16 + 8)
|
||||
p._path.clear()
|
||||
if p.job_runner != null:
|
||||
p.job_runner.cancel_job()
|
||||
Audit.log("wall", "dislodged %s from %s → %s on wall complete" % [p.pawn_name, tile, safe])
|
||||
|
||||
# Stamp the data-layer TileMap so room / roof / save logic sees the wall.
|
||||
World.mark_wall_tile(tile, wall_material)
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ func _ready() -> void:
|
|||
tile.y * TILE_SIZE_PX + TILE_SIZE_PX
|
||||
)
|
||||
World.register_workbench(self)
|
||||
World.register_build_site(self)
|
||||
# Phase 11: light-emitting workbenches register with the light-source registry.
|
||||
# All workbenches register; non-emitters return false from is_on() so
|
||||
# World.is_tile_lit() skips them at zero cost.
|
||||
|
|
@ -160,6 +161,7 @@ func _ready() -> void:
|
|||
func _exit_tree() -> void:
|
||||
World.unregister_workbench(self)
|
||||
World.unregister_light_source(self)
|
||||
World.unregister_build_site(self)
|
||||
|
||||
|
||||
## One-shot initialiser. Call after add_child() so _ready() has fired.
|
||||
|
|
|
|||
|
|
@ -1059,7 +1059,18 @@ func _advance_walk() -> void:
|
|||
return
|
||||
_step_progress += 1.0 / float(STEP_TICKS)
|
||||
if _step_progress >= 1.0:
|
||||
tile = _path[0]
|
||||
var next_tile: Vector2i = _path[0]
|
||||
# Guard: next tile may have become impassable mid-walk (e.g. another
|
||||
# pawn finished a wall on it). Walking onto it would strand us on a
|
||||
# non-walkable cell with no escape. Abort the walk; Decision reroutes.
|
||||
if World.pathfinder != null and not World.pathfinder.is_walkable(next_tile):
|
||||
_path.clear()
|
||||
_step_progress = 0.0
|
||||
if job_runner != null:
|
||||
job_runner.cancel_job()
|
||||
Audit.log("pawn", "%s walk aborted: %s became impassable" % [pawn_name, next_tile])
|
||||
return
|
||||
tile = next_tile
|
||||
_path.remove_at(0)
|
||||
_step_progress = 0.0
|
||||
if _path.is_empty():
|
||||
|
|
|
|||
|
|
@ -77,11 +77,13 @@ func _ready() -> void:
|
|||
accepted_types = []
|
||||
# Register with the World stockpile pool so HaulingProvider sees us.
|
||||
World.register_stockpile(self)
|
||||
World.register_build_site(self)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
World.unregister_stockpile(self)
|
||||
World.unregister_build_site(self)
|
||||
|
||||
|
||||
## One-shot initialiser called by the spawning / placement code.
|
||||
|
|
|
|||
|
|
@ -51,6 +51,33 @@ func is_walkable(cell: Vector2i) -> bool:
|
|||
return _astar.is_in_boundsv(cell) and not _astar.is_point_solid(cell)
|
||||
|
||||
|
||||
## BFS outward from `cell` for the nearest walkable tile. Used to dislodge a
|
||||
## pawn trapped on a tile that just became impassable (e.g. another pawn built
|
||||
## a wall on top of them). Returns `cell` itself if no walkable tile is found
|
||||
## within `max_radius` (BFS-ring distance).
|
||||
func find_nearest_walkable(cell: Vector2i, max_radius: int = 8) -> Vector2i:
|
||||
if is_walkable(cell):
|
||||
return cell
|
||||
var visited := {cell: true}
|
||||
var frontier: Array[Vector2i] = [cell]
|
||||
var offsets: Array[Vector2i] = [
|
||||
Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0),
|
||||
]
|
||||
for _ring in max_radius:
|
||||
var next: Array[Vector2i] = []
|
||||
for c in frontier:
|
||||
for off in offsets:
|
||||
var n: Vector2i = c + off
|
||||
if visited.has(n):
|
||||
continue
|
||||
visited[n] = true
|
||||
if is_walkable(n):
|
||||
return n
|
||||
next.append(n)
|
||||
frontier = next
|
||||
return cell
|
||||
|
||||
|
||||
## Returns the path from `from` to `to` as tile-coordinate steps.
|
||||
## The returned array EXCLUDES `from` and INCLUDES `to`.
|
||||
## Returns an empty Array[Vector2i] when:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue