Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion _bmad-output/implementation-artifacts/sprint-status.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ development_status:
1-4-the-driver-loop-and-the-first-input-wait: done
1-5-input-wait-detection: done
1-6-the-observation-part-one-the-schema-core-and-the-codec: done
1-7-the-observation-part-two-the-remaining-sections-and-the-read: backlog
1-7-the-observation-part-two-the-remaining-sections-and-the-read: review
1-8-the-observer-part-one-map-fog-traps-and-heaps: backlog
1-9-the-observer-part-two-actors-emotes-buffs-and-the-hero: backlog
1-10-the-observer-part-three-inventory-journal-log-and-prompts: backlog
Expand Down
94 changes: 94 additions & 0 deletions docs/adr/0005-observation-schema-and-hashing.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,97 @@ shown at all.
constant, so any change to the encoding is a change to the version, recorded here, before the pin
moves. `HeaderSection.version` is the schema version, which the codec refuses to encode unless it
is its own.

## Amendment: story 1.7 (2026-09-05)

The hero, the inventory, the journal, the log, the valid Actions and the Prompt are records,
`Observation` is the nine sections in the table's order, the readable form is `ObservationJson`
over a `JsonWriter` the Run log shares, and the Belief is `Belief`. The schema version is 2.
These are the choices the decision left open or did not foresee.

**The hero section carries what five views draw.** The cell; the name the hero window titles
(`…/windows/WndHero.java:162`; `…/actors/hero/Hero.java:417`); the subclass, which names the hero
once chosen (`Hero.java:412-414`); the armour ability, drawn as the fourth talent tier and the
action indicator (`Hero.java:209`, `:390`, `:402`); the level (`WndHero.java:160`); the experience
and what the next level needs, the exp bar's fill and its text on either pane
(`…/ui/StatusPane.java:334-345`; `WndHero.java:195`); the health, its maximum and the shielding exactly as the status pane
prints them (`StatusPane.java:322-327`; `WndHero.java:193-194`), so the hero's health is exact
where an actor's is quantised, because the pane prints the number; the strength and the bonus or
penalty printed after it (`WndHero.java:190-192`); the gold and the alchemical energy the bag
window prints (`…/windows/WndBag.java:186`, `:179`, `:219`), which the table did not list;
hunger as the icon's three states (`…/actors/buffs/Hunger.java:179-187`); every buff with an icon,
ordered by name, then timed, then turns, as an actor's are (`WndHero.java:301-314`;
`…/ui/BuffIndicator.java:192-196`); every talent of every tier the pane shows with its points, and
the unspent points per tier drawn as open stars (`…/ui/TalentsPane.java:75-84`, `:183`, `:259`;
`Hero.java:210`, `:387-396`), where the pane shows a tier from one level below its threshold, the
third only with a subclass and the fourth only with an ability, while the hero holds the first two
tiers from creation (`…/actors/hero/Talent.java:968-970`), so the section carries what is drawn;
and the six quickslots with the placeholder flag
(`…/QuickSlot.java:36-41`; `…/ui/QuickSlotButton.java:306`). The danger count of ADR-0006 is not a
field: it is the enemies among the actors, which that section lists with the invisible flag.

**The inventory is positional, in the belongings' order.** The equipped items in slot order, then
the backpack (`…/actors/hero/Belongings.java:428-429`, `:446-453`), which the record enforces,
because an `ItemRef` (ADR-0014, option 11) is a position in it. An item carries the family its
sprite and bag show (`ItemKind`, one member per item package of the tag), the display name, the
quantity, the level and curse flags with their visible values, the status text, the slot, the
actions the item window offers and the default action (`…/items/Item.java:110-115`, `:179-181`,
`:433-451`, `:483-499`, `:538`, `:570-572`; `…/windows/WndUseItem.java:54-76`). The sprite index
the ADR-0006 row whitelists is not carried: the name says what the sprite draws.

**The journal is notes and known appearances.** The notes tab's three records, landmark, keys and
a written note, by depth then kind, title, text and count (`…/journal/Notes.java:73-100`,
`:115-143`, `:145-151`, `:296-306`, `:375-420`); and the potions, scrolls and rings identified this
Run by their true names (`…/items/potions/Potion.java:402-404`). The guide's pages and the
bestiary are not here: static text the Codex carries, and cross-Run state.

**The log is the messages as emitted, capped at sixty-four.** The tone comes from the prefix
(`…/utils/GLog.java:32-39`; `…/ui/GameLog.java:72-87`) and the text is what follows it; the
new-line marker is dropped, and the merging of same-colour messages is the pane's. The source is
the raw signal every message goes through (`GLog.java:39`), which is the game log non-negotiable 1
names beside the renderer, and the reproducible choice, since frame timing is not part of the Run
tuple. The pane is a view of it: it takes a frame's messages in one batch, merges, and trims the
oldest entries beyond three or five lines of text before the frame is drawn (`GameLog.java:55-131`,
`:59`, `:89`, `:107-122`), so a burst that exceeds the lines in one frame loses its oldest
messages before they are ever drawn, and the Observation may carry a message the pane never
showed. That is the trade-off recorded here: the signal over the pane, for reproducibility, at
the cost of a line a human may not have read in a burst. The cap is a bound on the Observation's
size, not a claim about the pane. For story 1.10: the pane replaces the signal's listener and
buffers statically (`GameLog.java:47`, `:52`), so the Observer's capture must equal what the pane
receives, no more.

