pub struct Grid { /* private fields */ }Expand description
A 2D buffer of Tiles, addressable across up to 256 stacked layers.
Layer 0 is always allocated; higher layers are allocated on first write, growing the
layer-table Vec up to that layer’s id as needed (see Grid::new). Single-layer use pays
no overhead: layers 1+ stay unallocated until used, and the layer table itself never grows
past a single slot.
§Out-of-bounds drawing
Drawing off the grid is a no-op, the same convention as drawing off-screen: every write method
that names a position or region (e.g. put_tile, write_grapheme,
write_span, blit) silently discards any part of the
write that falls outside 0..width / 0..height, rather than panicking. The one deliberate
exception is indexing (Index<Pos>/IndexMut<Pos>, and by extension anything built on it),
which panics on an out-of-bounds Pos the same way indexing a slice does. Read accessors
that take a position (e.g. tile) report an out-of-bounds position as None,
indistinguishable from an unallocated layer.
Requires an allocator (backed by alloc::vec::Vec), so it is unavailable
in strictly static, no-alloc environments.
§Examples
use retroglyph_core::color::{Color, Style};
use retroglyph_core::grid::{Grid, Pos};
let mut grid = Grid::new(10, 5);
grid.put_tile(0, Pos::new(2, 1), retroglyph_core::tile::Tile::new('@', Style::new().fg(Color::GREEN)));
assert_eq!(grid[Pos::new(2, 1)].glyph(), '@');Implementations§
Source§impl Grid
impl Grid
Sourcepub fn new(width: u16, height: u16) -> Self
pub fn new(width: u16, height: u16) -> Self
Creates a new grid of the given dimensions.
Layer 0 is allocated immediately. Layers 1–255 are None until first
write via put_tile; the layer table itself only
grows as far as the highest layer id ever written, not all 256 slots
up front.
height may be 0 (an empty grid with no rows). resize may shrink an
existing grid to 0 on either axis, including width; only construction requires a nonzero
width.
§Panics
Panics if width is 0.
Sourcepub fn from_charmap<F>(map: &str, f: F) -> Self
pub fn from_charmap<F>(map: &str, f: F) -> Self
Builds a grid from a rectangular character map, one Tile per cell.
map is split on \n; the grid width is the longest line’s display
width (unicode-width’s UnicodeWidthStr)
and the height is the number of lines. Lines shorter than the widest are
padded with the default tile. f maps each character to its tile,
called once per character in reading order.
Each character is written through put_tile at its own
display column, so a 2-column (wide) character gets the same
TileFlags::WIDE_CHAR/TileFlags::WIDE_CHAR_SPACER lead/spacer pair
put_tile writes for any other fresh wide tile; the next character in the
line lands one column further along, past the spacer. A wide character in
the map’s last column has no room for its spacer and is refused, the same
as any other put_tile call in that position.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos};
use retroglyph_core::tile::Tile;
// A ragged map: the second line is shorter than the first.
let grid = Grid::from_charmap("###\n#.", |c| match c {
'#' => Tile::new('#', Style::default()),
_ => Tile::default(),
});
// Width comes from the longest line; the shorter line is padded with the default
// tile rather than truncating the grid to the shortest line.
assert_eq!((grid.width(), grid.height()), (3, 2));
assert_eq!(grid[Pos::new(0, 0)].glyph(), '#');
assert_eq!(grid[Pos::new(1, 1)].glyph(), ' '); // '.' maps to the default tile
assert_eq!(grid[Pos::new(2, 1)].glyph(), ' '); // padding past the short line's endSourcepub const fn height(&self) -> u16
pub const fn height(&self) -> u16
The grid’s height in cells (rows), not pixels. Valid row indices are 0..height; a Pos
with y >= height is out of bounds. May be 0 (an empty grid with no rows).
Sourcepub const fn max_layer(&self) -> u8
pub const fn max_layer(&self) -> u8
Returns the highest layer id that has ever been allocated.
Always at least 0 (layer 0 is always allocated). This only grows:
clearing a layer (clear) does not deallocate it, so
the value does not shrink once a higher layer has been written.
This is the layer id’s steady-state cost: every present, diff, and
full-grid iteration walks 0..=max_layer, skipping unallocated slots
with an O(1) None check, so compositing is O(max_layer) per cell
rather than O(topmost opaque layer). Writing once to layer 200 and
never touching layers 1-199 means every future frame walks past 199
None slots to reach it: cheap per skipped layer, but not free, which
is why low, contiguous ids are preferred for frequently-updated
content.
Sourcepub fn clear(&mut self, layer: u8)
pub fn clear(&mut self, layer: u8)
Clears a specific layer, resetting all tiles to the default.
Does nothing if the layer is unallocated.
Sourcepub fn resize(&mut self, width: u16, height: u16)
pub fn resize(&mut self, width: u16, height: u16)
Resizes the grid to width × height tiles.
Content within the overlapping region is preserved on all allocated layers. New cells are initialised to the default tile. Shrinking discards tiles outside the new bounds.
Shrinking can also orphan two structures that span more than one cell, since resize
keeps the top-left corner but a shrink can slice through a footprint’s far edge:
- A
TileFlags::WIDE_CHARlead left in the new last column, with itsTileFlags::WIDE_CHAR_SPACERnow out of bounds, is reset – the same thingclear_overlapdoes when an ordinary write orphans one. - A
TileFlags::SPAN_ANCHORwhose declared footprint no longer fits has its whole span cleared viareset_span_at, rather than left claiming a truncated area. Half a span is not representable, the same reasoningblitdocuments for clipping one.
Both repairs are bounded by the shrunk edge, not the whole grid, so a growing resize pays nothing for either.
Sourcepub fn write_grapheme(
&mut self,
layer: u8,
x: u16,
y: u16,
grapheme: &str,
style: Style,
) -> bool
pub fn write_grapheme( &mut self, layer: u8, x: u16, y: u16, grapheme: &str, style: Style, ) -> bool
Writes a grapheme cluster at (x, y) on layer 0, enforcing wide-
character invariants.
This is the canonical way to place content into the grid when the egc
feature is enabled. It:
- Clears any wide character whose primary or spacer cell would be overwritten.
- Sets
TileFlags::WIDE_CHARon the primary cell and places aTileFlags::WIDE_CHAR_SPACERin the adjacent cell for 2-column characters. - Stores multi-codepoint EGCs (combining marks, ZWJ sequences) in the
layer’s EGC side-table, capped at 8 codepoints total. Read it back via
DrawCell::grapheme, streamed offGrid::layers.
Does nothing, and returns false, if the grapheme has zero display width, (x, y) is out
of bounds, or a 2-column wide character would overflow the grid (the last column needs
both its own cell and a spacer). Returns true otherwise, once the write has landed: the
same success/refusal split put_tile reports via Option.
§Panics
Panics if the grapheme’s display width exceeds u16::MAX. In
practice this cannot happen: the maximum Unicode grapheme width is 2.
Only present when the egc feature is enabled.
Source§impl Grid
impl Grid
Sourcepub fn diff<'a>(
&'a self,
other: &'a Self,
) -> impl Iterator<Item = DrawCell<'a>> + 'a
pub fn diff<'a>( &'a self, other: &'a Self, ) -> impl Iterator<Item = DrawCell<'a>> + 'a
Yield a DrawCell for every changed position across all layers, in layer-major
(0 → max(self.max_layer, other.max_layer)) then row-major order.
Four cases per layer:
- Layer absent in both
selfandother: nothing yielded. - Layer in
self, absent inother(newly allocated): allwidth × heighttiles yielded. - Layer in both, and
selfandotherhave matching dimensions: only positions where theTileor its side-table entry (grapheme text, tint) differs are yielded. - Layer in both, but
selfandotherhave different dimensions: all positions inselfare considered changed, same as a newly allocated layer. - Layer in
otherbut no longer inself(stopped being written): every position is yielded as a cleared, defaultTile, sized toother’s dimensions. This case is only expressible whenselfandotherhave matching dimensions; a simultaneous size change and layer teardown falls back to “nothing yielded” for that layer, matching a layer absent from both.
This iterator is zero-allocation: it walks the layer buffers inline.
Source§impl Grid
impl Grid
Sourcepub fn blit(
&mut self,
layer: u8,
src: &Self,
src_rect: Rect,
dst_x: u16,
dst_y: u16,
)
pub fn blit( &mut self, layer: u8, src: &Self, src_rect: Rect, dst_x: u16, dst_y: u16, )
Copies tiles from src within src_rect to self at (dst_x, dst_y)
on layer. Empty tiles (nothing written; see Tile::is_empty) are
treated as transparent and skipped. An explicit space is copied and
overwrites the destination.
Multi-cell spans (see write_span) do not survive a blit: copied
tiles keep their glyphs but lose TileFlags::SPAN_ANCHOR/TileFlags::SPAN_COVERED,
so a span degrades to exactly its text fallback. src_rect can clip a span in half, and
half a span is not a thing the grid can represent; degrading to the fallback glyphs is
both representable and the same content a cell backend would have drawn anyway.
The same is true of wide-character pairs: src_rect clipping a lead from its spacer, or
the copy landing on only one half of a destination pair, both leave half a pair, which is
equally unrepresentable. Either case strips TileFlags::WIDE_CHAR/
TileFlags::WIDE_CHAR_SPACER from the surviving half (or clears the destination half
the copy overwrites), so a blit can never leave a dangling lead or an orphaned spacer
behind (retroglyph#1013).
Walks src’s and self’s layer buffers directly by flat index instead of going through
tile/put_tile per cell (see retroglyph#263):
each of those recomputes a coordinate conversion and a bounds check per cell, which this
does once per row instead. The destination layer is allocated once, up front, rather than
as a side effect of the first written cell, but only if src_rect (clamped to src’s
bounds) contains at least one non-empty tile, matching put_tile’s original
allocate-on-first-write behavior for a src_rect that is entirely transparent.
Sourcepub fn blit_alpha(
&mut self,
layer: u8,
src: &Self,
src_rect: Rect,
dst_x: u16,
dst_y: u16,
mode: BlendMode,
fg_alpha: f32,
bg_alpha: f32,
)
pub fn blit_alpha( &mut self, layer: u8, src: &Self, src_rect: Rect, dst_x: u16, dst_y: u16, mode: BlendMode, fg_alpha: f32, bg_alpha: f32, )
Same as blit but blends foreground and background
colors with the given alpha factors, using mode to compute the
blended color. fg_alpha and bg_alpha are in 0.0-1.0 range where
0.0 = keep destination, 1.0 = replace with src; for a non-
Linear mode, “replace with src” instead means
“replace with mode’s fully blended color” (see BlendMode).
Blending operates on packed RGB values; Color::Default preserves
the destination. Non-RGB color variants (Ansi/Indexed) are passed
through unblended, regardless of mode.
BlendMode::Linear’s per-channel color lerp is delegated to [gem::Mix]. The other
modes delegate to [alpha_blend::BlendMode] (imported in this module as
SeparableBlendMode to avoid colliding with this crate’s own BlendMode).
Like blit (see retroglyph#262/#263), walks src’s and self’s layer
buffers directly by flat index instead of per-cell tile/
put_tile, and allocates the destination layer once, up front, rather
than as a side effect of the first written cell.
Source§impl Grid
impl Grid
Sourcepub fn layers(&self) -> impl Iterator<Item = DrawCell<'_>> + '_
pub fn layers(&self) -> impl Iterator<Item = DrawCell<'_>> + '_
Yield a DrawCell for every allocated cell across all layers, in
layer-major (0 → max_layer) then row-major order. grapheme is
Some only when TileFlags::HAS_EXTRA is set.
Unallocated layers are skipped. This is used by backends that need
the full frame on every draw (see crate::backend::Output::needs_full_frame).
This iterator is zero-allocation: it walks the layer buffers inline.
Source§impl Grid
impl Grid
Sourcepub fn tint(&self, layer: u8, x: u16, y: u16) -> Tint
pub fn tint(&self, layer: u8, x: u16, y: u16) -> Tint
How a pixel backend recolours the sprite drawn for the cell at (x, y) on layer.
Tint::None for a cell that has never been tinted, for a cell whose glyph was
overwritten since (a glyph write drops the tint with the artwork it belonged to), and for
coordinates outside the grid or on an unallocated layer.
A tint is grid state rather than Tile state, for the same reason a multi-codepoint
grapheme is: it is rare per cell and Tile has no room
left. So it is read here, not through Tile::style.
Cell backends have no sprite to recolour and ignore this entirely.
Sourcepub fn set_tint(&mut self, layer: u8, x: u16, y: u16, tint: Tint)
pub fn set_tint(&mut self, layer: u8, x: u16, y: u16, tint: Tint)
Sets how a pixel backend recolours the sprite drawn for the cell at (x, y) on layer.
Applies to the cell as it stands, so it belongs after the write that put the glyph there: writing a glyph over a tinted cell drops the tint, on the grounds that a tint describes the artwork rather than the position. For a multi-cell span, tint the anchor; that is the cell a pixel backend draws the sprite from.
Setting Tint::None clears the tint, and drops the cell’s side-table entry entirely if
it held nothing else. Does nothing if (x, y) is out of bounds.
Source§impl Grid
impl Grid
Sourcepub fn put_tile(
&mut self,
layer: u8,
pos: impl Into<Pos>,
tile: Tile,
) -> Option<()>
pub fn put_tile( &mut self, layer: u8, pos: impl Into<Pos>, tile: Tile, ) -> Option<()>
Writes a tile to layer at pos, honoring tile’s own precomputed
width: a fresh 2-column tile also gets a
TileFlags::WIDE_CHAR_SPACER at pos.x + 1, the same pairing
write_grapheme writes, on every feature combination (Tile::width
comes from unicode-width, an unconditional dependency, not the egc-gated
unicode-segmentation).
Allocates the layer if it has not been written to yet. Returns None if pos is out of
bounds, or if a fresh tile is 2 columns wide and pos.x + 1 (the spacer’s column) is
not: the same last-column refusal write_grapheme makes, rather than leaving an orphaned
primary cell with no spacer.
To read back, use tile.
§Replaying an already-resolved tile
The wide-char synthesis above only applies to a fresh tile: one built through public
API (Tile::new, with_glyph, Tile::default), which can never
carry TileFlags::WIDE_CHAR/TileFlags::WIDE_CHAR_SPACER (both pub(crate)-only to
set). A tile that already carries either flag is, by construction, an already-resolved
tile read back out of some grid (e.g. Headless replaying a
DrawCell stream verbatim into its own copy) rather than a new
glyph placement, and is written through exactly as given, with no bounds refusal, spacer
synthesis, or overlap clearing of its own: those already happened on the call that
produced it, and re-running them here would (for a spacer tile specifically) clear the
other half of the very same wide pair being replayed, mistaking it for some unrelated
write landing on that spacer.
Any tile written this way has its extra grapheme text cleared, since a
caller-constructed Tile can never legitimately carry
TileFlags::HAS_EXTRA (the flag is crate-private). Internal callers
that need to preserve EGC text across a copy (e.g. blit)
follow up with a direct extras-table write. Any multi-cell span the
cell belongs to is cleared first, so a write can never leave an anchor
pointing at cells it no longer owns; a fresh wide tile additionally clears any wide
character it would partially overwrite, the same as write_grapheme.
tile’s own TileFlags::SPAN_ANCHOR/TileFlags::SPAN_COVERED role, if it has one, is
stripped too, for the same reason as HAS_EXTRA: those flags are crate-private, so a
caller-supplied tile (fresh or replayed, e.g. read back via tile) can
only carry one by copying it out of some other cell, and writing it through verbatim would
plant an anchor with no covered cells (or a covered cell with no anchor) at pos –
exactly the dangling footprint write_span’s own doc calls a broken
invariant. blit makes the same call for a copied span it cannot preserve
whole.
Sourcepub fn fill_region(&mut self, layer: u8, rect: Rect, tile: Tile)
pub fn fill_region(&mut self, layer: u8, rect: Rect, tile: Tile)
Fills every cell of rect (clipped to this grid) on layer with tile.
The batch counterpart to calling put_tile once per cell of rect:
same result, but the span/extras bookkeeping and the layer allocation each happen once for
the whole region rather than once per cell, and the write itself is one
fill_rect_solid call instead of rect.width() * rect.height() individual cell writes. Surface::fill_rect,
Surface::clear, and
Surface::clear_region are built on this.
A no-op if rect (after clipping to the grid) is empty. As with put_tile, tile can
never legitimately carry TileFlags::HAS_EXTRA (the flag is crate-private), so every
cell’s own extras entry, if any, is dropped rather than orphaned. tile’s own span role,
if it has one, is stripped for the same reason: see put_tile’s doc for why writing it
through verbatim would plant a dangling anchor or an anchorless covered cell in every cell
of rect.
Also a no-op if tile.width() != 1: unlike put_tile, this does not synthesize
TileFlags::WIDE_CHAR/TileFlags::WIDE_CHAR_SPACER lead/spacer pairs across the
region, so a wide tile (or a zero-width one) would otherwise leave every cell in rect
carrying the same glyph with no spacer, desyncing any cursor-advancing consumer that
trusts Tile::width/TileFlags::WIDE_CHAR_SPACER to track column position. Callers with a
wide glyph need a per-cell put_tile loop instead; see
Surface::fill_rect’s own fallback.
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos, Rect};
use retroglyph_core::tile::Tile;
let mut grid = Grid::new(4, 4);
grid.fill_region(0, Rect::new(1, 1, 2, 2), Tile::new('#', Style::default()));
assert_eq!(grid[Pos::new(1, 1)].glyph(), '#');
assert_eq!(grid[Pos::new(2, 2)].glyph(), '#');
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');Sourcepub fn tile(&self, layer: u8, pos: impl Into<Pos>) -> Option<&Tile>
pub fn tile(&self, layer: u8, pos: impl Into<Pos>) -> Option<&Tile>
Reads a tile on layer at pos, or None if the layer is
unallocated or pos is out of bounds.
Sourcepub fn tile_mut(&mut self, layer: u8, pos: impl Into<Pos>) -> Option<&mut Tile>
pub fn tile_mut(&mut self, layer: u8, pos: impl Into<Pos>) -> Option<&mut Tile>
Mutably borrows a tile on layer at pos, or None if the layer is
unallocated or pos is out of bounds.
This hands out a direct &mut Tile, so it cannot intercept a write the way
put_tile does: it does not clear a multi-cell span pos belongs to,
and it does not clear grapheme extras stored for the tile. Call
clear_span first if pos may belong to a span.
Source§impl Grid
impl Grid
Sourcepub fn write_span<S: AsRef<str>>(
&mut self,
layer: u8,
x: u16,
y: u16,
rows: &[S],
style: Style,
) -> Option<()>
pub fn write_span<S: AsRef<str>>( &mut self, layer: u8, x: u16, y: u16, rows: &[S], style: Style, ) -> Option<()>
Writes a multi-cell span at (x, y) on layer: one piece of artwork occupying a block of
cells rather than one.
rows holds one string per row of the footprint, so the span is rows.len() cells tall
and rows[0]’s character count wide, and every row must be that same width. Any
AsRef<str> row works, so a literal footprint (&["[==]", "|__|"]) and a computed one
(&Vec<String>) both pass without a borrowing pass over the rows. The first
character goes to the anchor cell at (x, y) with TileFlags::SPAN_ANCHOR; each
remaining character goes to its own cell with TileFlags::SPAN_COVERED. style applies
to every cell.
§Text fallback
The covered cells keep real glyphs, which is what lets one call render correctly on every backend with no capability check:
- A cell backend (
Headless,retroglyph-crossterm,retroglyph-terminal) ignoresTileFlags::SPAN_COVEREDand prints all of them, so["[==]", "|__|"]reads as a small piece of ASCII art. - A pixel backend (
retroglyph-software,retroglyph-gl) looks the anchor glyph up in its sprite cache, draws that one sprite across the whole footprint, and skips every covered cell’s glyph.
This is the deliberate difference from TileFlags::WIDE_CHAR_SPACER, which every
backend skips.
Any existing span or wide character the footprint would partially overwrite is cleared
first, in full, as write_grapheme does for its own 1- or 2-cell
write.
For the common sprite case (one runtime-chosen anchor glyph, blanks in every covered
cell), write_span_uniform says the same thing without
building the rows.
§Returns
Some(()) once the whole span is written, or None having written nothing at all when
rows is empty, its first row is empty, its rows differ in width, either axis exceeds 255
cells, or the footprint would not fit in the grid at (x, y).
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos};
let mut grid = Grid::new(8, 4);
grid.write_span(0, 1, 1, &["[==]", "|__|"], Style::default())?;
assert_eq!(grid.tile(0, Pos::new(1, 1))?.span(), (4, 2));
// Covered cells keep their fallback glyphs, and name their anchor.
assert_eq!(grid.tile(0, Pos::new(4, 2))?.glyph(), '|');
assert_eq!(grid.span_owner(0, 4, 2), Some(Pos::new(1, 1)));Sourcepub fn write_span_uniform(
&mut self,
layer: u8,
pos: impl Into<Pos>,
size: impl Into<Size>,
anchor: char,
fill: char,
style: Style,
) -> Option<()>
pub fn write_span_uniform( &mut self, layer: u8, pos: impl Into<Pos>, size: impl Into<Size>, anchor: char, fill: char, style: Style, ) -> Option<()>
Writes a size multi-cell span at pos on layer: anchor in the anchor cell, fill
in every other cell of the footprint.
The uniform case of write_span, and the shape a sheet-driven
renderer usually wants: one sprite, chosen at runtime, with the cells it covers blanked so
nothing shows through its transparent pixels. Spelling that as an array of blank rows
carries no information and, for a computed anchor, has to be allocated per draw.
fill is what a cell backend prints for the covered cells (a pixel backend skips them
and draws the sprite instead), so it is the span’s text fallback: ' ' blanks them, and a
visible character keeps the footprint legible in a terminal. See
write_span for the full write semantics.
§Returns
Some(()) once the whole span is written, or None having written nothing at all when
either axis of size is 0 or exceeds 255 cells, or the footprint would not fit in the
grid at pos.
§Examples
use retroglyph_core::color::Style;
use retroglyph_core::grid::{Grid, Pos};
let mut grid = Grid::new(8, 4);
let anchor = '\u{E000}'; // chosen at runtime from a tilesheet
grid.write_span_uniform(0, (1, 1), (2, 2), anchor, ' ', Style::default())?;
assert_eq!(grid.tile(0, Pos::new(1, 1))?.span(), (2, 2));
assert_eq!(grid.span_owner(0, 2, 2), Some(Pos::new(1, 1)));Sourcepub fn span_owner(&self, layer: u8, x: u16, y: u16) -> Option<Pos>
pub fn span_owner(&self, layer: u8, x: u16, y: u16) -> Option<Pos>
The anchor of the multi-cell span occupying (x, y) on layer, or None when the cell
belongs to no span or is out of bounds.
An anchor cell reports itself, so every cell of one span answers with the same position and hit-testing multi-cell artwork is a single comparison:
grid.write_span(0, 2, 1, &["[==]", "|__|"], Style::default())?;
let chest = Pos::new(2, 1);
// Any of the eight cells counts as standing on the chest.
assert_eq!(grid.span_owner(0, 2, 1), Some(chest));
assert_eq!(grid.span_owner(0, 5, 2), Some(chest));
assert_eq!(grid.span_owner(0, 6, 2), None);O(1): a covered tile stores its offset back to the anchor (see Tile::span_offset), so
this is a lookup and a subtraction, not a scan.
Sourcepub fn clear_span(&mut self, layer: u8, x: u16, y: u16)
pub fn clear_span(&mut self, layer: u8, x: u16, y: u16)
Clears the whole multi-cell span that (x, y) on layer belongs to, anchor included,
resetting every one of its cells to the default (empty) tile.
Works from any cell of the span, so it pairs with span_owner: hit-test
a cell, then clear the artwork it belongs to. Does nothing if the cell is not part of a
span, is out of bounds, or the layer is unallocated.
Trait Implementations§
Source§impl Debug for Grid
Shows width, height, max_layer, and has_spans; the layer buffers themselves are
omitted (see Display for a rendering of layer 0).
impl Debug for Grid
Shows width, height, max_layer, and has_spans; the layer buffers themselves are
omitted (see Display for a rendering of layer 0).
Source§impl Display for Grid
Renders layer 0 only, one character per cell, with · in place of a plain space.
impl Display for Grid
Renders layer 0 only, one character per cell, with · in place of a plain space.
Source§impl Index<Pos<u16>> for Grid
impl Index<Pos<u16>> for Grid
Source§fn index(&self, pos: Pos) -> &Tile
fn index(&self, pos: Pos) -> &Tile
Reads the tile on layer 0 at pos.
§Panics
Panics if pos is outside the grid’s 0..width x 0..height bounds. This is the
unchecked, layer-0-only counterpart to tile, which instead returns None
on either an out-of-bounds pos or an unallocated layer; reach for tile when pos
isn’t already known to be in bounds.