goldbox.commissions¶
The City Council’s books: the reward ledger, the offer board, the summons.
Everything here reads the 224 persistent flag bytes at $4A20-$4AFF, which
live at offset $0100 in SAVEDGAME0 (load address $4900) and are the same
bytes in a running machine. No transport and no Qt, so this works from a save
file, from a live read, or from a test’s own array.
Two structures in ECL08 (the Phlan City Hall, disk 3) carry the whole state:
- $4AA6 + i, i = 0..25 the reward ledger. The area script writes 254 when
the job is done; the clerk pays and writes 255. 0 is untouched, and four entries also keep a progress marker between 1 and 253 in the same byte.
- $4AC1 commissions completed – bumped by the clerk for the
ten jobs that count as major.
The index is the quest: each has the clerk’s own speech in the 26-entry
ONGOSUB [$6E79], 26 table at ECL08 $9D55, which is where the names below
come from. The clerk pays on exactly 254 ($9D1C COMPARE [$6E7A], 254), so any
other value sits there untouched by her.
Only four entries ever hold a value between 1 and 253; the other
twenty-two are 0, 254 or 255 and nothing else. MARKERS below carries those
four, each read off the script that writes it. A ledger byte the clerk will not
pay for is a real state, not a decoding error.
255 does not always mean the party was paid. Two entries are closed at 255
without a reward: index 3 when the party fled the Buccaneer’s Base and left the
boy behind (ECL08 $9EAD, beside “YOUR BUNGLING THE BIVANT RESCUE HAS COST
US”), and index 18 when the party broke the seal on Cadorna’s iron box, which
ECL08 $9AAF closes on entry to the City Hall while Cadorna confronts them.
Index 22 is dead. No instruction in any of the thirty scripts reads or
writes $4ABC, and its handler is a bare RETURN — but its row in the clerk’s
four payout tables at ECL08 $B5E0/$B5F7/$B60E/$B625 is not empty, so it
was a commission once. It can never be anything but 0.
offered() re-implements the board at ECL08 $A84D: sixteen candidates tested
in a fixed order, each a COMPARE/IF/GOTO $A890 gate, at most three offered
per visit. The cap counter is $4A05, in the per-script scratch page, so it
resets when the party leaves — and it is never reset inside the script, which is
why a second approach to the clerk on the same visit says only “BACK SO SOON? I
CAN ONLY REPEAT THE EARLIER OFFERS” and lists nothing.
Three things the board does that a pure predicate cannot show:
The graveyard commission is offered before the sixteen (
ECL08 $A584) and does not count against the cap.graveyard_offer()has it.Being offered a job writes flags. Candidates 3, 4, 5, 12, 13, 14 and 15 set
$4AB0,$4A97,$4A9B,$4A8C,$4A98,$4A99/$4A6Band$4A9Aas the clerk speaks.offered()predicts, it does not simulate.Running the board dry advances the plot. If the loop reaches candidate 16 without filling the three slots,
$A842/$AF2Cwrites 254 into$4ABE— ledger index 24, “Cadorna exposed as a traitor” — so the next visit gets that speech.
The write-ups, work/reports/quest-flags.md and
work/reports/commissions.md, are lost; the plan and its evidence table are
docs/103-quest-log-panel.md.
Nothing outside those two structures and the named appointment flags is exposed,
because nothing else in the region is confirmed to the same standard.
Module Attributes
How the 25 splits, for anything that has to explain the number. |
|
what a panel says about a side quest is #158 step 4 and is Donald's to word. |
Functions
|
|
|
The flag block, out of whichever container the caller happens to hold. |
|
|
|
|
|
The clerk's own words, or the index where the clerk has no speech. |
|
What a ledger byte between 1 and 253 means, or None if it means nothing. |
|
What the clerk would offer on the next visit, in the board's order. |
|
Everything, from the flag block. |
|
|
|
What a save says about each side quest, as far as it can say anything. |
|
The panel as text, for a terminal. |
Classes
A place the Council has told the party to go, or a job it has handed out. |
|
What the Council has asked for, and what it has already paid for. |
|
One row of the reward ledger. |
|
The 224 bytes, addressed the way the game addresses them. |
|
A candidate on the board: what the clerk says, and what it settles. |
|
One byte at one value, and the instruction that puts it there. |
|
|
|
A quest on its own flag bytes, outside the council's ledger. |
|
What a save says about one side quest. |
- class goldbox.commissions.Appointment[source]¶
Bases:
objectA place the Council has told the party to go, or a job it has handed out.
- __init__(address, name, kind, value, state, outstanding)¶
- class goldbox.commissions.Commissions[source]¶
Bases:
objectWhat the Council has asked for, and what it has already paid for.
- __init__(completed, ledger, offers, appointments)¶
- Parameters:
completed (int)
ledger (tuple[goldbox.commissions.Entry, ...])
offers (tuple[goldbox.commissions.Offer, ...])
appointments (tuple[goldbox.commissions.Appointment, ...])
- Return type:
None
- appointments: tuple[goldbox.commissions.Appointment, ...]¶
- property in_progress: tuple[goldbox.commissions.Entry, ...]¶
an area script’s own progress marker.
- Type:
A non-zero byte below 254
- ledger: tuple[goldbox.commissions.Entry, ...]¶
- offers: tuple[goldbox.commissions.Offer, ...]¶
- property outstanding: tuple[goldbox.commissions.Appointment, ...]¶
- property paid: tuple[goldbox.commissions.Entry, ...]¶
- property reward_waiting: tuple[goldbox.commissions.Entry, ...]¶
Done, and the money is still sitting at the City Hall.
- class goldbox.commissions.Entry[source]¶
Bases:
objectOne row of the reward ledger.
- __init__(index, address, name, source, value, major)¶
- class goldbox.commissions.Flags[source]¶
Bases:
objectThe 224 bytes, addressed the way the game addresses them.
- class goldbox.commissions.Offer[source]¶
Bases:
objectA candidate on the board: what the clerk says, and what it settles.
- __init__(order, text, ledger)¶
- goldbox.commissions.QUEST_UNSEEN = 'not seen'¶
what a panel says about a side quest is #158 step 4 and is Donald’s to word.
- Type:
A quest’s state, as an identifier. Not interface text
- class goldbox.commissions.QuestFlag[source]¶
Bases:
objectOne byte at one value, and the instruction that puts it there.
- __init__(address, value, meaning, durable, where)¶
- goldbox.commissions.SLUM_WANDERING = 15¶
How the 25 splits, for anything that has to explain the number.
- class goldbox.commissions.Scratch[source]¶
Bases:
object$4A00-$4A1F, the page an area change zeroes.Kept apart from
Flagson purpose: a byte in here means something only while the script that wrote it is still resident, so a caller has to ask for it by name rather than get it mixed into the persistent block.
- class goldbox.commissions.SideQuest[source]¶
Bases:
objectA quest on its own flag bytes, outside the council’s ledger.
- __init__(key, name, script, area, accept, finish, progress=())¶
- Parameters:
key (str)
name (str)
script (str)
area (int)
accept (goldbox.commissions.QuestFlag)
finish (goldbox.commissions.QuestFlag)
progress (tuple[goldbox.commissions.QuestFlag, ...])
- Return type:
None
- accept: goldbox.commissions.QuestFlag¶
- finish: goldbox.commissions.QuestFlag¶
- property flags: tuple[tuple[int, int, str], ...]¶
Every (address, value, meaning) this quest is made of.
- progress: tuple[goldbox.commissions.QuestFlag, ...] = ()¶
- class goldbox.commissions.SideQuestState[source]¶
Bases:
objectWhat a save says about one side quest.
- __init__(quest, state, accept_value, finish_value, progress_in_hand=False)¶
- Parameters:
quest (goldbox.commissions.SideQuest)
state (str)
accept_value (int | None)
finish_value (int)
progress_in_hand (bool)
- Return type:
None
- property ambiguous: bool¶
QUEST_UNSEENhere could equally mean “accepted, then left”.True whenever the accept flag is in the scratch page and the durable half says nothing. It is not a claim that the party accepted anything – it is the statement that this save cannot tell the two apart. Donald’s decision of 2026-09-04 settled what the panel does about that: it shows the errand once the potion is in hand and never from this scratch byte – see
durable_state. All 16 saves on the machine that never met Ohlo read exactly like a party that accepted the errand and walked out of the Slums.
- property durable_state: str¶
The same reading with
durable=Falseflags ignored.Donald’s decision, 2026-09-04: the Quest Log shows the errand once the game’s own durable bytes say so, and never shows
QUEST_ACCEPTEDfrom$4A04, which is scratch an area change wipes. So this never readsaccept_valueunlessquest.acceptis itself durable – for Ohlo it never is, and a page with$4A04= 250 alone readsQUEST_UNSEENhere whilestatereadsQUEST_ACCEPTED.
- goldbox.commissions.flags(source)[source]¶
The flag block, out of whichever container the caller happens to hold.
Accepts a
Flags, aSaveGame0(or anything withto_bytes), the wholeSAVEDGAME0payload, the$4A00page a memory dump tends to come in, or the 224 bytes themselves. Lengths are distinct, so no flag is needed.- Return type:
- goldbox.commissions.graveyard_offer(source, party_strength=None)[source]¶
ECL08 $A584: the graveyard commission, offered before the sixteen.Four gates, in the script’s order:
$4AC1 >= 4, the graveyard reward not already paid, party strength>= 19, and the commission not already accepted ($4A96 != 255).party_strengthof None means “not known”, and the strength gate is taken as passed.- Parameters:
party_strength (int | None)
- Return type:
goldbox.commissions.Offer | None
- goldbox.commissions.ledger_name(index)[source]¶
The clerk’s own words, or the index where the clerk has no speech.
- goldbox.commissions.marker_text(index, value)[source]¶
What a ledger byte between 1 and 253 means, or None if it means nothing.
Noneis the honest answer for a value no script writes: the four entries below are the only ones with markers, so anything else in 1..253 is a byte the game did not put there.
- goldbox.commissions.offered(source, limit=OFFER_LIMIT, party_strength=None)[source]¶
What the clerk would offer on the next visit, in the board’s order.
The graveyard commission comes first and does not count against
limit, which is howECL08runs it:$A584is reached before$A831zeroes the board index, and$4A05is only bumped inside the board’s own loop.- Parameters:
- Return type:
- goldbox.commissions.read(source, party_strength=None)[source]¶
Everything, from the flag block.
- Parameters:
party_strength (int | None)
- Return type:
- goldbox.commissions.scratch(source)[source]¶
$4A00-$4A1Fout of whichever container the caller holds.Nonewhere the source cannot carry it – the 224 persistent bytes, or aFlags. Everything elseflags()accepts does carry it: a$4A00page andSAVEDGAME0both start at or below$4A00.- Return type:
goldbox.commissions.Scratch | None
- goldbox.commissions.side_quests(source)[source]¶
What a save says about each side quest, as far as it can say anything.
The durable flag is read from
flags(); the accept flag is read fromscratch()when the source carries it. A quest whose accept flag is in the scratch page and whose durable flag is still 0 reads asQUEST_UNSEENfrom a save made outside its area whether or not the party accepted it.SideQuestState.durable_stateis the reading Donald’s decision of 2026-09-04 asked for: it never looks at the scratch page, so this ambiguity never reaches it.- Return type: