goldbox.world

The overland travel map — SQRDATA0n, read and stitched into one world.

Not `goldbox/world_state.py`, which is where a party is standing and when. This module is the map itself.

The overland map is not a `GEO`. It is the combat square engine – SQRPACI descriptor, one byte a square – pointed at SQRDATA0n instead of a combat arena. automap/combat.py reads exactly this shape for a fight; this module reads the same shape for the three files that make up Pool of Radiance’s wilderness.

A SQRDATA file is 648 bytes of grid, then 120 tile entries of 18 bytes each: an 18 x 36 grid, one byte a square, indexed y * 18 + x, followed by 120 glyphs of nine screen codes then nine colour attributes – a 3 x 3 block of characters out of SECSET0n. SQRDATA05 is 648 + 120 x 18 = 2808 bytes exactly; SQRDATA04 and SQRDATA06 carry eight spare bytes after that. All of this is CONFIRMED (docs/113-world-map.md, docs/137-wilderness-automap.md) and pinned against the player’s own disks by tests/test_p3.py.

The three files are overlapping windows on one world, thirteen columns apart, west to east: SQRDATA04 (west), SQRDATA05 (middle), SQRDATA06 (east) – goldbox/areas.py’s own order for areas 25, 26 and 27. The game’s own world coordinate is the window-local x plus 13 * window_indexdocs/137-wilderness-automap.md §1: “the party marked at ($49C3 + 13 x k, $49C4)” – so this module keeps that coordinate system rather than renormalising it to start at zero. The two windows stitch at world x = 15 and x = 28, and the walkable part of the world is x 2-41, y 2-33: 40 x 32 squares. CONFIRMED: the raw grid overlaps in a five-column band at each seam (18-13 = 5 columns, all 36 rows), and 179 of those 180 squares agree between the west and middle windows, 180 of 180 between the middle and east (docs/113-world-map.md); tests/test_world.py recomputes both counts against the disks.

A terrain code means only what its own window’s tables say. 2E is walkable mountain on map 19 and solid on map 1B (docs/113-world-map.md, docs/137-wilderness-automap.md, both “Do not read a terrain code against another window’s table”). So Window.square never crosses into another window’s data, and there is deliberately no global tile-name table here – only the raw index, which the window it came from is the sole authority on.

## What this module does not do, and why

passable(window, x, y) and site_at(world_x, y) are named by #11 (Draw the wilderness on the automapper) and are not implemented. Both need a table that is not in SQRDATA0n at all: “each script carries its site list as four tables (y, count, x, event) and its impassable-terrain list as one more” (docs/113-world-map.md) – inside ECL19/ECL1A/ECL1B’s own bytecode, not in the file this module reads. The byte offsets of those tables were recorded once, in work/reports/world-map.md, which is lost with work/ (#136 (Thirty-two cited write-ups are gone, because the knowledge base pointed into gitignored scratch)) – confirmed by tools/windowsquare.py’s own docstring: “Passability cannot be read off the disk: the impassable-terrain table’s address was in work/reports/world-map.md, which is lost (#136), so the running game is the only authority left.” Recovering the offsets means rebuilding an ECL decoder, and docs/115-review-the-scripts.md records that reading the ECL scripts at all was closed at Donald’s own direction on 2026-08-31 – “I don’t need to see the ECL scripts. If I decide I want to see them, we can approach the issue again at that time.” Reopening that is his call, not a default this module can reach for. Both functions raise NotImplementedError naming this rather than guessing at an address or shipping a table that was never read off anything.

Module Attributes

STRIDE

The grid is 18 columns wide and 36 rows tall, one byte a square, indexed y * STRIDE + x.

TILE_COUNT

120 glyph entries, nine screen codes then nine colour attributes -- a 3 x 3 block of characters.

MIN_FILE_SIZE

SQRDATA05 is exactly this size; SQRDATA04 and SQRDATA06 carry eight spare bytes after it (tests/test_p3.py).

PLAYABLE_X

The walkable part of a window, window-local -- the two-square border is never shown to the player and the game never walks a party into it (docs/113-world-map.md: "Walkable is x 2..15, y 2..33 of each window").

WINDOW_STEP

Windows are WINDOW_STEP world columns apart, west to east -- the game's own arithmetic, $49C3 + 13 * k (docs/137-wilderness-automap.md).

WINDOW_NAMES

west, middle, east -- goldbox/areas.py's own order for areas 25, 26 and 27.

WORLD_X_MIN

Where the walkable world starts and how wide/tall it is, in the game's own coordinate system (window-local x plus 13 * window_index, never renormalised to 0).

SEAM_WEST_MIDDLE

The two seams, in world x -- where window k hands off to window k + 1.

Functions

passable(window, x, y)

Whether the party may walk onto window-local (x, y).

site_at(world_x, y)

The site at world (world_x, y), or None.

Classes

Tile

One of a window's 120 glyphs.

Window

One SQRDATA file: an 18 x 36 grid of tile indices, plus its 120 tiles.

World

The three SQRDATA windows, addressed by the game's own world coordinate: window-local x plus 13 * window_index, never renormalised.

Exceptions

WorldError

A SQRDATA payload too short to hold a grid and its glyph table.

goldbox.world.MIN_FILE_SIZE = 2808

SQRDATA05 is exactly this size; SQRDATA04 and SQRDATA06 carry eight spare bytes after it (tests/test_p3.py).

goldbox.world.PLAYABLE_X = range(2, 16)

The walkable part of a window, window-local – the two-square border is never shown to the player and the game never walks a party into it (docs/113-world-map.md: “Walkable is x 2..15, y 2..33 of each window”).

goldbox.world.SEAM_WEST_MIDDLE = 15

