refactor(grid): GridData is a map, not a widget -- delete UIGrid's drawable half

closes #361
closes #370
closes #371

UIGrid inherited both UIDrawable and GridData, so every map carried a second
camera and RenderTexture that nothing on screen corresponded to. Appending a
_GridData to a scene drew the whole map again through that ghost, frozen at
construction values. GridData is now pure data -- no position, no size, no
render() -- and UIGridView is the only widget.

UIGrid is deleted rather than demoted: shedding UIDrawable left an empty
subclass whose only job was to be what an aliasing shared_ptr pointed into.
GridData absorbs the texture (it defines the cell size, which every tile<->pixel
conversion depends on) and its own PythonObjectCache serial; the binding layer
becomes struct PyGridData. PyObjectsEnum::UIGRID and its 14 case arms go with it.

  * mcrfpy.GridData is public and standalone-constructible. A map with no view
    at all -- an offscreen level, still steppable and pathable -- was previously
    undefined behavior: GridData::markDirty did static_cast<UIGrid*>(this).
  * UIGridView::init constructs the GridData directly instead of building a
    throwaway Python _GridData, stealing its base subobject, and copying ~15
    fields of rendering state out of the discarded wrapper. One init() serves
    both modes, so a second view is no longer a crippled kwarg subset
    (Grid(grid=other, z_index=5) used to raise TypeError).
  * entity.grid returns the GridData. Accepted break: an entity may be on a map
    with no view or with several, so "the grid an entity is in" is not a camera.
    Migrate player.grid.center_camera(p) -> view.center_camera(p).
  * tp_name is mcrfpy.GridView; mcrfpy.Grid is an alias to the same type object.

Two latent bugs surfaced and are fixed here:

  #370  Grid.center_camera() and Grid.size = ... were SILENT NO-OPS -- neither
        was defined on the view, so both delegated to the ghost camera. On
        master, center_camera((5,5)) leaves center_x at 50.0.
  #371  The shader uniform binder strcmp'd tp_name == "mcrfpy.Grid" (the VIEW's
        name) then cast to PyUIGridObject. UB that only worked because both
        wrappers had identical layout and both classes put UIDrawable at offset 0.

Suite 327/327.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-07-13 07:14:43 -04:00
commit c696b4ef41
51 changed files with 1520 additions and 2633 deletions

File diff suppressed because one or more lines are too long