McRogueFace/src
John McCardle 53234288ad fix(ui): maintain the parent link in every collection mutator
The parent link (#122/#183) is meant to be the inverse of collection
membership, but only append() maintained it.

insert(), extend() and setitem() called setParent(owner.lock())
unconditionally. A scene collection has no owner drawable, so that
resolved to null -- and setParent(nullptr) also clears parent_scene. The
drawable landed in the scene's UI vector, rendered fine, and reported a
.parent of None.

The slice arms never touched the link at all. A slice-delete left the
removed child pointing at a parent that no longer contained it; a
slice-assign never parented the incoming items, nor unparented the ones
they displaced. A stale parent is a live weak_ptr to a frame the child
has left, and it is what get_global_position() walks.

Route every mutator through link_child()/unlink_child(), which are now
the single place that knows a scene parents via setParentScene() and a
drawable via setParent().

Three latent traps in the same functions, found while fixing this:

  * collection[-1] on an EMPTY collection hung the engine forever:
    `while (index < 0) index += size()` adds zero on every pass.
  * getitem's `index > size() - 1` check underflowed to SIZE_MAX when
    size() was 0, so the bounds check passed and it indexed an empty
    vector.
  * insert() and setitem() resolved the index before detaching the
    incoming drawable from its old parent. If it was already a member of
    the same collection, detaching erased it and shifted everything
    after -- so an index validated against the old size could write past
    the end. Slice-assign now builds its result as an independent vector
    for the same reason.

closes #377

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 07:53:21 -04:00
..
3d Phase 1: Safety & performance foundation for Grid/Entity overhaul 2026-03-15 21:48:24 -04:00
audio Reject non-finite floats before int casts; closes #323, closes #324, closes #325 2026-06-26 23:53:02 -04:00
ldtk Fast path for hot property getters: stop importing mcrfpy per call; closes #331 2026-07-08 21:58:50 -04:00
platform SDL key scancode fixes (7DRL 2026 hotfix) 2026-03-07 10:08:59 -05:00
scripts Remove camelCase module functions (setScale, findAll, getMetrics, setDevConsole), closes #304 2026-04-10 01:07:22 -04:00
scripts_demo Fix demo game fog layer accumulation and web IDBFS mkdir race 2026-04-10 02:19:19 -04:00
scripts_playground opacity + animation fixes 2026-02-03 12:18:21 -05:00
tiled Fast path for hot property getters: stop importing mcrfpy per call; closes #331 2026-07-08 21:58:50 -04:00
ActionCode.h Add MCRF_HEADLESS compile-time build option for #158 2026-01-30 23:09:07 -05:00
Animation.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
Animation.h perf(animation): scalar-float fast path skipping variant visits; closes #342 2026-07-10 22:59:28 -04:00
BenchmarkLogger.cpp feat: Add work_time_ms to benchmark logging for load analysis 2025-11-28 16:13:40 -05:00
BenchmarkLogger.h feat: Add work_time_ms to benchmark logging for load analysis 2025-11-28 16:13:40 -05:00
CommandLineParser.cpp fix(engine): headless step() is a full simulation frame; --exec can no longer hang 2026-07-14 06:37:52 -04:00
CommandLineParser.h Squashed commit of the following: [interpreter_mode] 2025-07-05 17:23:09 -04:00
Common.h Add SDL2+OpenGL ES 2 renderer backend for Emscripten/WebGL 2026-01-31 11:13:15 -05:00
DiscreteMap.cpp Windowed perspective writeback in UIEntity::updateVisibility; closes #316 2026-06-21 09:40:05 -04:00
DiscreteMap.h Windowed perspective writeback in UIEntity::updateVisibility; closes #316 2026-06-21 09:40:05 -04:00
emscripten_pre.js Add Emscripten shell and pre-JS for browser compatibility 2026-01-31 14:36:22 -05:00
EntityBehavior.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
EntityBehavior.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
GameEngine.cpp fix(metrics): report counters Python can actually observe 2026-07-14 06:37:33 -04:00
GameEngine.h fix(metrics): report counters Python can actually observe 2026-07-14 06:37:33 -04:00
GridData.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
GridData.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
GridLayers.cpp fix(grid): correct Dijkstra path order and layer-setter cache invalidation 2026-07-14 07:29:03 -04:00
GridLayers.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
HeadlessRenderer.cpp Squashed commit of the following: [interpreter_mode] 2025-07-05 17:23:09 -04:00
HeadlessRenderer.h Add MCRF_HEADLESS compile-time build option for #158 2026-01-30 23:09:07 -05:00
ImGuiConsole.cpp Add SDL2+OpenGL ES 2 renderer backend for Emscripten/WebGL 2026-01-31 11:13:15 -05:00
ImGuiConsole.h Add SDL2+OpenGL ES 2 renderer backend for Emscripten/WebGL 2026-01-31 11:13:15 -05:00
ImGuiSceneExplorer.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
ImGuiSceneExplorer.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
IndexTexture.cpp Refactor: Python 3.12, build libtcod & SFML from source. Cmake build. Directory cleanup 2024-02-24 22:48:39 -05:00
IndexTexture.h Refactor: Python 3.12, build libtcod & SFML from source. Cmake build. Directory cleanup 2024-02-24 22:48:39 -05:00
main.cpp fix(engine): headless step() is a full simulation frame; --exec can no longer hang 2026-07-14 06:37:52 -04:00
MapOps.h DiscreteMap class - mask for operations or uint8 tile data 2026-02-03 20:36:42 -05:00
McRFPy_API.cpp fix(metrics): report counters Python can actually observe 2026-07-14 06:37:33 -04:00
McRFPy_API.h Animation and Scene clean up functions. Playground build target 2026-02-01 21:17:29 -05:00
McRFPy_Automation.cpp fix(render): content invalidation propagates again; clear hover on focus loss 2026-07-13 07:40:20 -04:00
McRFPy_Automation.h fix(render): content invalidation propagates again; clear hover on focus loss 2026-07-13 07:40:20 -04:00
McRFPy_Doc.h F15: convert frozen binding docstrings to MCRF_* macros (#314) 2026-06-21 01:20:55 -04:00
McRogueFaceConfig.h fix(engine): headless step() is a full simulation frame; --exec can no longer hang 2026-07-14 06:37:52 -04:00
McRogueFaceVersion.h Set version 0.2.8 for release; fix Linux package libtcod SONAME 2026-06-30 19:41:58 -04:00
PathProvider.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
PathProvider.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
Profiler.cpp feat: Add comprehensive profiling system with F3 overlay 2025-10-25 00:45:44 -04:00
Profiler.h fix(metrics): report counters Python can actually observe 2026-07-14 06:37:33 -04:00
ProfilerOverlay.cpp feat: Add comprehensive profiling system with F3 overlay 2025-10-25 00:45:44 -04:00
PyAlignment.cpp Alignment: reactive or automatically calculated repositioning of UIDrawables on their parent 2026-01-13 20:40:34 -05:00
PyAlignment.h Alignment: reactive or automatically calculated repositioning of UIDrawables on their parent 2026-01-13 20:40:34 -05:00
PyAnimation.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
PyAnimation.h Remove deprecated sprite_number property from Sprite and Entity, closes #305 2026-04-09 22:18:20 -04:00
PyBehavior.cpp Phase 2: Entity data model extensions for behavior system 2026-03-15 22:05:06 -04:00
PyBehavior.h Phase 2: Entity data model extensions for behavior system 2026-03-15 22:05:06 -04:00
PyBSP.cpp F15: correct docstring accuracy from adversarial verify pass (#314) 2026-06-21 06:43:47 -04:00
PyBSP.h Add MCRF_HEADLESS compile-time build option for #158 2026-01-30 23:09:07 -05:00
PyCallable.cpp perf(input): cache Key/InputState enum members per event; closes #344 2026-07-10 22:59:47 -04:00
PyCallable.h Simplify on_enter/on_exit callbacks to position-only signature 2026-01-28 17:36:02 -05:00
PyColor.cpp Add Color.__eq__/__ne__ for value comparison, closes #307 2026-04-09 21:18:47 -04:00
PyColor.h Freeze value semantics + record 1.0 compatibility policy 2026-07-02 20:23:11 -04:00
PyDiscreteMap.cpp feat(discretemap): buffer protocol for zero-copy numpy views; closes #334 2026-07-11 01:00:53 -04:00
PyDiscreteMap.h feat(discretemap): buffer protocol for zero-copy numpy views; closes #334 2026-07-11 01:00:53 -04:00
PyDrawable.cpp F15: correct docstring accuracy from adversarial verify pass (#314) 2026-06-21 06:43:47 -04:00
PyDrawable.h Squashed commit of 53 Commits: [alpha_streamline_2] 2025-07-09 22:41:15 -04:00
PyEasing.cpp animation loop parameter 2026-02-27 22:11:29 -05:00
PyEasing.h Easing functions as enum 2026-01-04 12:59:28 -05:00
PyFont.cpp Replace PyObject_GetAttrString with direct type references 2026-03-07 23:18:42 -05:00
PyFont.h Improve terse docstrings on Vector, Font, Texture, GridPoint, GridPointState 2026-04-09 21:18:33 -04:00
PyFOV.cpp feat: Implement FOV enum and layer draw_fov for #114 and #113 2025-12-01 15:18:10 -05:00
PyFOV.h feat: Implement FOV enum and layer draw_fov for #114 and #113 2025-12-01 15:18:10 -05:00
PyGridData.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
PyGridData.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
PyGridDataMethods.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
PyGridDataProperties.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
PyHeightMap.cpp Replace PyObject_GetAttrString with direct type references 2026-03-07 23:18:42 -05:00
PyHeightMap.h Migrate static PyTypeObject to inline, delete PyTypeCache workarounds 2026-02-16 20:58:09 -05:00
PyHeuristic.cpp Extend pathfinding API with heuristics, multi-root Dijkstra, and FLEE primitives; refs #315 2026-04-18 09:18:49 -04:00
PyHeuristic.h Extend pathfinding API with heuristics, multi-root Dijkstra, and FLEE primitives; refs #315 2026-04-18 09:18:49 -04:00
PyInputState.cpp perf(input): cache Key/InputState enum members per event; closes #344 2026-07-10 22:59:47 -04:00
PyInputState.h perf(input): cache Key/InputState enum members per event; closes #344 2026-07-10 22:59:47 -04:00
PyKey.cpp perf(input): cache Key/InputState enum members per event; closes #344 2026-07-10 22:59:47 -04:00
PyKey.h perf(input): cache Key/InputState enum members per event; closes #344 2026-07-10 22:59:47 -04:00
PyKeyboard.cpp feat: Add Sound/Music classes, keyboard state, version (#66, #160, #164) 2025-12-29 16:24:27 -05:00
PyKeyboard.h Migrate static PyTypeObject to inline, delete PyTypeCache workarounds 2026-02-16 20:58:09 -05:00
PyLock.cpp F15: correct docstring accuracy from adversarial verify pass (#314) 2026-06-21 06:43:47 -04:00
PyLock.h Fix #219: Add threading support with mcrfpy.lock() context manager 2026-01-19 23:37:49 -05:00
PyMouse.cpp Replace PyObject_GetAttrString with direct type references 2026-03-07 23:18:42 -05:00
PyMouse.h Migrate static PyTypeObject to inline, delete PyTypeCache workarounds 2026-02-16 20:58:09 -05:00
PyMouseButton.cpp Remove legacy string enum comparisons from InputState/Key/MouseButton, closes #306 2026-04-09 22:19:02 -04:00
PyMouseButton.h Remove legacy string enum comparisons from InputState/Key/MouseButton, closes #306 2026-04-09 22:19:02 -04:00
PyMusic.cpp Replace PyObject_GetAttrString with direct type references 2026-03-07 23:18:42 -05:00
PyMusic.h Migrate static PyTypeObject to inline, delete PyTypeCache workarounds 2026-02-16 20:58:09 -05:00
PyNoiseSource.cpp Replace PyObject_GetAttrString with direct type references 2026-03-07 23:18:42 -05:00
PyNoiseSource.h Noise, combination, and sampling: first pass at #207, #208, #194, #209 2026-01-12 19:01:20 -05:00
PyPerspective.cpp Replace UIEntity gridstate with DiscreteMap perspective_map; closes #294 2026-04-17 23:04:27 -04:00
PyPerspective.h Replace UIEntity gridstate with DiscreteMap perspective_map; closes #294 2026-04-17 23:04:27 -04:00
PyPositionHelper.h Positions are always mcrfpy.Vector, Vector/tuple/iterables expected as inputs, and for position-only inputs we permit x,y args to prevent requiring double-parens 2026-01-05 10:16:16 -05:00
PyScene.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
PyScene.h fix(input): dispatch hover-exit when the cursor leaves the window 2026-07-12 21:12:58 -04:00
PySceneObject.cpp perf(scene): skip per-frame update() lookup for non-subclassed scenes; closes #343 2026-07-10 23:00:04 -04:00
PySceneObject.h Migrate static PyTypeObject to inline, delete PyTypeCache workarounds 2026-02-16 20:58:09 -05:00
PyShader.cpp Shaders 2026-01-25 21:04:01 -05:00
PyShader.h Shaders 2026-01-25 21:04:01 -05:00
PySound.cpp Replace PyObject_GetAttrString with direct type references 2026-03-07 23:18:42 -05:00
PySound.h Add SoundBuffer type: procedural audio, sfxr synthesis, DSP effects 2026-02-19 18:58:11 -05:00
PySoundBuffer.cpp Reject non-finite floats before int casts; closes #323, closes #324, closes #325 2026-06-26 23:53:02 -04:00
PySoundBuffer.h Audio fixes: gain() DSP effect, sfxr phase wrap, SDL2 backend compat 2026-02-20 23:17:41 -05:00
PyTexture.cpp Reject non-finite floats before int casts; closes #323, closes #324, closes #325 2026-06-26 23:53:02 -04:00
PyTexture.h Add texture display bounds for non-uniform sprite content, closes #235 2026-04-10 02:57:41 -04:00
PythonObjectCache.cpp Fix PythonObjectCache race and document die() iteration (closes #269, closes #273) 2026-03-07 23:33:05 -05:00
PythonObjectCache.h Squashed commit of the following: [alpha_presentable] 2025-07-15 21:30:49 -04:00
PyTimer.cpp Fix callback/timer GC: prevent premature destruction of Python callbacks 2026-02-19 20:53:50 -05:00
PyTimer.h Migrate static PyTypeObject to inline, delete PyTypeCache workarounds 2026-02-16 20:58:09 -05:00
PyTransition.cpp scene transitions via Scene object 2026-01-03 13:53:18 -05:00
PyTransition.h scene transitions via Scene object 2026-01-03 13:53:18 -05:00
PyTrigger.cpp Phase 2: Entity data model extensions for behavior system 2026-03-15 22:05:06 -04:00
PyTrigger.h Phase 2: Entity data model extensions for behavior system 2026-03-15 22:05:06 -04:00
PyUniformBinding.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
PyUniformBinding.h Shaders 2026-01-25 21:04:01 -05:00
PyUniformCollection.cpp F15: correct docstring accuracy from adversarial verify pass (#314) 2026-06-21 06:43:47 -04:00
PyUniformCollection.h Fix UniformCollection owner validity check (closes #272) 2026-03-08 17:07:14 -04:00
PyVector.cpp Reject non-finite floats before int casts; closes #323, closes #324, closes #325 2026-06-26 23:53:02 -04:00
PyVector.h Freeze value semantics + record 1.0 compatibility policy 2026-07-02 20:23:11 -04:00
PyWindow.cpp Add MCRF_HEADLESS compile-time build option for #158 2026-01-30 23:09:07 -05:00
PyWindow.h Migrate static PyTypeObject to inline, delete PyTypeCache workarounds 2026-02-16 20:58:09 -05:00
Resources.cpp Adding text functionality to Caption (why was that missing???) 2024-03-07 12:35:07 -05:00
Resources.h Adding text functionality to Caption (why was that missing???) 2024-03-07 12:35:07 -05:00
Scene.cpp feat: Implement Phase A UI hierarchy foundations (closes #122, #102, #116, #118) 2025-11-27 16:33:17 -05:00
Scene.h feat: Implement Phase A UI hierarchy foundations (closes #122, #102, #116, #118) 2025-11-27 16:33:17 -05:00
SceneTransition.cpp Mobile-"ish" emscripten support 2026-02-09 08:40:34 -05:00
SceneTransition.h Mobile-"ish" emscripten support 2026-02-09 08:40:34 -05:00
shell.html Cross-platform persistent save directory (IDBFS on WASM, filesystem on desktop) 2026-02-25 20:42:44 -05:00
shell_game.html Cross-platform persistent save directory (IDBFS on WASM, filesystem on desktop) 2026-02-25 20:42:44 -05:00
SpatialHash.cpp Add multi-tile entity support with tile_width/tile_height, closes #236 2026-04-10 02:57:47 -04:00
SpatialHash.h Phase 2: Entity data model extensions for behavior system 2026-03-15 22:05:06 -04:00
Timer.cpp Fix callback/timer GC: prevent premature destruction of Python callbacks 2026-02-19 20:53:50 -05:00
Timer.h Fix callback/timer GC: prevent premature destruction of Python callbacks 2026-02-19 20:53:50 -05:00
UI.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIArc.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIArc.h Fix callback/timer GC: prevent premature destruction of Python callbacks 2026-02-19 20:53:50 -05:00
UIBase.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UICaption.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UICaption.h Add read-only Caption.font getter; addresses #320 2026-06-21 12:18:53 -04:00
UICircle.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UICircle.h Fix callback/timer GC: prevent premature destruction of Python callbacks 2026-02-19 20:53:50 -05:00
UICollection.cpp fix(ui): maintain the parent link in every collection mutator 2026-07-14 07:53:21 -04:00
UICollection.h Python API improvements: Vectors, bounds, window singleton, hidden types 2026-01-05 23:00:48 -05:00
UIDrawable.cpp fix(bindings): route every drawable wrapper through PythonObjectCache 2026-07-13 23:37:01 -04:00
UIDrawable.h fix(bindings): route every drawable wrapper through PythonObjectCache 2026-07-13 23:37:01 -04:00
UIEntity.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIEntity.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIEntityCollection.cpp fix(bindings): route every drawable wrapper through PythonObjectCache 2026-07-13 23:37:01 -04:00
UIEntityCollection.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIEntityPyMethods.h Fix UniformCollection owner validity check (closes #272) 2026-03-08 17:07:14 -04:00
UIFrame.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIFrame.h Fix callback/timer GC: prevent premature destruction of Python callbacks 2026-02-19 20:53:50 -05:00
UIGridPathfinding.cpp fix(grid): correct Dijkstra path order and layer-setter cache invalidation 2026-07-14 07:29:03 -04:00
UIGridPathfinding.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIGridPoint.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIGridPoint.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UIGridView.cpp fix(metrics): report counters Python can actually observe 2026-07-14 06:37:33 -04:00
UIGridView.h refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UILine.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UILine.h Fix callback/timer GC: prevent premature destruction of Python callbacks 2026-02-19 20:53:50 -05:00
UISprite.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UISprite.h Add compound Color and Vector animation targets (pos, fill_color), closes #218 2026-04-10 02:05:55 -04:00
UITestScene.cpp refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half 2026-07-13 07:14:43 -04:00
UITestScene.h Refactor: remove "s" prefix from "sRender" method ( -> "render") Closes #44 2024-04-20 14:16:14 -04:00