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
How many of the 1024 bytes may differ and the block still be that map. |
Functions
|
Are these 1024 bytes a Gold Box map at all? |
Classes
What we currently believe, and how strongly. |
|
Narrow the candidates by what the party has been observed to do. |
|
Read the loaded map block out of RAM, at |
- class automap.area.Candidates[source]¶
Bases:
objectWhat we currently believe, and how strongly.
- __init__(names, source, certain=False)¶
- class automap.area.Fingerprint[source]¶
Bases:
objectNarrow 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])
- property candidates: automap.area.Candidates¶
- contradictions¶
observations that would have left no candidate at all. Counted rather than obeyed – see
_narrow.
- 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:
objectRead 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.
searchremains 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.- 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
expectthis 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-checkgoldbox.geoalready 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.
UNKNOWNis the ordinary state whenever no map is loaded – at the title screen, mid-load, and in combat, whereSQRPACIoccupies the same page. It is not evidence of anything and must never disable a control.
- automap.area.looks_like_a_map(geo)[source]¶
Are these 1024 bytes a Gold Box map at all?
The question
verdictneeds answered before it may say a block is somebody else’s map: the page at$0400is a map only while one is loaded, and in combat it holdsSQRPACIinstead – a tile remap, the combat parameter block and code, which scores 137/480 = 0.285 read as a map (docs/50-experiments.md, “$0400is 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: