McRogueFace/tests/test_gridstate_resize.py

78 lines
2.7 KiB
Python

"""Regression test: entity gridstate must resize when moving between grids.
Bug: UIEntity::set_grid() only initialized gridstate when it was empty
(size == 0). When an entity moved from a small grid to a larger grid,
gridstate kept the old size. UIEntity::updateVisibility() then wrote
past the end of the vector using the new grid's dimensions, corrupting
adjacent heap memory.
Trigger: any entity that calls update_visibility() after moving to a
larger grid. In Liber Noster this was the player entity using the
engine's perspective FOV system across zone transitions.
This script should exit cleanly. Before the fix, it segfaulted or
produced incorrect gridstate lengths.
"""
import mcrfpy
# Create a small grid and a large grid
small = mcrfpy.Grid(grid_size=(10, 10))
large = mcrfpy.Grid(grid_size=(50, 50))
# Create an entity on the small grid
entity = mcrfpy.Entity(grid_pos=(5, 5), grid=small)
# Force gridstate initialization by calling update_visibility
small.perspective = entity
small.fov_radius = 4
entity.update_visibility() # gridstate sized to 10*10 = 100
# Verify gridstate matches small grid
gs = entity.gridstate
assert len(gs) == 100, f"Expected gridstate size 100 for 10x10 grid, got {len(gs)}"
# Move entity to the larger grid
entity.grid = large
# Gridstate must now match the large grid's dimensions
gs = entity.gridstate
assert len(gs) == 2500, f"Expected gridstate size 2500 for 50x50 grid, got {len(gs)}"
# Set up perspective on the large grid
large.perspective = entity
large.fov_radius = 8
# This triggers updateVisibility() which iterates 50*50 = 2500 cells.
# Before the fix, gridstate was only 100 entries — heap buffer overflow.
entity.update_visibility()
# Stress test: repeatedly move between grids of different sizes to
# exercise the resize path and pressure the heap allocator.
grids = [mcrfpy.Grid(grid_size=(s, s)) for s in (5, 80, 3, 60, 10, 100)]
for g in grids:
entity.grid = g
g.perspective = entity
g.fov_radius = 4
entity.update_visibility()
gs = entity.gridstate
expected = g.grid_w * g.grid_h
assert len(gs) == expected, f"Expected {expected}, got {len(gs)} for {g.grid_w}x{g.grid_h}"
# Also allocate other objects between transitions to fill freed heap
# regions — makes corruption more likely to manifest as a crash.
for i in range(20):
small_g = mcrfpy.Grid(grid_size=(5, 5))
entity.grid = small_g
small_g.perspective = entity
entity.update_visibility()
big_g = mcrfpy.Grid(grid_size=(80, 80))
entity.grid = big_g
big_g.perspective = entity
entity.update_visibility()
# Create and destroy interim objects to churn the heap
frames = [mcrfpy.Frame() for _ in range(10)]
del frames
print("PASS: gridstate resized correctly across all transitions")