The two seams, in world x – where window k hands off to window k + 1. A world x at or past a seam belongs to the eastern window of the pair; the two windows’ raw data agree there (179/180 and 180/180, see the module docstring), so which side answers a seam square is a bookkeeping choice, not a correctness one.

goldbox.world.STRIDE = 18

The grid is 18 columns wide and 36 rows tall, one byte a square, indexed y * STRIDE + x. This is $0612 + 1, read off GDRIVE00 $C3AFnot $0607 = 20, which is automap/combat.py’s own corrected note (docs/113-world-map.md, “Correction to docs/101-combat-view.md”).

goldbox.world.TILE_COUNT = 120

120 glyph entries, nine screen codes then nine colour attributes – a 3 x 3 block of characters. PROBABLE (docs/113-world-map.md): the split between the two halves is measured from the file’s own arithmetic, but which set the codes are drawn from is not independently confirmed.

class goldbox.world.Tile[source]

Bases: object

One of a window’s 120 glyphs.

screen_codes and attributes are nine bytes each, PROBABLE as “a 3 x 3 block of characters out of SECSET0n” (docs/113-world-map.md) – this module hands the two halves back as measured and decodes no further, since drawing the game’s own art is not what any of this is for (docs/137-wilderness-automap.md §5: “what a SECSET0n glyph looks like – not needed”).

__init__(screen_codes, attributes)
Parameters:
Return type:

None

attributes: bytes
screen_codes: bytes
goldbox.world.WINDOW_NAMES = ('SQRDATA04', 'SQRDATA05', 'SQRDATA06')

west, middle, east – goldbox/areas.py’s own order for areas 25, 26 and 27.

Type:

The three files, in world order

goldbox.world.WINDOW_STEP = 13

Windows are WINDOW_STEP world columns apart, west to east – the game’s own arithmetic, $49C3 + 13 * k (docs/137-wilderness-automap.md).

goldbox.world.WORLD_X_MIN = 2

Where the walkable world starts and how wide/tall it is, in the game’s own coordinate system (window-local x plus 13 * window_index, never renormalised to 0). docs/113-world-map.md: “the world’s playable area is 40 x 32”; docs/137-wilderness-automap.md: “stitched at world x 15 and 28”.

class goldbox.world.Window[source]

Bases: object

One SQRDATA file: an 18 x 36 grid of tile indices, plus its 120 tiles.

A terrain code means only what this window’s own tables say – see the module docstring. There is no method here that accepts a foreign code or reaches into another Window.

__init__(payload, name=None)[source]
Parameters:
classmethod from_disk(disk, name)[source]
Parameters:
Return type:

goldbox.world.Window

is_playable(x, y)[source]

Whether (x, y) is in the two-square-deep border the game never shows or walks a party into.

Parameters:
Return type:

bool

square(x, y)[source]

The raw tile index at window-local (x, y), 0-119.

Parameters:
Return type:

int

tile(index)[source]

One of the 120 glyph entries this window’s grid indexes into.

Parameters:

index (int)

Return type:

goldbox.world.Tile

tile_at(x, y)[source]

The glyph drawn at window-local (x, y)tile(square(x, y)).

Parameters:
Return type:

goldbox.world.Tile

to_bytes()[source]
Return type:

bytes

class goldbox.world.World[source]

Bases: object

The three SQRDATA windows, addressed by the game’s own world coordinate: window-local x plus 13 * window_index, never renormalised.

windows is (west, middle, east)SQRDATA04, 05, 06 – matching WINDOW_NAMES and goldbox/areas.py’s order for areas 25, 26 and 27.

__init__(windows)
Parameters:

windows (tuple[goldbox.world.Window, goldbox.world.Window, goldbox.world.Window])

Return type:

None

classmethod from_disks(disks)[source]

The three windows, found across a set of disks.

SQRDATA04, 05 and 06 are never on the same disk – they ride POOL6, 7 and 8 respectively (areas 25-27, goldbox/areas.py) – so, unlike Geo.from_disk, this cannot open one image and read three files out of it. disks is any iterable of D64 objects or paths; for each window the first disk that carries its file wins, the way tests/gamedata.py’s game_file does.

Raises WorldError naming whichever window no disk in disks carried.

Return type:

goldbox.world.World

locate(world_x)[source]

Which window owns world_x, and that window’s own local x there.

Raises IndexError outside the walkable world (WORLD_X_MIN .. WORLD_X_MAX). At a seam, the eastern window answers – see SEAM_WEST_MIDDLE / SEAM_MIDDLE_EAST.

Parameters:

world_x (int)

Return type:

tuple[goldbox.world.Window, int]

square(world_x, y)[source]

The raw tile index at world (world_x, y).

y is not stitched – every window shares the same y range, and only x moves between them.

The int this returns does not carry the window it came from, and a terrain code only means anything against its own window’s table: 2E is walkable mountain on map 19 and solid on 1B. Nothing here can be asked what a bare code means – there is no global code-to-name table to misuse – but a caller that stores this number and decodes it later, once passable exists, is the way that protection is lost. Use locate and keep the Window, or call Window.square directly.

Parameters:
Return type:

int

tile_at(world_x, y)[source]

The glyph drawn at world (world_x, y).

Parameters:
Return type:

goldbox.world.Tile

windows: tuple[goldbox.world.Window, goldbox.world.Window, goldbox.world.Window]
exception goldbox.world.WorldError[source]

Bases: ValueError

A SQRDATA payload too short to hold a grid and its glyph table.

goldbox.world.passable(window, x, y)[source]

Whether the party may walk onto window-local (x, y). Not implemented – see the module docstring.

Parameters:
Return type:

bool

goldbox.world.site_at(world_x, y)[source]

The site at world (world_x, y), or None. Not implemented – see the module docstring.

Parameters:
Return type:

str | None