automap.area

Which of the game’s GEO maps is the party standing on?

Answered twice over, and the module carries both answers.

A save says outright: $4BC2 is the GEO file number – see docs/50-experiments.md, “The area id: $4BC2, and it was in the header all along”, and goldbox.savegame.AREA.

A live game says it even more directly, because the map it is drawing is resident: ResidentGeo reads the 1024 bytes at $0400 and matches them against the disk copies, which is an exact identification and follows the game into a new area as soon as the load finishes.

Fingerprint narrows the candidates by what the party can and cannot do. It needs no addresses at all, so it stays wired up underneath ResidentGeo as the contradiction check: if a strategy names a map the party’s own movements contradict, the strategy is wrong.

Both take the candidate set as a dict and never enumerate one. Pool of Radiance has 29 maps numbered $00-$20, but Curse’s ids are sparse and chapter-grouped and Silver Blades, Champions and Death Knights have no GEO00 at all – their lowest id is $10 or $20 (write-up lost, work/reports/goldbox-inventory.md). Anything that counted maps, or walked a range, would be wrong for four of the six titles on the shelf.

goldbox.areas carries the names and the area-to-map relation; nothing here duplicates it.

Module Attributes

NEAR_ENOUGH

How many of the 1024 bytes may differ and the block still be that map.

Functions

looks_like_a_map(geo)

Are these 1024 bytes a Gold Box map at all?

Classes

Candidates

What we currently believe, and how strongly.

Fingerprint

Narrow the candidates by what the party has been observed to do.

ResidentGeo

Read the loaded map block out of RAM, at RESIDENT_GEO.

class automap.area.Candidates[source]

Bases: object

What we currently believe, and how strongly.

__init__(names, source, certain=False)
Parameters:
Return type:

None

property best: str | None
certain: bool = False
names: list[str]
source: str
class automap.area.Fingerprint[source]

Bases: object

Narrow the candidates by what the party has been observed to do.

Every square the party occupies must be walkable on the real map, and every step it completes must cross a passable edge. Feed observations in as they happen; the set only shrinks.

A blocked step – the party tried to move and stayed put – is the strongest single observation available, because it must correspond to an impassable edge, and impassable edges are rare.

__init__(maps)[source]
Parameters:

maps (dict[str, goldbox.geo.Geo])

blocked: list[tuple[int, int, int]]
property candidates: automap.area.Candidates
contradictions

observations that would have left no candidate at all. Counted rather than obeyed – see _narrow.

moved(x0, y0, x1, y1)[source]

Record a completed step between two adjacent squares.

Parameters:
Return type:

None

occupied: set[tuple[int, int]]
refused(x, y, direction)[source]

Record a step the game would not let the party take.

Parameters:
Return type:

None

saw(x, y)[source]
Parameters:
Return type:

None

steps: list[tuple[int, int, int]]
automap.area.NEAR_ENOUGH = 128

How many of the 1024 bytes may differ and the block still be that map.

Not zero, because the running game is allowed to write into the block it is drawing and an exact test would then read a legitimate session as somebody else’s game – which disables the controls in front of a player who has done nothing wrong. MEASURED on the player’s own disks: the two closest distinct maps anywhere in Pool of Radiance and Curse differ in 379 of 1024 bytes, and the median pair differs in about 790, so 128 leaves a factor of three before any tolerance could confuse one map with another.

class automap.area.ResidentGeo[source]

Bases: object

Read the loaded map block out of RAM, at RESIDENT_GEO.

The best strategy available live: no disks, no inference, and it tracks the game the instant it loads a new area. search remains for the case where the address is wrong for some title or version – sweep memory for a block that decodes as a plausible GEO, and check it against the map we already believe we are on.

__init__(target, address=RESIDENT_GEO)[source]
Parameters:

address (int | None)

identify(maps)[source]

The name of the map the game currently has loaded, or None.

An exact byte match against the disk copies, so a hit is certain – no fingerprint, no filename, and it changes the instant the game loads a new area.

Parameters:

maps (dict[str, goldbox.geo.Geo])

Return type:

str | None

read()[source]
Return type:

goldbox.geo.Geo | None

search(expect=None, ranges=SEARCH_RANGES)[source]

Sweep RAM for a resident copy of the map.

With expect this is an exact-match hunt and the answer is certain. Without it, look for any 1024 bytes whose barrier plane is highly reciprocal – that is the self-check goldbox.geo already relies on, and random memory does not pass it.

Parameters:

expect (goldbox.geo.Geo | None)

Return type:

int | None

verdict(maps)[source]

Is the machine running the title these maps came off? Three answers.

(OURS, name) – the block is one of these maps, near enough. (NOT_OURS, None) – it is somebody else’s map. (UNKNOWN, None) – the block is not a map at all, so it says nothing.

The three-way answer is the point. Asking “which title is this?” needs every title’s disks and fails open when it has not got them; asking “is this ours?” needs only the disks we already have and fails closed, and that is the difference #21 turns on.

UNKNOWN is the ordinary state whenever no map is loaded – at the title screen, mid-load, and in combat, where SQRPACI occupies the same page. It is not evidence of anything and must never disable a control.

Parameters:

maps (dict[str, goldbox.geo.Geo])

Return type:

tuple[str, str | None]

automap.area.looks_like_a_map(geo)[source]

Are these 1024 bytes a Gold Box map at all?

The question verdict needs answered before it may say a block is somebody else’s map: the page at $0400 is a map only while one is loaded, and in combat it holds SQRPACI instead – a tile remap, the combat parameter block and code, which scores 137/480 = 0.285 read as a map (docs/50-experiments.md, “$0400 is not the combat map”).

Reciprocity alone is not enough, and the reason is worth keeping: a page of zeroes reciprocates 1.000, because every square agrees with its neighbour that there is nothing there. So a map must also actually draw walls, and the two sides of a walled edge must mostly agree about which wall it is.

Parameters:

geo (goldbox.geo.Geo)

Return type:

bool