Implements the #328 bulk-edit convention for grid layers. Writable zero-copy
views of layer data are exposed ONLY through a context manager:
with layer.edit() as view: # ColorLayer -> (h, w, 4) uint8
np.asarray(view)[...] = ... # TileLayer -> (h, w) int32 (-1 = no tile)
# on __exit__ the whole layer is conservatively invalidated (markDirty),
# so the edit re-renders.
Both layers already stored dense, contiguous, row-major data (std::vector<
sf::Color> / std::vector<int>; render caches chunk separately), so the views are
true zero-copy aliases. A new internal _LayerEdit type is the buffer exporter
(bf_getbuffer) and the context manager (__enter__ returns memoryview(self),
__exit__ calls GridLayer::markDirty()); it is not directly instantiable. The
conservative whole-layer invalidation on exit means no change-tracking is
needed, and it composes with the #351 render early-out (markDirty bumps
content_generation).
Independent of GridData SoA (#332): these are the render layers, not the logic
grid. A contiguous walkable/transparent view now has its storage prerequisite
from #332 but remains a separate follow-up.
Regression test issue_335_layer_edit_test.py: buffer shape/format/readonly,
zero-copy writeback both directions, the -1 tile sentinel, optional numpy
aliasing, and a screenshot check that __exit__ actually re-renders. API-surface
golden re-baselined (two new edit methods); docs/stubs regenerated; frozen
docstring gate 100%. Suite 312/312.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>