automap.combat¶
The fight: what the machine holds while a battle runs, and where to draw it.
No Qt in here, the same way live.py and render.py have none. The window
paints what battlefield yields and the tests assert on it against captured
bytes.
Everything is gated on `$6E11 == 2`. LINKER, the 136-byte resident at
$2B80, is the outer loop: read $6E11, load that overlay at $0800, call it,
repeat – 0 GEN, 1 DUNGEON, `2` COMBAT, 3 INIT, 4 COM.PREP,
5 POST.COM, 8 FINAL, 9 CAMP. The gate is not an optimisation: outside
combat $8B00 is a graphics buffer, and in a captured world snapshot it reads
00 00 FF FF ..., so an ungated reader draws combatants stacked at (0,0).
The shape is read, never assumed. SQRPACI<nn> supplies a parameter block
at $0600 and the two combat files seen do not agree: SQRPACI01 has a row
bounds 55 x 25, SQRPACI00 bounds 17 x 35 – and 18 x 36 squares is exactly
the 648 bytes that sit in front of the glyph table in a SQRDATA file.
COM.PREP $08C6 derives the camera clamps from the same bytes, which is what
proves the reading. The write-up, work/reports/combat-terrain.md, is lost.
Functions
|
Every primitive for one fight: ground, then combatants. |
|
How big a square can be and still fit the window. |
|
The part of the map worth drawing, as (x, y, width, height). |
|
Which of |
|
The fight in progress, or None when there is not one. |
|
The shape, or None if these bytes cannot be a parameter block. |
|
Which square a point in the canvas is over. |
Classes
One reading of a fight in progress. |
|
One entry of the position table, with its roster block and record. |
|
The map's geometry, as the |
- class automap.combat.Battle[source]¶
Bases:
objectOne reading of a fight in progress.
- __init__(shape, terrain, combatants, camera)¶
- Parameters:
shape (automap.combat.Shape)
terrain (bytes)
combatants (tuple[automap.combat.Combatant, ...])
- Return type:
None
- at(x, y)[source]¶
- Parameters:
- Return type:
automap.combat.Combatant | None
- combatants: tuple[automap.combat.Combatant, ...]¶
- property enemies: tuple[automap.combat.Combatant, ...]¶
- property party: tuple[automap.combat.Combatant, ...]¶
- shape: automap.combat.Shape¶
- class automap.combat.Combatant[source]¶
Bases:
objectOne entry of the position table, with its roster block and record.
recordis None when the slot it names holds nothing readable, which is an ordinary state mid-load rather than an error. Everything the tooltip shows is optional for the same reason: a field we have not read is left out.- __init__(index, x, y, slot, pose, on_map, initiative, hp=None, hp_max=None, armour_class=None, thac0=None, movement=None, record=None, helpless=frozenset({}))¶
- property dimmed: bool¶
Dead, or gone from the map. Drawn faint rather than removed, so you can see what happened.
- helpless: frozenset[int] = frozenset({})¶
Which of
HELPLESS_TRAITS(31, 52, 53) is on this combatant’s index right now, empty if none. Recomputed from the$4900arrays every poll and never carried forward, so it goes the moment the game clears the id.
- property kind: str¶
the party green, an enemy red, and a helpless enemy yellow.
The party keeps its green when it is helpless. The fill says which side a square is on before it says anything else, and a yellow party square would read as a third side; the tooltip carries the condition for both.
- Type:
How the square is filled
- lines()[source]¶
The tooltip, as text. Only what is decoded.
A field we cannot read is missing from the list rather than guessed at, and a trait code with no name shows its number so that an unnamed code is visibly unnamed.
- record: goldbox.record.CharacterRecord | None = None¶
- class automap.combat.Shape[source]¶
Bases:
objectThe map’s geometry, as the
$0600block gives it.- __init__(map_base, stride, width, height, positions, count)¶
- automap.combat.battlefield(battle, box=None, cell=None, margin=MARGIN, shading=SHADING)[source]¶
Every primitive for one fight: ground, then combatants.
Terrain is drawn as wall or not wall and nothing finer. The glyphs at
$91B0say what each code looks like on the C64’s own screen, but nothing here has been checked against them, and a map that invented a diagonal would be worse than one that draws a block.- Parameters:
battle (automap.combat.Battle)
cell (int | None)
margin (int)
shading (str)
- automap.combat.extent(battle, pad=PAD, least=LEAST)[source]¶
The part of the map worth drawing, as (x, y, width, height).
Both maps seen are 56 x 26 with the fight in a corner, so drawing all 1456 squares would spend the whole window on empty ground. The box covers every combatant and the 7 x 7 window the game itself is showing, padded, and is never smaller than
leastsquares a side.
- automap.combat.helpless_indices(save_head)[source]¶
Which of
HELPLESS_TRAITSis on which combatant right now, by index.Read out of the four
$4900arrays every poll and never remembered: the game clears the id when the condition ends (docs/133-active-effects.md), and a set carried forward would keep a monster gold after it woke up.The owner byte is the combat combatant index – 0-7 the party in save-slot order, 8 upward the monsters,
$FFthe whole party – which is why this can say which GOBLIN GUARD is helpless where the shared record cannot.$FFis dropped rather than trusted: no index reaches it, and a party-wide helplessness is not a thing any spell does.
- automap.combat.read_battle(target, previous=None)[source]¶
The fight in progress, or None when there is not one.
Two bursts, because the map’s address and length are in the first one: the mode byte, the parameter block and the camera, then the map, the roster, the positions, the initiative bytes and the head of the save image, which carries the effect arrays and the twelve record slots together. The cost of a read is the round trip and not the bytes – ~14.3 ms either way under VICE – so the number that matters is two.
previousis last poll’s battle, and supplies the last known square of a combatant that has left the map.- Parameters:
previous (automap.combat.Battle | None)
- Return type:
automap.combat.Battle | None
- automap.combat.shape_from_params(block)[source]¶
The shape, or None if these bytes cannot be a parameter block.
Validate before trust:
$0600is ordinary RAM and holds something else entirely between fights – in a captured world snapshot it reads00 08 00 00 00 8c ..., whose map base is zero.- Parameters:
block (bytes)
- Return type:
automap.combat.Shape | None