A player record is one object. A map asks the same memory question thousands of times: which tile is this, and what does this player know about it? In a Wesnoth match, terrain and visibility may describe the same location but live in different layers of state. Coordinates give us a way to relate them.
Section: A map is usually a grid.
A rectangular map can be flattened into a one-dimensional array:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Make the conversion checked so an invalid tile cannot silently wrap:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
On a three-column, two-row map, tile (2, 1) becomes index 1 times 3 + 2 equals 5. Tile (3, 1) is outside the map and returns None. The checked multiplication also refuses dimensions so large that the index would overflow usize.
In brief: A flat list can hold a grid: a tile’s index is its row times the width plus its column. Check the bounds before you use it.
Never trust coordinates from remote memory until they pass bounds checks.
Section: A grid needs a coordinate contract.
The formula only works after you know what x, y, width, and the base address mean. Games may store rows from top to bottom or bottom to top, pad each row, split terrain and visibility into separate layers, or keep one visibility layer per team.
For a candidate grid, recover these facts separately:
origin: which tile is (0, 0);
axis direction: which way increasing x and y move;
stride: how many elements or bytes separate two rows;
element width: how many bytes represent one tile;
layer: terrain, occupancy, visibility, or another property;
owner: global map state or one player’s derived view.
If the row stride includes padding, y * width + x is wrong even when the first row looks perfect. The byte address becomes base + y * row_stride + x * element_size. Verify it with tiles on different rows and near both edges of the map.
Predict first
A map is 3 tiles wide and 2 tall, one byte per tile. The game pads every row to 4 bytes, so each row ends with one unused byte.
Counting from byte 0, which byte holds tile (2, 1)?
Answer: Byte 6. Skip one whole row of 4 bytes (1 times 4 equals 4), then move 2 tiles along it: 4 + 2 equals 6. Bytes 3 and 7 are the padding.

Visibility is often a derived view of terrain, units, teams, and line-of-sight checks for range and blocking terrain. A cached visibility layer can be useful to read without being the source of truth, because the game rebuilds it from those inputs. What matters is which system recomputes it and when.
Alternative: Think of fog as a sheet of tracing paper over the map: the terrain underneath is shared, but each player has their own sheet showing what they have seen.
Section: Find one known tile.
In an offline Wesnoth match, compare a currently visible tile with a tile hidden by fog. Search for the observed visibility value, reveal or hide the tile through normal gameplay, and filter again.

Repeat with several tiles. You are testing whether candidates belong to:
one tile;
one row;
one player’s visibility layer;
a cached drawing value;
the authoritative map state.
Section: Break on a normal update.
Set a write breakpoint on a confirmed tile and move a unit so visibility updates.

Look for:
a base pointer to map data;
row or column math;
a player or team index;
the value written for hidden, explored, and visible states.
Do not assume a value such as this value means “visible.” It may mean -1, a bit mask, or an uninitialized sentinel. Collect examples for every state you name.
Section: Model visibility as states.
Suppose repeated observations show 0 for hidden tiles, 1 for explored tiles, and 2 for visible tiles in this exact build. Decode those raw values into named states so later code cannot confuse visibility with terrain. Keep every other number as Unknown until it has been investigated.
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Keep Unknown. Silently treating a new value as visible would hide the fact that the byte has a state the model does not know about.
Narration: A value you did not expect is information: it may be a state you have not met yet. Keeping Unknown makes the surprise visible instead of hiding it.
Section: Read a bounded layer.
Give the reader a verified layer base and map dimensions. It first checks width times height, caps the tile count, copies four bytes per tile from the game process, then decodes each copied u32 with the mapping above. The result is an owned vector of visibility states, not a pointer into live game memory.
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
The unwrap is safe here because chunks_exact(4) guarantees every chunk has four bytes, but an explicit conversion helper can make that invariant clearer in shared code.
Section: Prefer an external visualization.
Instead of patching the game, print a small text map or draw a separate window for your local lab:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
That keeps the experiment observable and easy to shut down.

Section: Reproduce the original Wesnoth map patch.
The original course called this exercise the map hack.
For Wesnoth 1.14.9, the write-breakpoint trail leads to this value. The original eight-byte sequence updates a visibility column.
This build does not store one u32 per tile the way the model earlier in this lesson does. It packs 32 tiles into a single 32-bit column, one bit per tile, so the instructions below are bit-level field assignment rather than the y * width + x array indexing shown above. mov e a x, e b p then shl e a x, c l builds a single set bit, shifted into the position c l names — the bit that belongs to one specific tile in that column. not e a x flips every bit, which turns that into a mask that is 0 at the tile’s position and 1 everywhere else. and dword ptr [e s i], e a x applies the mask to the stored column, clearing only that one tile’s visibility bit while leaving the other 31 alone. Replacing that full span with the following bytes forces the column bits to 1 using or [e s i], hexadecimal F F:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
The observation, grid model, and debugger experiment above are the core exercise here. For an automated repeat after Lesson 8.3 teaches patch ownership and D L L, dynamic link library, lifecycle, the verified patch object expresses the same byte check and restoration in code:
Code example. The surrounding prose explains this example; the full lesson contains the exact code.
Move a unit in a local match. As each visibility column is updated, every bit becomes this value, revealing the map as shown above. Call patch.restore() and start a fresh map to confirm fog behaves normally again.
The expected bytes are no longer an unnamed profile placeholder: they encode the four instructions shown in the debugger. The injected implementation is enable_wesnoth_map_reveal. After injection, F3 toggles it and End restores it.
Section: What the grid model teaches.
Fog of war is a per-player view over shared map data. The experiment shows how a grid and per-player visibility state can be represented; a different build may pack the same relationship into different bytes.