The suite was reporting 331/331 while at least 82 of those tests asserted
nothing at all.
They raised during setup on APIs removed long ago -- add_layer(name=...),
GridPoint.color, mcrfpy.Animation(), entity.gridstate, mcrfpy.setScene,
assets/kenney_ice.png, GridData.compute_astar -- registered no timers, hit the
engine's auto-exit-when-no-timers path, exited 0, and were scored PASS. Their
assertions had not executed in months. test_metrics.py is the sharpest example:
the existing metrics test died on line 140 with a TypeError, which is precisely
why #341 (get_metrics counters reading 0) went unnoticed.
The engine no longer permits this (#350: a headless --exec script must call
sys.exit()), and run_tests.py no longer passes a test whose output contains a
Traceback. This commit repairs the 82 they exposed, migrating each to the
current API while preserving its original intent -- not deleting assertions to
make the command exit 0. Each repair was adversarially re-verified by a second
pass asking "is this still a test, or was it gutted?"; none were.
Two tests could not be made to pass because they were right and the engine was
wrong. Rather than paper over them they were left failing and the bugs fixed
separately in 48eef0b: DijkstraMap path order (#375) and layer-setter cache
invalidation (#376). Three integration tests had encoded the reversed Dijkstra
order as expected behavior; their assertions now state the real contract
(excludes the origin, ends at the root).
Suite: 334/334, every one of them actually asserting.
Refs #341, #350, #372
125 lines
4.2 KiB
Python
125 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Debug empty paths issue
|
|
|
|
Original intent: pathfinding was returning EMPTY paths on a fully-walkable grid.
|
|
This test verifies that A* and Dijkstra both return non-empty paths, that walls
|
|
are respected (detour), that an unreachable/blocked destination yields no path,
|
|
and that the TCOD map stays in sync with walkability changes.
|
|
|
|
API notes (2026-07): compute_astar_path/compute_dijkstra/get_dijkstra_path are gone.
|
|
Pathfinding lives on GridData: find_path() -> AStarPath | None,
|
|
get_dijkstra_map(root=...) -> DijkstraMap (cached; clear_dijkstra_maps() after
|
|
walkability changes). TCOD sync is automatic -- there is no sync_tcod_map().
|
|
"""
|
|
|
|
import mcrfpy
|
|
import sys
|
|
|
|
print("Debugging empty paths...")
|
|
|
|
failures = []
|
|
|
|
def check(cond, msg):
|
|
if cond:
|
|
print(f" OK: {msg}")
|
|
else:
|
|
print(f" FAIL: {msg}")
|
|
failures.append(msg)
|
|
|
|
# Create scene and grid
|
|
debug = mcrfpy.Scene("debug")
|
|
grid = mcrfpy.Grid(grid_size=(10, 10), pos=(0, 0), size=(320, 320))
|
|
data = grid.grid_data
|
|
|
|
# Initialize grid - all walkable
|
|
print("\nInitializing grid...")
|
|
for y in range(10):
|
|
for x in range(10):
|
|
data.at(x, y).walkable = True
|
|
|
|
# Test simple path
|
|
print("\nTest 1: Simple path from (0,0) to (5,5)")
|
|
path = data.find_path((0, 0), (5, 5))
|
|
check(path is not None, "A* found a path on an all-walkable grid")
|
|
steps = list(path) if path else []
|
|
print(f" A* path: {steps}")
|
|
print(f" Path length: {len(steps)}")
|
|
check(len(steps) > 0, "A* path is not empty")
|
|
check(path is not None and tuple(path.destination) == (5, 5), "A* path ends at (5,5)")
|
|
|
|
# Test with Dijkstra
|
|
print("\nTest 2: Same path with Dijkstra")
|
|
dmap = data.get_dijkstra_map(root=(0, 0))
|
|
dpath = dmap.path_from((5, 5))
|
|
dsteps = list(dpath) if dpath else []
|
|
print(f" Dijkstra path: {dsteps}")
|
|
print(f" Path length: {len(dsteps)}")
|
|
check(len(dsteps) > 0, "Dijkstra path is not empty")
|
|
check(dmap.distance((5, 5)) > 0, "Dijkstra distance to (5,5) is finite and positive")
|
|
|
|
# Check if grid is properly initialized
|
|
print("\nTest 3: Checking grid cells")
|
|
for y in range(3):
|
|
for x in range(3):
|
|
cell = data.at(x, y)
|
|
print(f" Cell ({x},{y}): walkable={cell.walkable}")
|
|
check(cell.walkable, f"cell ({x},{y}) reports walkable=True")
|
|
|
|
# Test with walls
|
|
print("\nTest 4: Path with wall")
|
|
for wx in (2, 3, 4):
|
|
data.at(wx, 2).walkable = False
|
|
data.clear_dijkstra_maps() # walkability changed: invalidate cached maps
|
|
print(" Added wall at y=2, x=2,3,4")
|
|
|
|
path2 = data.find_path((0, 0), (5, 5))
|
|
check(path2 is not None, "A* still finds a path around the wall")
|
|
steps2 = list(path2) if path2 else []
|
|
print(f" A* path with wall: {steps2}")
|
|
print(f" Path length: {len(steps2)}")
|
|
check(len(steps2) > 0, "walled A* path is not empty")
|
|
blocked = {(2, 2), (3, 2), (4, 2)}
|
|
check(not any(tuple(p) in blocked for p in steps2),
|
|
"A* path does not cross the wall cells (TCOD map synced with walkability)")
|
|
|
|
# Test invalid paths
|
|
print("\nTest 5: Path to blocked cell")
|
|
data.at(9, 9).walkable = False
|
|
data.clear_dijkstra_maps()
|
|
path3 = data.find_path((0, 0), (9, 9))
|
|
print(f" Path to blocked cell: {path3}")
|
|
check(path3 is None or len(list(path3)) == 0,
|
|
"no path is produced to a non-walkable destination")
|
|
|
|
# Check TCOD map sync
|
|
print("\nTest 6: Verify TCOD map is synced")
|
|
# Fully wall off (9,8) and (8,9) too -- (9,9) neighbors -- then re-open (9,9):
|
|
# a stale TCOD map would still refuse the destination.
|
|
data.at(9, 9).walkable = True
|
|
data.clear_dijkstra_maps()
|
|
path_reopened = data.find_path((0, 0), (9, 9))
|
|
check(path_reopened is not None and len(list(path_reopened)) > 0,
|
|
"re-opening a blocked cell makes it reachable again (automatic TCOD sync)")
|
|
|
|
# Try path again
|
|
print("\nTest 7: Path after mutation round-trip")
|
|
path4 = data.find_path((0, 0), (5, 5))
|
|
steps4 = list(path4) if path4 else []
|
|
print(f" A* path: {steps4}")
|
|
check(len(steps4) > 0, "A* path (0,0)->(5,5) still non-empty after mutations")
|
|
|
|
# Quick UI setup: the grid must survive being attached to a live scene.
|
|
ui = debug.children
|
|
ui.append(grid)
|
|
debug.activate()
|
|
mcrfpy.step(0.1)
|
|
check(mcrfpy.current_scene is debug, "grid attached to the active scene")
|
|
|
|
if failures:
|
|
print(f"\nFAIL ({len(failures)} check(s) failed):")
|
|
for f in failures:
|
|
print(f" - {f}")
|
|
sys.exit(1)
|
|
|
|
print("\nPASS")
|
|
sys.exit(0)
|