The Caption class docstring listed `font` under its Attributes, but no getter
existed -- the PyUICaptionObject.font slot was GC-managed yet never populated by
init(). Wire it up:
- init() now stores the supplied Font (incref) or, when none/None was given, a
wrapper around the engine default font, so the getter reflects what is
actually rendered rather than returning None.
- New read-only `font` getset (consistent with Sprite.texture being read-only).
Regenerated API docs/stubs/man page and rebaselined the api-surface snapshot
(one added line: Caption `prop font: Font (ro)`). Frozen docstring gate 100%,
suite 297/297.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnywUddaFRhkxo5kijxJnv
Three small bugs surfaced by the #314 docstring-accuracy verify pass:
#317 automation.scroll() dropped the x of its position argument: scroll()
resolved (x, y) but called injectMouseEvent(MouseWheelScrolled, clicks, y),
passing the scroll amount as x. injectMouseEvent now takes the scroll delta as
its own parameter and scroll() forwards the real x/y.
#318 GridView.texture always returned None (a TODO stub). It now returns a
Texture wrapper sharing the underlying shared_ptr<PyTexture>, mirroring
Grid.texture. (mcrfpy.Grid and mcrfpy.GridView are the same type post-#252, so
this fixes both names.)
#319 Entity.visible_entities(radius=None) raised TypeError: radius was parsed
with the 'i' format code, which rejects None. It now parses radius as an object
and treats None / omitted / -1 as "use the grid's default fov_radius"; a
non-int, non-None radius raises a clear TypeError.
- regression tests for each under tests/regression/
- api_surface snapshot re-baselined (visible_entities signature; texture
property now Texture | None) and docs/stubs regenerated; frozen docstring
gate still 100%
closes#317closes#318closes#319
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnywUddaFRhkxo5kijxJnv
Clip the demote+promote passes in updateVisibility() to an AABB sized to
fov_radius instead of two full-W*H walks per entity. A prev_fov window cache
demotes last tick's promoted rect (not the current one) so a moving entity
leaves no trailing "ghost vision". On a 1000x1000 grid the Phase 5.2 benchmark's
flat ~25-36 ms/entity writeback overhead collapses to single-digit microseconds
(384x-6577x speedup on the cheap algorithms; below timing noise on the rest).
Adversarial verify caught a regression the happy-path test missed: the
documented from_bytes -> assign -> update_visibility() load/resume path left
permanent ghost-VISIBLE cells, because prev_fov only bounds engine-promoted
cells. Fixed with a one-shot perspective_full_demote_pending flag (full demote
only on the tick after an external assignment; per-turn hot path stays
windowed). Documented the engine-owned demote contract on the perspective_map
property.
- src/UIEntity.cpp/.h: windowed demote/promote, prev_fov cache, demote flag
- src/DiscreteMap.cpp/.h: demoteVisibleRect(x0, y0, x1, y1)
- tests/regression/issue_316_sparse_perspective_test.py: 7-section regression
(equivalence matrix, radius-0, moving disjoint, trailing-edge, grid resize,
load/resume assignment, AABB margin lock)
- docs regenerated for the perspective_map docstring change
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnywUddaFRhkxo5kijxJnv
Follow-up to the macro conversion: an adversarial verify pass (one agent per
file vs. the C++ implementation + stubs) found 62 content issues; the real
ones are fixed here.
Callbacks (centralized):
- on_click: receives (pos, MouseButton, InputState), not str (8 files).
- on_enter/on_exit/on_move (UIBase.h): hover passes only (pos) -- removed the
fictional button/action args.
- bounds/global_bounds (UIBase.h): mark (tuple, read-only).
Signatures / types:
- Grid.find_path: document heuristic + weight; get_dijkstra_map: document roots;
compute_fov: FOV | int = FOV.BASIC (not the C constant FOV_BASIC) + Returns;
at/is_in_fov: document (pos) and (x, y) call forms.
- get_metrics: document all 16 returned dict keys (was 8); bresenham: drop the
bogus '*' keyword-only separator.
- Nullable defaults typed correctly: BSP seed/size, ColorLayer draw_fov/
apply_perspective Color|None, Entity.visible_entities radius int=-1 (None is
rejected by the 'i' parser -> see #319).
- Type-token fixes: GridView.center -> Vector; GridView.texture -> (None,
read-only) (unimplemented, #318); GridPoint.grid_pos -> (tuple, read-only);
EntityCollection.find -> Entity | list[Entity] | None; extend RuntimeError;
UniformCollection.values -> list[float | tuple | None].
- automation: onScreen (x, y) form documented; scroll notes x is ignored (#317).
Also: correct stale AStarPath/DijkstraMap signatures in docs/api-audit-2026-04.md
(the bindings were right, the audit table was outdated). Rebaseline the API
snapshot golden and regenerate docs/stubs.
Code-level bugs surfaced by the pass are filed as #317, #318, #319.
Refs #314, #317, #318, #319
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert 289 raw PyMethodDef/PyGetSetDef docstring slots to MCRF_METHOD /
MCRF_PROPERTY across the 20 frozen (non-3D) binding files, bringing the
frozen surface to 100% macro compliance (check_frozen_docstrings.sh PASS).
Done via a one-agent-per-file workflow gated by validate_file_docstrings.sh
and per-wave build/doc-rebuild checks.
- Adds #include "McRFPy_Doc.h" where missing; fills the lone genuine doc
gap (UIGrid.at, which was MISSING a doc field in two arrays).
- McRFPy_Doc.h: comment documenting the MCRF_METHOD_DOC comma rule (the
trap that broke the GridLayers conversion mid-run).
- Rebaseline api_surface golden: property types now resolve to real types
instead of "Any" (e.g. grid_pos: Vector, on_cell_click: Callable | None),
and 11 properties correctly flip rw->ro now that their docstrings carry
"read-only" (collections, grid_size, hovered_cell, texture, view — all
verified against NULL setter slots).
- Regenerate docs/stubs/man page from the new docstrings.
Module-level functions use MCRF_METHOD(<name>, ...) (expands identically to
the intended MCRF_FUNCTION; the audit's compliance set is METHOD/PROPERTY).
Experimental 3D/Voxel bindings (src/3d/) remain exempt from the freeze.
Pre-existing failures unrelated to this change: test_animation_*,
test_constructor_comprehensive (reference the removed mcrfpy.Animation and
old constructor arity).
Refs #314
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UIEntity now depends on the grid DATA layer only:
- GridData gains cell_width_px/cell_height_px (mirrored from the grid
texture in UIGrid's 5-arg ctor; texture is write-once) so entity
tile<->pixel math no longer reaches into rendering (getTexture()).
- GridData gains markDirty()/markCompositeDirty(): set the flag on the
UIGrid subobject AND notify owning_view, covering both render paths.
UIGrid disambiguates via 'using UIDrawable::markDirty' so all
pre-existing UIGrid-receiver calls resolve exactly as before.
- The three Python wrappers that still need the full UIGrid (GridPoint
from entity.at(), the _GridData fallback in get_grid, find_path's temp
wrapper) reconstruct it via a single aliasing-downcast helper
(grid_as_uigrid) that documents the never-independently-allocated
GridData invariant; init/set_grid simplify (share grid_data directly).
Removing the casts is deferred to #252.
entity.texture (new, frozen surface +1): thin get/set over the entity's
own UISprite. Entities render with their OWN texture (default_texture
fallback at construction); the grid's texture only determines cell size.
Setter preserves sprite_index; rejects non-Texture (TypeError),
null-data Texture wrappers (ValueError), and deletion.
Adversarial review fixes folded in:
- set_texture/get_texture guard uninitialized Entity wrappers
(RuntimeError), isinstance errors, and null-data Textures.
- PyUIGridViewType tp_dealloc no longer unconditionally severs
GridData::owning_view: gated on last-owner (#251 use_count pattern)
plus owning-view identity. Previously ANY Grid wrapper GC while the
view lived (e.g. scene.children.append(mcrfpy.Grid(...))) silently
broke entity.grid -> Grid identity and data-layer dirty notification.
Tests: tests/regression/issue_313_entity_grid_data_test.py (texture
semantics, grid-cell-size invariance, entity.grid identity, #251 gate
survival, GridPoint outliving teardown, review-fix guards, owning_view
survival) + tests/unit/entity_texture_test.py. API snapshot golden
re-baselined: exactly +1 surface line (Entity.texture) + writability
probe flip. Docs/stubs regenerated. Native + Emscripten builds verified.
Known edges recorded in docs/api-audit-2026-04.md: texture read-back is
a fresh wrapper each get (no Texture __eq__); sprite_index not
re-validated against a new atlas. Multi-view markDirty broadcast and
pure-GridData wrappers remain deferred to #252. Addresses #314.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regenerate HTML/Markdown API reference, man page, and type stubs against
current committed HEAD (post API-freeze pass #304-#308, #252 overhaul, and
Phase 3/4/5.1/5.2 changes).
The previous tools/generate_stubs_v2.py hand-maintained hardcoded strings,
which drifted badly: stubs still contained removed module functions
(setScale, findAll, getMetrics, setDevConsole), lacked new types (GridView,
Behavior, Trigger, DiscreteMap, Viewport3D, Entity3D, Model3D, Billboard,
NoiseSource, WangSet, LdtkProject, HeightMap, DijkstraMap, AStarPath,
ColorLayer, TileLayer, etc.), and missed post-overhaul properties
(tile_width/tile_height, sprite_grid, perspective_map, cell_pos, labels,
turn_order, move_speed, etc.).
Rewrite the generator as a runtime-introspection script mirroring
generate_dynamic_docs.py's approach:
- classify mcrfpy members (classes, enums, functions, constants, submodules)
- parse signatures from docstring first line with proper paren-depth tracking
- translate multi-form signatures (foo(x,y) or foo(pos)) to *args/**kwargs
- sanitize docstring '...' placeholder params into '**kwargs'
- emit IntEnum blocks for int-subclass types with uppercase members
- discover delegated methods via instance probing (Grid/GridView -> _GridData)
- conservative property type inference (only accept recognized primitives
and CapitalCase class names in parenthesized hints)
Resulting stubs/mcrfpy.pyi (2069 lines) parses as valid Python.
Markdown/HTML/man-page regeneration is otherwise timestamp-only since the
introspection path was already current -- the stubs were the stale artifact.
Per-entity FOV memory moves from std::vector<UIGridPointState> (two-bool
visible/discovered pairs) to a 3-state DiscreteMap (0=UNKNOWN, 1=DISCOVERED,
2=VISIBLE), exposed as entity.perspective_map. The invariant
visible-subset-of-discovered becomes structural (single value per cell), and
the map is a live, serializable, first-class object rather than an implicit
internal array.
Changes:
- New DiscreteMap C++ class with shared ownership; PyDiscreteMapObject now
holds shared_ptr<DiscreteMap>. UIEntity holds the same shared_ptr.
- New mcrfpy.Perspective IntEnum (UNKNOWN/DISCOVERED/VISIBLE), modelled on
PyInputState.
- entity.perspective_map: lazy-allocated on first access with a grid;
setter validates size against grid and raises ValueError on mismatch;
None clears (next access lazy-reallocates fresh).
- updateVisibility() now demotes 2->1 then promotes visible cells to 2.
- entity.at(x, y) returns grid.at(x, y) when VISIBLE, else None.
- Fog-of-war rendering in UIGridView and UIGrid reads the 3-state map.
- Removed: UIEntity::gridstate, ensureGridstate(), entity.gridstate getter,
UIGridPointState struct + PyUIGridPointStateType.
- Obsolete tests deleted (test_gridpointstate_point,
issue_265_gridpointstate_dangle); 4 new tests cover lazy allocation,
identity, serialization round-trip, size validation, and the
visible-subset-of-discovered invariant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Doc generator fixes (tools/generate_dynamic_docs.py):
- Add types.GetSetDescriptorType detection for C++ extension properties
- All 22 classes with properties now have documented Properties sections
- Read-only detection via "read-only" docstring convention
Scene class documentation (src/PySceneObject.h):
- Expanded tp_doc with constructor, properties, lifecycle callbacks
- Documents key advantage: on_key works on ANY scene
- Includes usage examples for basic and subclass patterns
CLAUDE.md additions:
- New section "Adding Documentation for New Python Types"
- Step-by-step guide for tp_doc, PyMethodDef, PyGetSetDef
- Documentation extraction details and troubleshooting
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BREAKING CHANGE: Constructor keyword argument renamed from `click` to
`on_click` for all UIDrawable types (Frame, Caption, Sprite, Grid, Line,
Circle, Arc).
Before: Frame(pos=(0,0), size=(100,100), click=handler)
After: Frame(pos=(0,0), size=(100,100), on_click=handler)
The property name was already `on_click` - this makes the constructor
kwarg match, completing the callback naming standardization from #139.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>