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>