**The valid Actions are a section, and the Observation is built in two steps.** `Action` is the
sealed interface of ADR-0014 with one record per kind, amended there: item use is three kinds by
the shape of its target, a window of options an item opens being a Prompt at its own Input wait,
and `MoveTo` records a human's click. `ActionsSection` sorts by kind and then by the action's own
bytes, refuses repeats and refuses `MoveTo`, so a set enumerated in any order is one section with
one hash and a human's click is never a valid Action. The table's circularity, a valid set computed from the Observation and
part of it, is resolved by `ActionsSection.NONE` and `Observation.withActions`: the Observer builds
the record without Actions, story 1.12's `validActions` reads that, and the record with the set is
what is hashed. The Observation refuses an Action naming what it does not carry: a cell off the
map, an item reference whose index, name or quantity the inventory does not list, an item action
the item does not offer, an answer past the prompt's options, a talent the hero section does not
list, an ability the hero does not have. That is ADR-0014's rule that every parameter is a value
the Observation carries, as a constructor check rather than an executor's.

**The prompt section is the kind, the title, the text and the button labels in drawing order**
(`…/windows/WndOptions.java:57`, `:92`). With no Prompt open it is empty, and the header's kind
must equal the section's, which the Observation holds. The richer windows, a trade or a subclass
choice, are flattened into labels by story 1.10.

**The readable form is canonical JSON.** `JsonWriter` sorts an object's keys by their UTF-16 code
units whatever order they were given in, writes no whitespace, writes integers only, and escapes
only what JSON requires; `ObservationJson` renders the hash, the section hashes and the nine
sections, each record an object keyed by its component names, each enum its name, each Action
with a `kind` key. It is derived from the records and never hashed, and nothing in `api` reads it
back: `JsonRenderingTest` scans the module for a method that turns text or bytes into a record of
the schema and finds none, and reads the rendering with a strict reader that accepts only the
canonical shape. ADR-0011's Run log uses the same writer.

**The Belief is a class, not a record.** A record over a byte array would compare by identity, so
`Belief(version, bytes)` copies its bytes in and out, is equal by content, and hashes SHA-256 over
the version and the bytes; the harness logs a Belief it cannot read (AD-14). No record of the
schema has a component of that type, which the reflection test holds.

**The version is 2 and pinned.** Every section's bytes are new relative to version 1, and the pin
moved with them. Story 1.6's rule stands: from here, a pin move is a version bump.
23 changes: 23 additions & 0 deletions docs/adr/0014-action-schema-and-executor-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,26 @@ Rules:
granularity.
- `ItemRef` desyncs during a Replay after an unsupported input. Mitigation: verifiability already
ends there.

## Amendment: story 1.7 (2026-09-05)

The `Action` records exist from story 1.7, since the `actions` section of the Observation
(ADR-0005) is a list of them; story 1.12 adds `validActions`. They differ from the table above in
five places. Item use is three kinds by the shape of its target, `UseItem(item, action)`,
`UseItemAt(item, action, cell)` and `UseItemOn(item, action, target)`, so that every component is
a value and every switch over actions is exhaustive without an optional field. The option index
the table allowed as a target is not a kind: a window of options an item opens, an enchantment to
choose from the scroll's three, lists labels that are not known at the Input wait the item is
used from, so an index chosen there would be blind; and a recognised window in front is an Input
wait of its own (ADR-0015 as story 1.5 amended it), so that window is a Prompt and its answer an
`AnswerPrompt` at the next wait, with the kind story 1.10 adds. Option 8's same-wait answer
stays for the cell selector and the bag, which show the Observation's own cells and items.
`Talent(talent)` names the talent as the hero section lists it, and the ability is
`Ability(ability)` or `AbilityAt(ability, cell)`. `MoveTo(cell)` is a record of the schema, so
that a human's click on a distant cell in the Overlay can be logged as ADR-0011 needs; it is never
in a valid set, and `ActionsSection` refuses it. `ItemRef(index, name, quantity)` is the position in
the inventory section plus the display name and the quantity, and the Observation refuses an
Action whose reference does not match its inventory, which puts option 11's desync check at
construction as well as at execution. `Rest(full)` carries its flag as a boolean. Each record
names its kind, which the codec writes first and the JSON writes as the `kind` key; the canonical
form of an Action in the Run log is that JSON.
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ graph TD

| Module | May depend on | Contents |
|---|---|---|
| `api` | nothing | DTOs only: `Observation`, `Action`, `Decision`, run-log records |
| `api` | nothing | DTOs only: `Observation`, `Action`, `Belief`, `Decision`, run-log records, the `JsonWriter` they share |
| `harness` | `core`, `api` | `Observer` (the only class allowed to read game state into the bot), `ActionExecutor` (the only class that drives the hero), RNG control, snapshot/restore, redetermination, `HeadlessBoot` (the backend, the no-op graphics binding, in-memory settings), `HeadlessScene` (the game's own scene, constructed without a graphics context) and `SceneStepper` (one fenced frame at a time), `HeadlessDriver` (owns a Run in `org.shatterfish.harness.driver`: starts a seeded game the way a player does and steps it to each Input wait, a death or a requested scene change), `EmbeddedDriver` |
| `codex` | `core` | Reflection dump of every mob, item, generator table, mob rotation, trap, recipe, and changelog entry, parameterised by depth and challenges; writes `codex/<tag>/*.json` and generated docs |
| `brain` | `api` only | Beliefs, scripted policies, tactical search, strategic playbooks, evaluation. Identical code runs headless and in the overlay |
Expand Down
Loading
Loading