Session¶
The session layer plays any game move by move and returns semantic frames: the board by meaning, the decision with its checks and evidence, the panel and the outcome. The command line and the web showcase both run on it, and it speaks the versioned protocol described on Session protocol; recorded games use the replay format. Open this page when you drive a game from Python or write a front end.
Everything below is importable from grounded_decision.session and its
modules. The game modules' contract is described in
Agents and checks by game and the
web plan.
session ¶
The session layer: play any game move by move and get semantic frames.
One pure API for every front end. The command line (grounded-decision
play, its terminal views and --view json), the web showcase's
browser worker and a future server all open a :class:Session from a
:class:SessionSpec and speak the versioned protocol in
:mod:~grounded_decision.session.protocol. Nothing here does I/O, starts
threads, handles signals or reads the wall clock, so it runs unchanged in
Pyodide.
Example::
from grounded_decision.session import Session, SessionSpec
session = Session.open(SessionSpec("tictactoe", agents=("you", "gated")))
session.legal_moves()[4].notation # "5"
frame = session.submit("5") # X takes the centre
frame = session.step() # the gated player answers
frame.decision.verdict, frame.board.cells
Modules and what they own:
:mod:~grounded_decision.session.spec
:class:SessionSpec: what a session plays, validated as play does.
:mod:~grounded_decision.session.session
:class:Session, its :class:~.session.Seat and
:class:~.session.SubmittedMoves, and :class:SessionError.
:mod:~grounded_decision.session.frame
:class:Frame and its parts (board, decision, panel, outcome).
:mod:~grounded_decision.session.builder
:class:~.builder.FrameBuilder: frames from positions and moves.
:mod:~grounded_decision.session.notation
:class:~.notation.MoveNotation: typed moves, and each legal move's text.
:mod:~grounded_decision.session.catalogue
:class:~.catalogue.Catalogue: what describe returns.
:mod:~grounded_decision.session.protocol
The messages, :data:PROTOCOL_VERSION, the :class:~.protocol.Endpoint
that answers them and the :class:~.protocol.ProtocolSchema.
:mod:~grounded_decision.session.wire
JSON encoding, validated decoding and JSON Schema from the dataclasses.
:mod:~grounded_decision.session.compatibility
:class:~.compatibility.SchemaChanges: what a schema change breaks.
:mod:~grounded_decision.session.tokens
:class:~.tokens.DesignTokens: colours and glyphs from the terminal palette.
:mod:~grounded_decision.session.jsonlines
--view json: the frames' JSON-lines projection.
:mod:~grounded_decision.session.replay
:class:~.replay.Replay: the .gdr replay format, recorded,
encoded, decoded and verified.
:mod:~grounded_decision.session.gallery
The curated replays make replays writes (the site's gallery and
the replay package's golden files).
:mod:~grounded_decision.session.artefacts
:class:~.artefacts.Artefacts: the generated files make protocol writes.
PROTOCOL_VERSION
module-attribute
¶
The protocol's semantic version: major.minor.patch (see docs/reference/protocol.md).
Frame
dataclass
¶
Frame(
index: int,
to_move: int | None,
awaiting_input: bool,
decision: MoveDecision | None,
board: Board,
transition: tuple[TransitionStill, ...],
move_cells: tuple[Point, ...],
proposal_cells: tuple[Point, ...],
event: EventLine | None,
panel: Panel,
outcome: Outcome,
legal_moves: tuple[LegalMove, ...],
assumption: str,
arrows: tuple[MoveArrow, ...] = (),
)
One position of a session, with everything a front end draws for it.
Attributes:
| Name | Type | Description |
|---|---|---|
index |
int
|
Moves played to reach it (0 is the starting position). |
to_move |
int | None
|
The seat whose turn it is, or null once the game is over. |
awaiting_input |
bool
|
Whether that seat is a person ( |
decision |
MoveDecision | None
|
The move that led here and how it was chosen; null at the start. |
board |
Board
|
The position. |
transition |
tuple[TransitionStill, ...]
|
In-between pictures of the move, in order (may be empty). |
move_cells |
tuple[Point, ...]
|
The cells the move touched, origin first. |
proposal_cells |
tuple[Point, ...]
|
On an override, the cells the rejected proposal would have touched; else empty. |
event |
EventLine | None
|
What the move did in a few words, or null. |
panel |
Panel
|
The panel's contents. |
outcome |
Outcome
|
Whether the game is over and why. |
legal_moves |
tuple[LegalMove, ...]
|
When a person is to move, every legal move with its notation; else empty. |
assumption |
str
|
When the world is not |
arrows |
tuple[MoveArrow, ...]
|
The move as arrows (protocol 1.7): the played move and, on an override, the rejected proposal, each checked against the previous frame's board; empty when the move is not one piece going from one cell to another (a placement, a drop, a slide of the whole board). |
LegalMove
dataclass
¶
LegalMove(
notation: str,
description: str,
cells: tuple[Point, ...],
certified: bool | None,
)
A move a person may submit in the current position.
Attributes:
| Name | Type | Description |
|---|---|---|
notation |
str
|
The text |
description |
str
|
The move as the game describes it. |
cells |
tuple[Point, ...]
|
The cells it touches, origin first and destination last. |
certified |
bool | None
|
For a guarded seat, whether the gate would pass it; else null. |
SessionInfo
dataclass
¶
SessionInfo(
game: str,
title: str,
seed: int,
seats: tuple[SeatInfo, ...],
world: str | None,
panel: PanelSlots,
max_steps: int | None,
controls: RealtimeControls | None = None,
)
What stays the same for a whole session.
Attributes:
| Name | Type | Description |
|---|---|---|
game |
str
|
The game's name. |
title |
str
|
Its title. |
seed |
int
|
The seed that fixes every random choice. |
seats |
tuple[SeatInfo, ...]
|
Who plays, in turn order. |
world |
str | None
|
The world (who controls the randomness), or null for a game without worlds. |
panel |
PanelSlots
|
The panel's fixed rows. |
max_steps |
int | None
|
The move budget, or null for none. |
controls |
RealtimeControls | None
|
Real-time keys (1.5), or null. |
Endpoint
dataclass
¶
Endpoint(
session: Session | None = None,
comparison: ComparisonSession | None = None,
audit: AuditSession | None = None,
_catalogue: Catalogue | None = None,
_demos: Demos = Demos(),
_encoder: WireEncoder = WireEncoder(),
)
Answers one front end's requests, holding its open session, comparison and audit.
:meth:handle takes a decoded JSON message and always returns a reply
(an error reply rather than an exception), so a transport only
moves JSON; :meth:handle_text does the same for JSON text.
Attributes:
| Name | Type | Description |
|---|---|---|
session |
Session | None
|
The open session, or |
comparison |
ComparisonSession | None
|
The open comparison, or |
audit |
AuditSession | None
|
The open audit, or |
handle ¶
handle(message: JsonValue) -> JsonObject
The reply to one request, as a JSON object.
Source code in src/grounded_decision/session/protocol.py
handle_text ¶
The reply to one request given as JSON text, as compact JSON text.
What a transport carries (the browser worker passes these strings
between JavaScript and Python). Text that is not JSON gets the
message error a non-object gets.
Source code in src/grounded_decision/session/protocol.py
Session
dataclass
¶
Session(
spec: SessionSpec,
match: Match[Any, Any],
seats: tuple[Seat, ...],
builder: FrameBuilder,
cut_off: bool = False,
_frame: Frame | None = None,
)
One match of a :class:~.spec.SessionSpec, stepped move by move.
Open one with :meth:open. :meth:step plays the agent to move;
:meth:submit plays a person's move; :meth:run steps until a person
must move or the game ends. :attr:frame is the current frame.
Attributes:
| Name | Type | Description |
|---|---|---|
spec |
SessionSpec
|
What is played. |
match |
Match[Any, Any]
|
The game, the agents and the position. |
seats |
tuple[Seat, ...]
|
Who sits where. |
builder |
FrameBuilder
|
Builds frames, keeping the panel's running state. |
cut_off |
bool
|
Whether the move budget has stopped the game. |
controls
property
¶
The game's real-time controls (controls), or None for a turn-based game.
open
classmethod
¶
open(
spec: SessionSpec,
*,
human: Callable[[], HumanProposer] | None = None,
clock: Clock | None = None,
) -> Session
A session at the starting position of spec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
SessionSpec
|
The game, seats, rules, world and seed. |
required |
human
|
Callable[[], HumanProposer] | None
|
Makes each person's seat (the command line's terminal
prompt); by default a :class: |
None
|
clock
|
Clock | None
|
Times each decision; |
None
|
Source code in src/grounded_decision/session/session.py
step ¶
step() -> Frame
The agent to move decides and plays; the frame after its move.
Raises:
| Type | Description |
|---|---|
SessionError
|
The game is over, or a person must move. |
Source code in src/grounded_decision/session/session.py
run ¶
run(count: int = 1) -> tuple[Frame, ...]
Up to count moves, stopping early when a person must move or the game ends.
Source code in src/grounded_decision/session/session.py
submit ¶
submit(text: str) -> Frame
Play a person's move, typed in the game's notation; the frame after it.
With a guarded seat the move is only a proposal: the gate checks it and plays its safe fallback if a check fails (the frame's decision says why).
Raises:
| Type | Description |
|---|---|
SessionError
|
Not a person's turn, the text names no move, or (unguarded) the move is not legal. |
Source code in src/grounded_decision/session/session.py
legal_moves ¶
legal_moves() -> tuple[LegalMove, ...]
Every legal move of the seat to move, with the text that submits it.
Moves are listed as the mover sees the position (a hidden-information game shows a person only what they may know); for a guarded seat, each says whether the gate would pass it.
Source code in src/grounded_decision/session/session.py
select ¶
select(piece: Point) -> PieceSelection
A person picks the piece on piece: its targets and the move to each.
Games with select(state, text) and cell_name(state, position)
hooks (Hexxagon) highlight their own targets, as the terminal
prompt does; for any other game the targets are the legal moves
that start on piece.
Raises:
| Type | Description |
|---|---|
SessionError
|
Not a person's turn, or no move starts on |
Source code in src/grounded_decision/session/session.py
play ¶
play(
observers: Sequence[Observer[Any, Any]] = (),
*,
detail: bool = True,
keep_steps: bool = False,
) -> MatchRecord[Any, Any]
Play the match to the end, telling observers (the terminal views) every move.
This is how the command line runs a session: no frames are built,
so a huge board runs at full speed. detail is what agents see
from :func:~grounded_decision.examples.games.engine.detail_wanted.
Source code in src/grounded_decision/session/session.py
record ¶
The match so far (moves, overrides, outcome), without its steps.
SessionError ¶
Bases: Exception
The session cannot do that now (not your turn, an illegal move, the game is over).
SessionSpec
dataclass
¶
SessionSpec(
game: str,
agents: tuple[str, ...] = (),
world: str | None = None,
seed: int = 0,
size: str | None = None,
players: int | None = None,
until_length: int | None = None,
rules: Mapping[str, RuleValue] = dict[str, RuleValue](),
you: bool = False,
you_play: str | None = None,
guarded: bool = False,
max_steps: int | None = None,
)
Everything that fixes a match: game, seats, rules, world and seed.
Construction validates it all, exactly as grounded-decision play
does, and raises
:class:~grounded_decision.examples.games.registry.PlayError with the
command line's message otherwise.
Attributes:
| Name | Type | Description |
|---|---|---|
game |
str
|
The game's name ( |
agents |
tuple[str, ...]
|
One agent name per seat, in seat order; empty for the
game's default line-up; one name fills every seat; |
world |
str | None
|
Who controls the randomness ( |
seed |
int
|
Fixes every random choice; the same spec always plays the same game. |
size |
str | None
|
|
players |
int | None
|
Number of players, for games with a choice (Chinese checkers). |
until_length |
int | None
|
Snake's length goal; null for a full board. |
rules |
Mapping[str, RuleValue]
|
Rule options by name, as the game's |
you |
bool
|
Put a person in a seat ( |
you_play |
str | None
|
The person's seat, by player name or number (1 = first). |
guarded |
bool
|
The game's gate checks the person's moves. |
max_steps |
int | None
|
The move budget; null (or 0) for the default (10,000,000). |
entry
class-attribute
instance-attribute
¶
entry: GameModule = field(
init=False, repr=False, compare=False, metadata=_DERIVED
)
The game's module (derived).
rules_game
class-attribute
instance-attribute
¶
The configured game (derived; not yet bound to agents).
seats
class-attribute
instance-attribute
¶
One agent name per seat, defaults filled in (derived).
build_agents ¶
build_agents(
human: Callable[[], HumanProposer],
) -> list[Agent[Any, Any]]
Fresh agents for every seat; person seats are human(), guarded if asked.
Raises:
| Type | Description |
|---|---|
PlayError
|
An unknown agent, or |
Source code in src/grounded_decision/session/spec.py
spec ¶
What a session plays: the :class:SessionSpec.
A spec names everything that changes the game itself (the game, its
size and rules, the world, the seats and the seed), never how it is
shown. grounded-decision play builds one from its options and the web
front end sends one in an open message; both are validated here by
:class:~grounded_decision.examples.games.registry.GameModule, the code
play has always used, so the command line and a session can never
disagree about what is allowed.
SessionSpec
dataclass
¶
SessionSpec(
game: str,
agents: tuple[str, ...] = (),
world: str | None = None,
seed: int = 0,
size: str | None = None,
players: int | None = None,
until_length: int | None = None,
rules: Mapping[str, RuleValue] = dict[str, RuleValue](),
you: bool = False,
you_play: str | None = None,
guarded: bool = False,
max_steps: int | None = None,
)
Everything that fixes a match: game, seats, rules, world and seed.
Construction validates it all, exactly as grounded-decision play
does, and raises
:class:~grounded_decision.examples.games.registry.PlayError with the
command line's message otherwise.
Attributes:
| Name | Type | Description |
|---|---|---|
game |
str
|
The game's name ( |
agents |
tuple[str, ...]
|
One agent name per seat, in seat order; empty for the
game's default line-up; one name fills every seat; |
world |
str | None
|
Who controls the randomness ( |
seed |
int
|
Fixes every random choice; the same spec always plays the same game. |
size |
str | None
|
|
players |
int | None
|
Number of players, for games with a choice (Chinese checkers). |
until_length |
int | None
|
Snake's length goal; null for a full board. |
rules |
Mapping[str, RuleValue]
|
Rule options by name, as the game's |
you |
bool
|
Put a person in a seat ( |
you_play |
str | None
|
The person's seat, by player name or number (1 = first). |
guarded |
bool
|
The game's gate checks the person's moves. |
max_steps |
int | None
|
The move budget; null (or 0) for the default (10,000,000). |
entry
class-attribute
instance-attribute
¶
entry: GameModule = field(
init=False, repr=False, compare=False, metadata=_DERIVED
)
The game's module (derived).
rules_game
class-attribute
instance-attribute
¶
The configured game (derived; not yet bound to agents).
seats
class-attribute
instance-attribute
¶
One agent name per seat, defaults filled in (derived).
build_agents ¶
build_agents(
human: Callable[[], HumanProposer],
) -> list[Agent[Any, Any]]
Fresh agents for every seat; person seats are human(), guarded if asked.
Raises:
| Type | Description |
|---|---|
PlayError
|
An unknown agent, or |
Source code in src/grounded_decision/session/spec.py
session ¶
A match you can step, feed moves into and draw: the :class:Session.
open(spec) builds the game and its seats; :meth:Session.step lets
the agent to move decide and play; :meth:Session.submit plays a
person's move; every call returns a :class:~.frame.Frame. The command
line drives the same session with :meth:Session.play and its terminal
views as observers, so both run one code path
(:class:~grounded_decision.examples.games.engine.Match).
The session layer is pure: no I/O, threads, signals or wall clock (a test imports it with those blocked), so it runs unchanged in a browser (Pyodide) and on a server. A clock may be injected for timing.
Concepts and the classes that own them:
:class:Session
One match in progress and its frames.
:class:Seat
Who sits in one seat: the agent, and for a person how their moves
arrive and whether the gate guards them.
:class:SubmittedMoves
A person's seat whose moves arrive through :meth:Session.submit.
:class:SessionError
A call the session cannot honour now (a move when it is not your
turn, an unreadable or illegal move, a finished game).
SessionError ¶
Bases: Exception
The session cannot do that now (not your turn, an illegal move, the game is over).
SubmittedMoves
dataclass
¶
SubmittedMoves(
name: str = HUMAN,
certify: Callable[[Any, Any], bool] | None = None,
pending: Any = None,
predictable: bool = False,
)
A person's seat whose moves come from :meth:Session.submit.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The agent name ( |
certify |
Callable[[Any, Any], bool] | None
|
Set when guarded: whether the gate would pass a move. |
pending |
Any
|
The move to play next, set by |
predictable |
bool
|
Always |
Seat
dataclass
¶
Seat(
index: int,
player: str,
agent: Agent[Any, Any],
proposer: HumanProposer | None,
)
Who sits in one seat.
Attributes:
| Name | Type | Description |
|---|---|---|
index |
int
|
The seat, in turn order. |
player |
str
|
The player's name. |
agent |
Agent[Any, Any]
|
The agent that moves for it (a gate around a person when guarded). |
proposer |
HumanProposer | None
|
A person's own seat object (the terminal prompt or
:class: |
submitted
property
¶
submitted: SubmittedMoves | None
The seat's :class:SubmittedMoves, if its moves come from submit.
Session
dataclass
¶
Session(
spec: SessionSpec,
match: Match[Any, Any],
seats: tuple[Seat, ...],
builder: FrameBuilder,
cut_off: bool = False,
_frame: Frame | None = None,
)
One match of a :class:~.spec.SessionSpec, stepped move by move.
Open one with :meth:open. :meth:step plays the agent to move;
:meth:submit plays a person's move; :meth:run steps until a person
must move or the game ends. :attr:frame is the current frame.
Attributes:
| Name | Type | Description |
|---|---|---|
spec |
SessionSpec
|
What is played. |
match |
Match[Any, Any]
|
The game, the agents and the position. |
seats |
tuple[Seat, ...]
|
Who sits where. |
builder |
FrameBuilder
|
Builds frames, keeping the panel's running state. |
cut_off |
bool
|
Whether the move budget has stopped the game. |
controls
property
¶
The game's real-time controls (controls), or None for a turn-based game.
open
classmethod
¶
open(
spec: SessionSpec,
*,
human: Callable[[], HumanProposer] | None = None,
clock: Clock | None = None,
) -> Session
A session at the starting position of spec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
SessionSpec
|
The game, seats, rules, world and seed. |
required |
human
|
Callable[[], HumanProposer] | None
|
Makes each person's seat (the command line's terminal
prompt); by default a :class: |
None
|
clock
|
Clock | None
|
Times each decision; |
None
|
Source code in src/grounded_decision/session/session.py
step ¶
step() -> Frame
The agent to move decides and plays; the frame after its move.
Raises:
| Type | Description |
|---|---|
SessionError
|
The game is over, or a person must move. |
Source code in src/grounded_decision/session/session.py
run ¶
run(count: int = 1) -> tuple[Frame, ...]
Up to count moves, stopping early when a person must move or the game ends.
Source code in src/grounded_decision/session/session.py
submit ¶
submit(text: str) -> Frame
Play a person's move, typed in the game's notation; the frame after it.
With a guarded seat the move is only a proposal: the gate checks it and plays its safe fallback if a check fails (the frame's decision says why).
Raises:
| Type | Description |
|---|---|
SessionError
|
Not a person's turn, the text names no move, or (unguarded) the move is not legal. |
Source code in src/grounded_decision/session/session.py
legal_moves ¶
legal_moves() -> tuple[LegalMove, ...]
Every legal move of the seat to move, with the text that submits it.
Moves are listed as the mover sees the position (a hidden-information game shows a person only what they may know); for a guarded seat, each says whether the gate would pass it.
Source code in src/grounded_decision/session/session.py
select ¶
select(piece: Point) -> PieceSelection
A person picks the piece on piece: its targets and the move to each.
Games with select(state, text) and cell_name(state, position)
hooks (Hexxagon) highlight their own targets, as the terminal
prompt does; for any other game the targets are the legal moves
that start on piece.
Raises:
| Type | Description |
|---|---|
SessionError
|
Not a person's turn, or no move starts on |
Source code in src/grounded_decision/session/session.py
play ¶
play(
observers: Sequence[Observer[Any, Any]] = (),
*,
detail: bool = True,
keep_steps: bool = False,
) -> MatchRecord[Any, Any]
Play the match to the end, telling observers (the terminal views) every move.
This is how the command line runs a session: no frames are built,
so a huge board runs at full speed. detail is what agents see
from :func:~grounded_decision.examples.games.engine.detail_wanted.
Source code in src/grounded_decision/session/session.py
record ¶
The match so far (moves, overrides, outcome), without its steps.
frame ¶
What a front end draws after each move: the :class:Frame and its parts.
A frame is semantic, like the game hooks it comes from: cells are
:class:~grounded_decision.examples.games.views.CellKind values, never
glyphs or colours, and highlights are
:class:~grounded_decision.examples.games.views.Overlay values. The
terminal views draw the same meaning with characters; the web board
draws it with the shared design tokens. Every class here is a frozen
value that :func:~grounded_decision.session.wire.encode turns into
JSON, and the protocol's JSON Schema is generated from these classes.
Concepts and the classes that own them:
:class:Frame
One position of a session: the board, the move that led to it and
how it was decided, the panel, the outcome.
:class:Board
The picture of a position: cells, per-cell details, ordered paths,
overlays, hex offsets and coordinates (:class:CellNote,
:class:BoardPath, :class:Mark, :class:Label, :class:Point).
:class:TransitionStill
One in-between picture of a move, for the player to animate.
:class:MoveArrow
A piece's move as an arrow, over the position before the move (the
rule every arrow obeys is :class:~.arrows.ArrowRule).
:class:MoveDecision
The move and how it was chosen: the proposal, every check with its
evidence (:class:Check), the verdict, notes and metrics.
:class:Panel
The fixed panel's contents (:class:Counters, :class:StandingRow,
:class:EventLine, :class:Sparkline); its fixed rows are a
:class:PanelSlots, sent once per session.
:class:Outcome
Whether the game is over, who won and why.
:class:LegalMove
A move a person may submit, with the text that submits it.
:class:PieceSelection
A person's selected piece, its highlighted targets and their moves.
Scalar
module-attribute
¶
A metric or stat value: what a panel prints.
RuleValue
module-attribute
¶
A rule option's value (None turns an option off, as --stall-plies 0 does).
Point
dataclass
¶
A cell of the board, in the board's cells coordinates.
Attributes:
| Name | Type | Description |
|---|---|---|
row |
int
|
Row, 0 at the top. |
column |
int
|
Column, 0 at the left. |
CellNote
dataclass
¶
CellNote(
row: int,
column: int,
order: float | None,
connections: tuple[str, ...],
heading: str | None,
label: str | None,
tint: int | None,
extent: tuple[float, ...] | None = None,
)
Extra detail for one cell beyond its kind (CellDetail in the views).
Attributes:
| Name | Type | Description |
|---|---|---|
row |
int
|
The cell's row. |
column |
int
|
The cell's column. |
order |
float | None
|
0.0 for the head or newest … 1.0 for the tail or oldest (colour gradients; a 2048 tile's colour level), or null. |
connections |
tuple[str, ...]
|
Neighbours this cell is joined to ( |
heading |
str | None
|
Direction of travel for an arrow ( |
label |
str | None
|
Short text drawn on the cell (a Minesweeper count), or null. |
tint |
int | None
|
The seat whose territory the cell is (a home arm), or null. |
extent |
tuple[float, ...] | None
|
Part of the cell covered, LTRB (1.5). |
BoardPath
dataclass
¶
BoardPath(
kind: PathKind,
cells: tuple[Point, ...],
heading: str | None = None,
)
An ordered path of cells (CellPath in the views), from which a front end derives details.
The cells' notes are not listed in the board's details: a reader
derives them, as the terminal does. Cell i of n gets
order i / (n - 1) (0.0 for a one-cell path) and
connections to cells i - 1 and i + 1 when they are
neighbours (diagonals included); the first cell gets heading: the
path's own when it has one (1.8: the snake's last move, which a
one-cell snake cannot show), else the direction from the second cell to
it. A note the board's details
gives for the same cell replaces the derived one. New kinds may be
added in a minor protocol version.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
PathKind
|
What the path is: |
cells |
tuple[Point, ...]
|
The cells, head (or newest) first. |
heading |
str | None
|
Where the head faces ( |
Mark
dataclass
¶
A highlight on one cell for this frame.
Attributes:
| Name | Type | Description |
|---|---|---|
row |
int
|
The cell's row. |
column |
int
|
The cell's column. |
overlay |
Overlay
|
What the highlight means. |
Label
dataclass
¶
A coordinate drawn on one (empty) cell, at most two characters.
Attributes:
| Name | Type | Description |
|---|---|---|
row |
int
|
The cell's row. |
column |
int
|
The cell's column. |
text |
str
|
The label. |
Board
dataclass
¶
Board(
layout: Layout,
rows: int,
columns: int,
cells: tuple[tuple[CellKind, ...], ...],
offsets: tuple[int, ...],
details: tuple[CellNote, ...],
overlays: tuple[Mark, ...],
labels: tuple[Label, ...],
row_labels: tuple[str, ...],
column_labels: tuple[str, ...],
focus: Point | None,
paths: tuple[BoardPath, ...] = (),
)
The picture of one position, by meaning: what each cell holds and what is highlighted.
Attributes:
| Name | Type | Description |
|---|---|---|
layout |
Layout
|
How cells sit on screen: |
rows |
int
|
Number of rows of |
columns |
int
|
Number of columns of every row. |
cells |
tuple[tuple[CellKind, ...], ...]
|
Rows of cell kinds, top to bottom. |
offsets |
tuple[int, ...]
|
Hex boards: per-row ( |
details |
tuple[CellNote, ...]
|
Extra detail for some cells. |
overlays |
tuple[Mark, ...]
|
Highlights: the last move, a gate override's rejected and chosen targets, pieces that changed hands, targets. |
labels |
tuple[Label, ...]
|
Coordinates on cells. |
row_labels |
tuple[str, ...]
|
Coordinates of the rows (may be empty). |
column_labels |
tuple[str, ...]
|
Coordinates of the columns (may be empty). |
focus |
Point | None
|
The cell to keep in view on a board bigger than the screen (the snake's head), or null. |
paths |
tuple[BoardPath, ...]
|
Ordered paths of cells (the snake's body, head first),
whose cells' details are derived rather than listed in
|
of
classmethod
¶
of(
game: Any,
state: Any,
overlays: Mapping[Position, Overlay] | None = None,
) -> Board
The whole board of state, as the terminal views collect it.
Uses :func:~grounded_decision.examples.games.views.board_image
(the views' own code path) on a window that is the whole board,
with coordinates. overlays are the move's highlights; the
game's own overlays(state) are merged in as the views do. The
game's cell_paths travel as :class:BoardPath values, not
expanded into details. The game's move_path_details (the
terminal's line along the last move) are left out: the frame's
arrows show the move.
Source code in src/grounded_decision/session/frame.py
TransitionStill
dataclass
¶
TransitionStill(board: Board, weight: float)
One in-between picture of a move (a piece landing before pieces flip).
Attributes:
| Name | Type | Description |
|---|---|---|
board |
Board
|
The intermediate position, with this picture's overlays. |
weight |
float
|
Its share of the move's animation time (relative). |
ArrowRole ¶
MoveArrow
dataclass
¶
A piece going from one cell to another, drawn over the position before the move.
The frame builder emits an arrow only when
:class:~grounded_decision.session.arrows.ArrowRule holds on the
previous frame's board: the origin holds a piece of seat and every
later cell is free. A reader draws it over the previous frame's board
(where the piece still stands) or while animating into this frame,
never over this frame's board, where the origin may be empty.
Attributes:
| Name | Type | Description |
|---|---|---|
role |
ArrowRole
|
|
seat |
int
|
The seat whose piece moves. |
cells |
tuple[Point, ...]
|
The path, origin first and destination last (a jump's landings in between); two or more cells. |
Check
dataclass
¶
One check the gate ran on a proposal.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The check's name (a key of the game's checks in |
passed |
bool
|
Whether the proposal satisfied it. |
explanation |
str
|
The evidence, in one sentence. |
MoveDecision
dataclass
¶
MoveDecision(
seat: int,
player: str,
agent: str,
move: str,
proposal: str | None,
verdict: Verdict | None,
checks: tuple[Check, ...],
notes: tuple[str, ...],
metrics: Mapping[str, Scalar],
regime: Regime | None,
lapse: str = "",
)
The move that led to a frame and how it was chosen.
Attributes:
| Name | Type | Description |
|---|---|---|
seat |
int
|
The seat that moved. |
player |
str
|
That seat's name ( |
agent |
str
|
The agent in that seat. |
move |
str
|
The move played, as the game describes it. |
proposal |
str | None
|
For a gated agent, the proposer's move; else null. |
verdict |
Verdict | None
|
|
checks |
tuple[Check, ...]
|
Every check run on the proposal. |
notes |
tuple[str, ...]
|
Extra lines from the agent (captures, search depth). |
metrics |
Mapping[str, Scalar]
|
Numbers from the agent (nodes searched, evaluation). |
regime |
Regime | None
|
The agent's declared information regime, or null. |
lapse |
str
|
Why the gate cannot certify this move (no move meets what its checks protect), so its guarantee lapses here (protocol 1.10); empty while it holds. |
of
classmethod
¶
of(game: Any, step: Step[Any, Any]) -> MoveDecision
The decision of step, with moves described from the position before it.
Source code in src/grounded_decision/session/frame.py
EventLine
dataclass
¶
What a move did, in a few words (the panel keeps the last few).
Attributes:
| Name | Type | Description |
|---|---|---|
number |
int
|
The move's number (1 for the first move). |
text |
str
|
|
size |
int
|
Pieces taken, converted or cleared (0 for a quiet move). |
big |
bool
|
Whether to draw it prominently. |
StandingRow
dataclass
¶
StandingRow(
seat: int,
name: str,
kind: CellKind,
value: float,
maximum: float,
text: str,
history: tuple[float, ...],
shared: bool = False,
)
One player's progress bar in the panel.
Attributes:
| Name | Type | Description |
|---|---|---|
seat |
int
|
The player. |
name |
str
|
The player's name. |
kind |
CellKind
|
A cell kind whose glyph and colour stand for the player. |
value |
float
|
Progress, between 0 and |
maximum |
float
|
A full bar. |
text |
str
|
Short text after the bar. |
history |
tuple[float, ...]
|
Recent values, oldest first (the sparkline). |
shared |
bool
|
Every player's value is a share of the same |
Counters
dataclass
¶
Running totals over the moves so far.
Attributes:
| Name | Type | Description |
|---|---|---|
moves |
int
|
Moves played. |
acts |
int
|
Gated moves whose proposal was played. |
overrides |
int
|
Gated moves overridden by the safe fallback. |
ungated |
int
|
Moves by agents without a gate. |
Sparkline
dataclass
¶
The panel's sparkline: one number followed over the recent moves.
Attributes:
| Name | Type | Description |
|---|---|---|
label |
str
|
What it follows (a stat such as |
values |
tuple[float, ...]
|
Recent values, oldest first. |
PanelSlots
dataclass
¶
PanelSlots(
checks: tuple[str, ...],
note_rows: int,
stats_keys: tuple[str, ...],
standing_rows: int,
event_rows: int,
sparkline: bool,
gated: bool,
)
The panel's fixed rows for a whole session (PanelLayout in the views).
The panel never grows or shrinks between moves: each slot has as many rows as the game can ever fill, and a slot with nothing this move stays empty.
Attributes:
| Name | Type | Description |
|---|---|---|
checks |
tuple[str, ...]
|
Every check the gate can run, one row each, in order. |
note_rows |
int
|
Rows for a decision's notes. |
stats_keys |
tuple[str, ...]
|
The stats shown, in order (from the starting position or the game's declared schema). |
standing_rows |
int
|
One row per player's standing (0 without standings). |
event_rows |
int
|
Rows of recent move events (0 if the game has none). |
sparkline |
bool
|
Whether a sparkline row is shown (games without standings). |
gated |
bool
|
Whether check rows are reserved. |
Panel
dataclass
¶
Panel(
counters: Counters,
stats: Mapping[str, Scalar] | None,
standings: tuple[StandingRow, ...],
events: tuple[EventLine, ...],
sparkline: Sparkline | None,
)
The panel's contents for one frame (its rows are the session's :class:PanelSlots).
Attributes:
| Name | Type | Description |
|---|---|---|
counters |
Counters
|
Moves, acts, overrides and ungated moves so far. |
stats |
Mapping[str, Scalar] | None
|
The game's running stats for this position (all of them;
the panel shows |
standings |
tuple[StandingRow, ...]
|
One row per player, or empty. |
events |
tuple[EventLine, ...]
|
Recent move events, newest first. |
sparkline |
Sparkline | None
|
The sparkline, or null when the panel has none. |
Outcome
dataclass
¶
Outcome(
finished: bool,
winner: int | None,
reason: str,
scores: tuple[float, ...],
cut_off: bool,
placings: tuple[int, ...],
)
Where the game stands.
Attributes:
| Name | Type | Description |
|---|---|---|
finished |
bool
|
Whether the game is over. |
winner |
int | None
|
The winning seat, or null (unfinished, a draw, or no winner). |
reason |
str
|
One sentence ( |
scores |
tuple[float, ...]
|
Per-player scores, if the game keeps them. |
cut_off |
bool
|
Whether the session's move budget ended it. |
placings |
tuple[int, ...]
|
Place per seat (1 = first) once a game of several players is over, if the game ranks them; else empty. |
LegalMove
dataclass
¶
LegalMove(
notation: str,
description: str,
cells: tuple[Point, ...],
certified: bool | None,
)
A move a person may submit in the current position.
Attributes:
| Name | Type | Description |
|---|---|---|
notation |
str
|
The text |
description |
str
|
The move as the game describes it. |
cells |
tuple[Point, ...]
|
The cells it touches, origin first and destination last. |
certified |
bool | None
|
For a guarded seat, whether the gate would pass it; else null. |
PieceSelection
dataclass
¶
PieceSelection(
piece: Point,
overlays: tuple[Mark, ...],
hint: str,
moves: tuple[LegalMove, ...],
)
A person picked a piece: where it may go, before they pick the target.
The web form of the terminal's selection prompt (Hexxagon: type a
piece, see its clone and jump targets, type the target). Games with a
select hook draw their own highlights; for any other game the
targets are the legal moves that start on the piece.
Attributes:
| Name | Type | Description |
|---|---|---|
piece |
Point
|
The selected cell. |
overlays |
tuple[Mark, ...]
|
|
hint |
str
|
One line on what the highlights mean. |
moves |
tuple[LegalMove, ...]
|
One legal move per target, origin first and the target
last in |
Frame
dataclass
¶
Frame(
index: int,
to_move: int | None,
awaiting_input: bool,
decision: MoveDecision | None,
board: Board,
transition: tuple[TransitionStill, ...],
move_cells: tuple[Point, ...],
proposal_cells: tuple[Point, ...],
event: EventLine | None,
panel: Panel,
outcome: Outcome,
legal_moves: tuple[LegalMove, ...],
assumption: str,
arrows: tuple[MoveArrow, ...] = (),
)
One position of a session, with everything a front end draws for it.
Attributes:
| Name | Type | Description |
|---|---|---|
index |
int
|
Moves played to reach it (0 is the starting position). |
to_move |
int | None
|
The seat whose turn it is, or null once the game is over. |
awaiting_input |
bool
|
Whether that seat is a person ( |
decision |
MoveDecision | None
|
The move that led here and how it was chosen; null at the start. |
board |
Board
|
The position. |
transition |
tuple[TransitionStill, ...]
|
In-between pictures of the move, in order (may be empty). |
move_cells |
tuple[Point, ...]
|
The cells the move touched, origin first. |
proposal_cells |
tuple[Point, ...]
|
On an override, the cells the rejected proposal would have touched; else empty. |
event |
EventLine | None
|
What the move did in a few words, or null. |
panel |
Panel
|
The panel's contents. |
outcome |
Outcome
|
Whether the game is over and why. |
legal_moves |
tuple[LegalMove, ...]
|
When a person is to move, every legal move with its notation; else empty. |
assumption |
str
|
When the world is not |
arrows |
tuple[MoveArrow, ...]
|
The move as arrows (protocol 1.7): the played move and, on an override, the rejected proposal, each checked against the previous frame's board; empty when the move is not one piece going from one cell to another (a placement, a drop, a slide of the whole board). |
SeatInfo
dataclass
¶
Who sits in one seat.
Attributes:
| Name | Type | Description |
|---|---|---|
seat |
int
|
The seat's index, in turn order. |
player |
str
|
The player's name ( |
agent |
str
|
The agent's name ( |
human |
bool
|
Whether a person plays it (moves come from |
guarded |
bool
|
Whether the game's gate checks the person's moves. |
regime |
Regime | None
|
The agent's declared information regime, or null. |
SessionInfo
dataclass
¶
SessionInfo(
game: str,
title: str,
seed: int,
seats: tuple[SeatInfo, ...],
world: str | None,
panel: PanelSlots,
max_steps: int | None,
controls: RealtimeControls | None = None,
)
What stays the same for a whole session.
Attributes:
| Name | Type | Description |
|---|---|---|
game |
str
|
The game's name. |
title |
str
|
Its title. |
seed |
int
|
The seed that fixes every random choice. |
seats |
tuple[SeatInfo, ...]
|
Who plays, in turn order. |
world |
str | None
|
The world (who controls the randomness), or null for a game without worlds. |
panel |
PanelSlots
|
The panel's fixed rows. |
max_steps |
int | None
|
The move budget, or null for none. |
controls |
RealtimeControls | None
|
Real-time keys (1.5), or null. |
protocol ¶
The protocol between a front end and the engine: versioned messages over JSON.
A front end (the web page's worker, later a WebSocket client) sends a
request and gets one reply. Requests: describe, open,
step, submit, select, and (protocol 1.3) compare,
audit and analyse. Replies: describe (the catalogue),
frames (the session's info and new frames), selection (a picked
piece's targets), comparison (finished games and the table so far),
audit (judged decisions and the report so far), analysis (the
session's position analysed) and error.
Protocol 1.4 adds the demos beyond games (:mod:.demos): learn,
monitor and runtime, answered by learned, monitored and
decisions. Protocol 1.5 adds real-time play (SessionInfo.controls:
the clock and the keys of a game played one tick at a time) and
sub-cell positions (CellNote.extent), for Breakout; 1.8 adds
RealtimeControls.repeat_last (a tick with no key repeats the last move:
Snake played in real time) and BoardPath.heading (where a path's head
faces, which a one-cell snake cannot show); 1.9 adds
GameInfo.size_offers (every size a game's board may take, its default
and usual sizes and the rule in words, per rule option where it differs;
empty for a fixed size); 1.10 adds MoveDecision.lapse (why a gate
cannot certify the move it played, so its guarantee lapses there); 1.11
adds GameInfo.rules (each rule option's label, kind, range, default,
meaning and the option values it needs). Every message is a
JSON object with a type; a request may carry an id, which its
reply repeats. The JSON Schema of all of them is generated from the
classes below (make protocol) and versioned by
:data:PROTOCOL_VERSION (semver; docs/reference/protocol.md has the
rules, and a test fails a breaking change without a major bump).
Concepts and the classes that own them:
:class:DescribeRequest, :class:OpenRequest, :class:StepRequest, :class:SubmitRequest, :class:SelectRequest, :class:CompareRequest, :class:AuditRequest, :class:AnalyseRequest
What a front end may ask.
:class:DescribeReply, :class:FramesReply, :class:SelectionReply, :class:ComparisonReply, :class:AuditReply, :class:AnalysisReply, :class:ErrorReply
What the engine answers.
:data:DOCUMENTS
Files the engine's types describe but no message carries (a lesson).
:class:Endpoint
Answers requests for one front end, holding its open session,
comparison and audit.
:class:ProtocolSchema
The JSON Schema of every message.
PROTOCOL_VERSION
module-attribute
¶
The protocol's semantic version: major.minor.patch (see docs/reference/protocol.md).
MAX_STEPS_PER_REQUEST
module-attribute
¶
The most moves one step request may ask for.
DOCUMENTS
module-attribute
¶
Types of files the site reads that no message carries: a lesson (make lessons).
DescribeRequest
dataclass
¶
Ask for every game, its agents, checks, worlds, variants and sizes.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['describe']
|
|
id |
int | None
|
Repeated in the reply, to match replies to requests. |
OpenRequest
dataclass
¶
OpenRequest(
spec: SessionSpec,
type: Literal["open"] = "open",
id: int | None = None,
)
Start a session (replacing any open one); the reply holds its first frame.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['open']
|
|
spec |
SessionSpec
|
What to play. |
id |
int | None
|
Repeated in the reply. |
StepRequest
dataclass
¶
Let the agents play up to count moves (stopping when a person must move).
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['step']
|
|
count |
int
|
Most moves to play, 1 to 10,000. |
id |
int | None
|
Repeated in the reply. |
SubmitRequest
dataclass
¶
Play a person's move, in the notation of the frame's legal_moves.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['submit']
|
|
move |
str
|
The move's notation. |
id |
int | None
|
Repeated in the reply. |
SelectRequest
dataclass
¶
SelectRequest(
cell: Point,
type: Literal["select"] = "select",
id: int | None = None,
)
A person picks a piece (protocol 1.1): the reply lists where it may go.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['select']
|
|
cell |
Point
|
The piece's cell, in the frame's board coordinates. |
id |
int | None
|
Repeated in the reply. |
CompareRequest
dataclass
¶
CompareRequest(
spec: CompareSpec,
type: Literal["compare"] = "compare",
games: tuple[int, ...] = (),
results: tuple[ComparedGame, ...] = (),
id: int | None = None,
)
Play games of a comparison (protocol 1.3): the reply has them and the table so far.
A spec equal to the open comparison's continues it, so several requests (or several engines, each told the others' results) fill in one table; any other spec starts a new comparison.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['compare']
|
|
spec |
CompareSpec
|
The comparison. |
games |
tuple[int, ...]
|
The games to play now, by index (at most 100; may be empty, to ask only for the table). A game already known is not played again. |
results |
tuple[ComparedGame, ...]
|
Games finished elsewhere (by another engine), to count in the table. |
id |
int | None
|
Repeated in the reply. |
AuditRequest
dataclass
¶
AuditRequest(
spec: AuditSpec,
type: Literal["audit"] = "audit",
moves: int = 100,
id: int | None = None,
)
Play on an audited match (protocol 1.3): the reply judges the audited agent's decisions.
A spec equal to the open audit's continues it; any other spec starts a new audit from the first move.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['audit']
|
|
spec |
AuditSpec
|
The audit. |
moves |
int
|
Most moves (all players) to play now, 1 to 10,000; none once the match is over. |
id |
int | None
|
Repeated in the reply. |
AnalyseRequest
dataclass
¶
Analyse the open session's position with the game's own solvers (protocol 1.3).
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['analyse']
|
|
id |
int | None
|
Repeated in the reply. |
DescribeReply
dataclass
¶
DescribeReply(
id: int | None,
version: str,
catalogue: Catalogue,
type: Literal["describe"] = "describe",
)
Every game that can be played.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['describe']
|
|
id |
int | None
|
The request's id, or null. |
version |
str
|
The protocol version the engine speaks. |
catalogue |
Catalogue
|
The games. |
FramesReply
dataclass
¶
FramesReply(
id: int | None,
session: SessionInfo,
frames: tuple[Frame, ...],
type: Literal["frames"] = "frames",
)
The open session and its new frames (oldest first).
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['frames']
|
|
id |
int | None
|
The request's id, or null. |
session |
SessionInfo
|
What stays the same for the session. |
frames |
tuple[Frame, ...]
|
The new frames: the first position after |
SelectionReply
dataclass
¶
SelectionReply(
id: int | None,
selection: PieceSelection,
type: Literal["selection"] = "selection",
)
A picked piece's targets, to highlight before the person picks one (protocol 1.1).
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['selection']
|
|
id |
int | None
|
The request's id, or null. |
selection |
PieceSelection
|
The piece, its highlights and the move to each target. |
ErrorReply
dataclass
¶
A request that could not be honoured; the session (if any) is unchanged.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['error']
|
|
id |
int | None
|
The request's id, or null. |
code |
ErrorCode
|
|
message |
str
|
What went wrong, in one sentence. |
ComparisonReply
dataclass
¶
ComparisonReply(
id: int | None,
plan: ComparePlan,
games: tuple[ComparedGame, ...],
table: CompareTable,
type: Literal["comparison"] = "comparison",
)
The games a compare request played and the table of every game known (protocol 1.3).
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['comparison']
|
|
id |
int | None
|
The request's id, or null. |
plan |
ComparePlan
|
What stays fixed for the comparison. |
games |
tuple[ComparedGame, ...]
|
The games asked for, finished, in the order asked. |
table |
CompareTable
|
The ranked table of every finished game this engine knows. |
AuditReply
dataclass
¶
AuditReply(
id: int | None,
info: AuditInfo,
decisions: tuple[AuditedMove, ...],
summary: AuditSummary,
type: Literal["audit"] = "audit",
)
The audited agent's decisions in the moves just played, and the report so far (protocol 1.3).
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['audit']
|
|
id |
int | None
|
The request's id, or null. |
info |
AuditInfo
|
What stays fixed for the audit. |
decisions |
tuple[AuditedMove, ...]
|
The audited agent's decisions among the moves just played, judged, oldest first. |
summary |
AuditSummary
|
The report of every decision so far. |
AnalysisReply
dataclass
¶
AnalysisReply(
id: int | None,
index: int,
analysis: Analysis | None,
type: Literal["analysis"] = "analysis",
)
The open session's position, analysed (protocol 1.3).
Attributes:
| Name | Type | Description |
|---|---|---|
type |
Literal['analysis']
|
|
id |
int | None
|
The request's id, or null. |
index |
int
|
The frame analysed (moves played to reach it). |
analysis |
Analysis | None
|
The analysis, or null where the game has none for this position. |
Endpoint
dataclass
¶
Endpoint(
session: Session | None = None,
comparison: ComparisonSession | None = None,
audit: AuditSession | None = None,
_catalogue: Catalogue | None = None,
_demos: Demos = Demos(),
_encoder: WireEncoder = WireEncoder(),
)
Answers one front end's requests, holding its open session, comparison and audit.
:meth:handle takes a decoded JSON message and always returns a reply
(an error reply rather than an exception), so a transport only
moves JSON; :meth:handle_text does the same for JSON text.
Attributes:
| Name | Type | Description |
|---|---|---|
session |
Session | None
|
The open session, or |
comparison |
ComparisonSession | None
|
The open comparison, or |
audit |
AuditSession | None
|
The open audit, or |
handle ¶
handle(message: JsonValue) -> JsonObject
The reply to one request, as a JSON object.
Source code in src/grounded_decision/session/protocol.py
handle_text ¶
The reply to one request given as JSON text, as compact JSON text.
What a transport carries (the browser worker passes these strings
between JavaScript and Python). Text that is not JSON gets the
message error a non-object gets.
Source code in src/grounded_decision/session/protocol.py
ProtocolSchema
dataclass
¶
ProtocolSchema(version: str = PROTOCOL_VERSION)
The JSON Schema (draft 2020-12) of every protocol message.
Attributes:
| Name | Type | Description |
|---|---|---|
version |
str
|
The protocol version it describes. |
document ¶
The whole schema: $defs for every type, and the request and reply unions.
Source code in src/grounded_decision/session/protocol.py
notation ¶
How a person names a move: parsing typed text, and the text for each legal move.
The terminal prompt (:class:~grounded_decision.examples.games.play.HumanAgent)
and :meth:grounded_decision.session.Session.submit parse through the
same :class:MoveNotation, so a move typed at the command line and one
sent by the web front end mean the same thing.
MoveNotation
dataclass
¶
A game's move notation: its parse_move, or numbers into the legal moves.
Games with parse_move(state, text) take their own notation
(e5, 11-15, w); others take the 1-based number of a move
in the list of legal moves.
Attributes:
| Name | Type | Description |
|---|---|---|
game |
Any
|
The rules (any object with |
parse ¶
The move text names, or None; it may be illegal (the gate judges it).
Source code in src/grounded_decision/session/notation.py
describe ¶
The game's description of move, or str(move) if describing fails.
Source code in src/grounded_decision/session/notation.py
notations ¶
For each legal move, a text :meth:parse turns back into exactly that move.
A game's optional move_notation(state, move) hook is used when
present; otherwise the move's description, or the first part of
it, or one of its words, whichever parses back. Every text is
checked by parsing it, so None (no text found) never names
the wrong move.
Source code in src/grounded_decision/session/notation.py
catalogue ¶
What can be played: the :class:Catalogue returned by describe.
Everything comes from the game modules' contract constants
(AGENT_DESCRIPTIONS, AGENT_DETAILS, CHECK_DESCRIPTIONS,
WORLDS, BOARDS …), the same text that play GAME --list,
--explain and docs/reference/agents.md print, so the web front
end and the command line describe every game in the same words.
COUNT_FLAGS
module-attribute
¶
COUNT_FLAGS: dict[str, RuleFlag] = {
"players": RuleFlag(
"players",
"the number of players",
kind="int",
label="Players",
),
"target_length": RuleFlag(
"until_length",
"snake: stop at this length",
kind="int",
label="Stop at length",
),
}
The two counts set by their own flags (--players, --until-length), not rule flags.
Named
dataclass
¶
A name and one sentence about it (a check, a world, a board variant).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The name to send back (in a spec or a check result). |
description |
str
|
One sentence. |
AgentInfo
dataclass
¶
One agent a seat can hold.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The name to put in a spec's |
description |
str
|
One line. |
details |
str
|
A paragraph on how it decides and what it guarantees. |
regime |
Regime | None
|
Its information regime, or null for a baseline. |
gated |
bool
|
Whether it is the game's gated agent (propose, check, act or fall back). |
SizePreset
dataclass
¶
A named board size.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The name a spec's |
width |
int
|
Columns. |
height |
int
|
Rows. |
RuleCondition
dataclass
¶
A rule option's value another option needs (Tetris's level: mode realtime).
Attributes:
| Name | Type | Description |
|---|---|---|
option |
str
|
The |
value |
str
|
Its value, as the command line writes it ( |
RuleInfo
dataclass
¶
RuleInfo(
name: str,
flag: str,
label: str,
kind: str,
choices: tuple[str, ...] = (),
span: Span | None = None,
values: str = "",
default: str = "",
help: str = "",
requires: tuple[RuleCondition, ...] = (),
zero_is_off: bool = False,
)
One rule option a game takes, as a front end offers it (protocol 1.11).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The |
flag |
str
|
The command-line flag without dashes ( |
label |
str
|
A few words naming it in a form. |
kind |
str
|
|
choices |
tuple[str, ...]
|
The values a |
span |
Span | None
|
The whole numbers an |
values |
str
|
What it takes, in a few words ( |
default |
str
|
|
help |
str
|
What it means, in the words of |
requires |
tuple[RuleCondition, ...]
|
Option values it applies under; a front end offers it only then. |
zero_is_off |
bool
|
|
all_of
classmethod
¶
all_of(entry: GameModule) -> tuple[RuleInfo, ...]
Every option of entry's configure a front end can set: the counts,
then the rule flags in --help order (the order a command lists them).
Source code in src/grounded_decision/session/catalogue.py
SizeOffer
dataclass
¶
The sizes a front end offers for a game's board, under one option value (protocol 1.9).
Attributes:
| Name | Type | Description |
|---|---|---|
rule |
SizeRule
|
Every size the board may take, its default and the usual
ones ( |
description |
str
|
The rule in one line ( |
option |
str
|
The rule option (a |
value |
str
|
That option's value ( |
all_of
classmethod
¶
all_of(rule: SizeRule | None) -> tuple[SizeOffer, ...]
The offer without an option, then one per case; empty for a fixed size.
Source code in src/grounded_decision/session/catalogue.py
GameInfo
dataclass
¶
GameInfo(
name: str,
title: str,
players: tuple[str, ...],
agents: tuple[AgentInfo, ...],
default_agents: tuple[str, ...],
default_mode: str,
checks: tuple[Named, ...],
worlds: tuple[Named, ...],
boards: tuple[Named, ...],
starts: tuple[Named, ...],
layouts: tuple[Named, ...],
sizes: tuple[SizePreset, ...],
options: tuple[str, ...],
guardable: bool,
notation: bool,
oracles: tuple[Named, ...] = (),
size_offers: tuple[SizeOffer, ...] = (),
rules: tuple[RuleInfo, ...] = (),
)
One game, as a front end offers it.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The name a spec's |
title |
str
|
A one-line title. |
players |
tuple[str, ...]
|
The default seats' player names, in turn order. |
agents |
tuple[AgentInfo, ...]
|
Every agent, in display order. |
default_agents |
tuple[str, ...]
|
The default line-up, one per seat. |
default_mode |
str
|
|
checks |
tuple[Named, ...]
|
Every check the gated agent runs on a proposal. |
worlds |
tuple[Named, ...]
|
Who may control the randomness (empty: nothing random to control); the first is the default. |
boards |
tuple[Named, ...]
|
Board variants (rule |
starts |
tuple[Named, ...]
|
Opening positions (rule |
layouts |
tuple[Named, ...]
|
Screen layouts (rule |
sizes |
tuple[SizePreset, ...]
|
Named sizes. |
options |
tuple[str, ...]
|
Rule options the game's configuration accepts. |
guardable |
bool
|
Whether a person's moves can be guarded by the gate. |
notation |
bool
|
Whether a person types moves in the game's notation (else the number of a legal move). |
oracles |
tuple[Named, ...]
|
The judges |
size_offers |
tuple[SizeOffer, ...]
|
Every size the board may take, the default, the usual ones and the rule in words (protocol 1.9): the first offer holds unless a later one's option has its value (a Hexxagon donut needs side 4). Empty for a fixed size, which a front end does not offer to change. |
rules |
tuple[RuleInfo, ...]
|
Every option of |
RegimeInfo
dataclass
¶
An information regime, in the words play GAME --explain uses (protocol 1.2).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
Regime
|
The regime, as agents and seats report it. |
label |
str
|
Its name for people ( |
description |
str
|
What an agent in it may know about the future, and so what it can promise. |
Catalogue
dataclass
¶
Catalogue(
games: tuple[GameInfo, ...],
regimes: tuple[RegimeInfo, ...],
)
Every game that can be played.
Attributes:
| Name | Type | Description |
|---|---|---|
games |
tuple[GameInfo, ...]
|
The games, in the command line's order. |
regimes |
tuple[RegimeInfo, ...]
|
Every information regime an agent can declare (protocol 1.2). |
jsonlines ¶
play --view json: one JSON object per line, a projection of the session's frames.
The lines are start, one step per move and end. A step
line is :meth:StepEvent.line of the move's decision and the stats of
the position after it, and :meth:StepEvent.of_frame builds the same
event from a session :class:~.frame.Frame, so the command line's JSON
output equals the session layer's frames in this documented canonical
form (docs/reference/protocol.md; a golden test compares them byte
for byte).
Concepts and the classes that own them:
:class:StartEvent, :class:StepEvent, :class:EndEvent
The three kinds of line, each with payload() and line().
:class:JsonLinesView
The engine observer that writes them (--view json).
StartEvent
dataclass
¶
The first line: the game, its players and agents, and its world.
Attributes:
| Name | Type | Description |
|---|---|---|
game |
str
|
The game's name. |
players |
tuple[str, ...]
|
Player names, in turn order. |
agents |
tuple[str, ...]
|
Agent names, in seat order. |
world |
str | None
|
The game's world, or |
StepEvent
dataclass
¶
StepEvent(
index: int,
decision: MoveDecision,
stats: Mapping[str, Scalar] | None,
)
One move: who played what, the gate's verdict and checks, and the stats after it.
Attributes:
| Name | Type | Description |
|---|---|---|
index |
int
|
The move's index (0 for the first move). |
decision |
MoveDecision
|
The move and how it was chosen. |
stats |
Mapping[str, Scalar] | None
|
The game's stats after the move, or |
of_frame
classmethod
¶
The event of the move that led to frame.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Source code in src/grounded_decision/session/jsonlines.py
EndEvent
dataclass
¶
The last line: moves, overrides, whether the budget cut it off, the outcome.
Attributes:
| Name | Type | Description |
|---|---|---|
summary |
Mapping[str, Any]
|
:meth: |
JsonLinesView ¶
replay ¶
Recorded games: the .gdr replay format (version 2), written and read.
A replay is a whole session's frames, so a front end can play a game back without running Python (the web plan's phase 2). The file is gzip of UTF-8 text, one JSON value per line:
- the header (:class:
ReplayHeader): format and protocol versions, the package version, the game, the :class:~.spec.SessionSpecand seed that reproduce it, the session's fixed info, the frame count, the keyframe interval, the SHA-256 of the canonical frame stream and the outcome; - one record per frame: a keyframe (the whole frame) every
keyframe_intervalframes, starting with frame 0, and in between a delta against the previous frame (:class:FrameDelta), so a reader seeks to any frame by decoding from the keyframe before it.
The reference is docs/reference/replay-format.md; the TypeScript
reader is web/packages/replay. Everything here is pure (bytes in,
bytes out); the command line does the file I/O.
Concepts and the classes that own them:
:class:FrameStream
A session's frames in order, their canonical text and its hash.
:class:JsonPatch
The difference between two JSON values as set operations
(:class:SetOperation) and array splices (:class:SpliceOperation),
and applying it.
:class:FrameDelta
One frame against the previous one: a patch of everything but the
transition, and each transition picture patched against the picture
before it.
:class:ReplayHeader
The first line of a replay.
:class:Replay
A header and its frames: recorded from a spec, encoded to .gdr
bytes, decoded and verified.
:class:ReplayError
Bytes that are not a valid replay, or a replay that fails a check.
FORMAT_VERSION
module-attribute
¶
The .gdr format version this module writes and reads.
DEFAULT_KEYFRAME_INTERVAL
module-attribute
¶
A whole frame every this many frames: seeking applies at most this many frames' changes, and a larger interval saves little (a gallery snake: 40 KB every 32, 35 KB every 256, 34 KB every 1,024).
Same
module-attribute
¶
How a diff compares two values: :func:_equal (fast) or :func:_same (exact).
ReplayError ¶
Bases: ValueError
Not a valid .gdr replay, or a replay that fails verification.
FrameStream
dataclass
¶
FrameStream(frames: tuple[Frame, ...])
A session's frames, in order, and the hash that identifies them.
The canonical stream is each frame's wire JSON as
:func:canonical_json, one per line, each line ending in \n;
its SHA-256 is the replay's content hash. It depends only on the
frames, never on how a file encodes them (keyframe interval, deltas,
compression).
Attributes:
| Name | Type | Description |
|---|---|---|
frames |
tuple[Frame, ...]
|
The frames, frame 0 (the starting position) first. |
SetOperation
dataclass
¶
SetOperation(path: Path, value: JsonValue)
Replace what is at path with value (the empty path replaces the whole value).
Its file form is [path, value].
SpliceOperation
dataclass
¶
SpliceOperation(
path: Path,
start: int,
delete: int,
items: tuple[JsonValue, ...],
)
In the array at path, replace delete items from start with items.
A list that shifts (a snake's body gains a head and loses its tail, a
sparkline's window moves on, a newest-first event list takes a new
event) is two small splices instead of the whole list. Its file form
is [path, start, delete, items] (format 2).
AddOperation
dataclass
¶
Add amount to the whole number at path.
A count that goes up by one every move (moves played, acts) is the
same operation every time, which gzip stores in a few bits, where
setting each new value is new text every move. Its file form is
[path, "+", amount] (format 2).
JsonPatch
dataclass
¶
How one JSON value becomes another: set and splice operations, in order.
A path is a list of object keys and array indices from the root. A set
replaces what is at its path; a splice replaces part of the array at
its path (:class:SpliceOperation). :meth:between writes the
shortest it finds: it descends into objects with the same keys, and
for an array chooses the shortest of its items' own changes (same
length), one splice around the common start and end, two splices for
a list shifted by a few places, or the whole array.
Attributes:
| Name | Type | Description |
|---|---|---|
operations |
tuple[Operation, ...]
|
The operations, applied in order. |
SHIFTS
class-attribute
¶
How far an array may have moved (in places, either way) for :meth:between to splice it.
MATCHED
class-attribute
¶
The most items (old and new together) :meth:between matches one by one.
between
classmethod
¶
The patch from old to new, exact to the JSON text.
The diff compares with == (at C speed), which cannot tell 1,
1.0 and true apart; the result is then proved once, on the
whole value (applied to old, it gives new's exact text), and only
a value where == hid such a difference is compared exactly
(:func:_same) all the way down.
Source code in src/grounded_decision/session/replay.py
apply ¶
value with every operation applied; value itself is never changed.
at
classmethod
¶
value with what is at path replaced by change of it (copy on write).
Source code in src/grounded_decision/session/replay.py
of_wire
classmethod
¶
A patch from its JSON form, checked: [path, value] sets and
[path, start, delete, items] splices.
Source code in src/grounded_decision/session/replay.py
operation_of_wire
staticmethod
¶
operation_of_wire(item: JsonValue) -> Operation
One operation from its JSON form, checked.
Source code in src/grounded_decision/session/replay.py
FrameDelta
dataclass
¶
One frame as a change to the frame before it.
patch turns the previous frame (without its transition, and with
its index one higher, as frames are consecutive) into this one
(without its transition): the index is never stored. The transition's pictures are
usually small changes of the previous position, so each picture's
board is a patch of the board before it: the first against the
previous frame's board, each next one against the picture before.
Attributes:
| Name | Type | Description |
|---|---|---|
patch |
JsonPatch
|
Everything but the transition. |
transition |
tuple[tuple[float, JsonPatch], ...]
|
Each transition picture: its weight and its board's patch. |
Channel
dataclass
¶
Every change to one part of the frame, in order (one line of the file).
A part is named by the leading object keys of the paths it changes, at
most two (board.cells, decision.metrics, arrows): the same
kind of change lies together, which compresses far better than each
frame's changes side by side. Its line is {"c": keys, "o": changes},
each change [gap, operation]: frames since the channel's last
change, then the operation with its path after keys.
Attributes:
| Name | Type | Description |
|---|---|---|
keys |
tuple[str, ...]
|
The leading keys every path here starts with. |
changes |
tuple[tuple[int, Operation], ...]
|
|
ReplayBody
dataclass
¶
ReplayBody(
keyframes: tuple[JsonObject, ...],
channels: tuple[Channel, ...],
transitions: tuple[
tuple[int, tuple[tuple[float, JsonPatch], ...]], ...
],
)
A replay's frames as the file stores them (format 2), after the header.
Lines, in this order: every keyframe ({"k": frame}, frame 0 and each
multiple of the keyframe interval), one line per :class:Channel, and
the transitions ({"t": [[gap, pictures], ...]}, each picture
[weight, operations] against the board before it). Every frame
that is not a keyframe is the frame before it, with the next index and
no transition, changed by its operations in every channel and given
its pictures.
The writer never changes, in one frame, both a path and a path inside
it (:meth:JsonPatch.between either sets a value or changes its
parts), so a frame's operations from different channels can be applied
in any order; within a channel they keep theirs.
Attributes:
| Name | Type | Description |
|---|---|---|
keyframes |
tuple[JsonObject, ...]
|
The keyframes, in frame order. |
channels |
tuple[Channel, ...]
|
Every channel, by its keys. |
transitions |
tuple[tuple[int, tuple[tuple[float, JsonPatch], ...]], ...]
|
|
frames ¶
The count frames, in order, as wire JSON.
Source code in src/grounded_decision/session/replay.py
of_lines
classmethod
¶
of_lines(lines: Sequence[str], first: int) -> ReplayBody
The body from its lines; first is the first line's number in the file.
Source code in src/grounded_decision/session/replay.py
ReplayHeader
dataclass
¶
ReplayHeader(
format_version: int,
protocol_version: str,
package_version: str,
game: str,
spec: SessionSpec,
seed: int,
session: SessionInfo,
frame_count: int,
keyframe_interval: int,
content_hash: str,
outcome: Outcome,
)
The first line of a .gdr file: what was played and how to check it.
Attributes:
| Name | Type | Description |
|---|---|---|
format_version |
int
|
The file format's version (:data: |
protocol_version |
str
|
The protocol version of the frames. |
package_version |
str
|
The grounded-decision version that recorded it. |
game |
str
|
The game's name. |
spec |
SessionSpec
|
Everything that fixes the match; recording it again gives the same frames. |
seed |
int
|
The seed ( |
session |
SessionInfo
|
What stays the same for the session: seats, world, the panel's fixed rows. |
frame_count |
int
|
Number of frames (moves played + 1). |
keyframe_interval |
int
|
A whole frame every this many frames. |
content_hash |
str
|
|
outcome |
Outcome
|
The last frame's outcome. |
of_wire
classmethod
¶
of_wire(value: JsonValue) -> ReplayHeader
A header from its JSON line, validated; unknown fields are ignored.
Raises:
| Type | Description |
|---|---|
ReplayError
|
Not a |
Source code in src/grounded_decision/session/replay.py
Replay
dataclass
¶
Replay(header: ReplayHeader, frames: tuple[Frame, ...])
A recorded session: its header and every frame.
Make one with :meth:record (play a spec to the end) or
:meth:decode (read .gdr bytes); :meth:encode writes the bytes.
Attributes:
| Name | Type | Description |
|---|---|---|
header |
ReplayHeader
|
What was played, and the hash and outcome of its frames. |
frames |
tuple[Frame, ...]
|
Every frame, frame 0 first. |
record
classmethod
¶
record(
spec: SessionSpec,
*,
keyframe_interval: int = DEFAULT_KEYFRAME_INTERVAL,
package_version: str | None = None,
) -> Replay
Play spec to its end (or its move budget) and keep every frame.
Raises:
| Type | Description |
|---|---|
ReplayError
|
A seat is a person (a replay records agents only). |
Source code in src/grounded_decision/session/replay.py
lines ¶
The uncompressed text, line by line: the header, then the body (:class:ReplayBody).
Source code in src/grounded_decision/session/replay.py
body ¶
body() -> ReplayBody
The frames as the file stores them: keyframes, channels and transitions.
encode ¶
decode
classmethod
¶
decode(data: bytes) -> Replay
A replay from .gdr bytes.
Raises:
| Type | Description |
|---|---|
ReplayError
|
Not gzip, not a replay, an unknown format version, a malformed record, or a frame that does not fit the protocol. |
Source code in src/grounded_decision/session/replay.py
json_line
staticmethod
¶
json_line(line: str, number: int) -> JsonValue
Line number of a file, parsed.
Source code in src/grounded_decision/session/replay.py
problems ¶
What is wrong with this replay (empty when sound): its frames against
its header (:meth:hash_problems), then every arrow that breaks
:class:~grounded_decision.session.arrows.ArrowRule.
Source code in src/grounded_decision/session/replay.py
hash_problems ¶
What is wrong with this replay's frames against its header (empty when sound).
Source code in src/grounded_decision/session/replay.py
byte_problems
classmethod
¶
What is wrong with .gdr bytes against their own header (empty when
sound), without building the frames' protocol objects: the frames the
body decodes to must hash to the header's content hash, and the last
one's outcome must be the header's. A quick check of a file just
written; :meth:decode then :meth:problems checks the frames too.
Raises:
| Type | Description |
|---|---|
ReplayError
|
The bytes are not a readable replay. |
Source code in src/grounded_decision/session/replay.py
arrow_reports ¶
Every move with its arrows, checked against the position before it.
reproduce ¶
reproduce() -> Replay
The header's spec recorded again, with this replay's keyframe interval.
Source code in src/grounded_decision/session/replay.py
reproduced ¶
Whether recording the header's spec again gives exactly these frames (by hash).
BytePart
dataclass
¶
One part of a replay's records and its bytes.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
|
text |
int
|
Its uncompressed text, in bytes. |
compressed |
int
|
That text gzipped on its own, in bytes (the file is gzipped whole, so these add up to a little more than it). |
ByteReport
dataclass
¶
ByteReport(
size: int, text: int, parts: tuple[BytePart, ...]
)
Where a replay's bytes go, part by part (replay-info --bytes).
Attributes:
| Name | Type | Description |
|---|---|---|
size |
int
|
The replay's |
text |
int
|
Its uncompressed text's bytes. |
parts |
tuple[BytePart, ...]
|
Every part, the most compressed bytes first. |
lines ¶
The report as text: one line per part.
Source code in src/grounded_decision/session/replay.py
gallery ¶
The curated replays: the site's gallery and the replay package's golden files.
make replays (python -m grounded_decision.session --replays)
records them. They are committed, because the site is built without Python and
the TypeScript tests read them; a test records every one again and fails
when a committed file no longer matches (a rule or agent changed), so the
set is always current.
Concepts and the classes that own them:
:class:RecordCommand
The grounded-decision record command line that records a spec:
shown on the site ("run it yourself") and parsed back by a test.
:class:CuratedReplay
One replay to keep: a name, a title, a spec and a keyframe interval.
:class:ReplaySet
A directory of curated replays and the files it holds: the .gdr
files, and a manifest (the gallery) or each replay's frames as JSON
(the golden files the TypeScript decoder is compared with).
GALLERY
module-attribute
¶
GALLERY = ReplaySet(
GALLERY_DIRECTORY,
(
CuratedReplay(
"tictactoe",
"Tic-tac-toe: the rule of thumb against the gated player",
"The gate checks every proposal against the exact solver; the game is a draw.",
SessionSpec(
"tictactoe",
agents=("rule_of_thumb", "grounded"),
seed=1,
),
),
CuratedReplay(
"snake",
"Snake: the certified snake heads for each apple",
"Apples land at random; every move is checked safe, and the snake grows.",
SessionSpec(
"snake",
world="random",
size="10x10",
seed=1,
),
),
CuratedReplay(
"snake-adversarial",
"Snake against partly adversarial apples",
"Three apples in ten land where they hurt most. The snake's safety proof holds whatever the apples do; only its speed bets on random apples.",
SessionSpec(
"snake",
world="adversarial:0.3",
size="10x10",
seed=1,
),
),
CuratedReplay(
"checkers",
"Checkers: the gated player against the search",
"Captures, kings and the gate's checks on every move.",
SessionSpec("checkers", seed=1),
),
CuratedReplay(
"chinese-checkers-6",
"Chinese checkers for six",
"Six arms of the star race for the opposite corner; placings at the end.",
SessionSpec(
"chinese_checkers", players=6, seed=1
),
),
CuratedReplay(
"go-9x9",
"Go on 9 x 9",
"Captures and territory on the small board, to two passes.",
SessionSpec("go", size="9x9", seed=1),
),
CuratedReplay(
"tetris-sz",
"Tetris against S and Z pieces only",
"The worst pieces there are; every placement checked against the preview.",
SessionSpec("tetris", world="sz", seed=1),
),
CuratedReplay(
"hexxagon-ring",
"Hexxagon: the ring trap",
"The ring opening sets a trap on move 1; pieces flip on every capture.",
SessionSpec(
"hexxagon", rules={"start": "ring"}, seed=1
),
),
CuratedReplay(
"minesweeper-expert",
"Minesweeper, expert board",
"30 x 16 with 99 mines: proved-safe clicks first, the least risky guess otherwise.",
SessionSpec(
"minesweeper", size="expert", seed=1
),
),
CuratedReplay(
"2048-evil",
"2048 in the evil world",
"Every new tile lands where it hurts most.",
SessionSpec("2048", world="evil", seed=1),
),
CuratedReplay(
"connect4-greedy-vs-gated",
"Connect Four: greedy against the same player, gated",
"Ungated, O loses in 7 moves. Gated, the gate turns down three moves that would lose, and O wins in 26.",
SessionSpec(
"connect4",
agents=("greedy", "gated"),
seed=15,
),
),
CuratedReplay(
"breakout-adversarial-spin",
"Breakout against adversarial spin",
"The spin works against the paddle. The gate keeps each bounce spin-proof while it can, and says when it cannot, a flight before a ball is lost.",
SessionSpec(
"breakout",
world="adversarial",
rules={
"epsilon": 32,
"paddle_speed": 12,
"ball_speed": 4,
},
seed=1,
),
),
),
)
The site's gallery: every game, with interesting worlds, sizes and openings.
GOLDEN
module-attribute
¶
GOLDEN = ReplaySet(
GOLDEN_DIRECTORY,
tuple(
CuratedReplay(
name,
name,
"A golden replay for the decoder's tests.",
spec,
4,
)
for name, spec in (
(
"tictactoe",
SessionSpec(
"tictactoe",
agents=("rule_of_thumb", "grounded"),
seed=1,
),
),
(
"snake",
SessionSpec(
"snake",
size="6x6",
seed=2,
max_steps=12,
),
),
(
"checkers",
SessionSpec(
"checkers", seed=0, max_steps=4
),
),
(
"chinese-checkers",
SessionSpec(
"chinese_checkers",
players=3,
seed=0,
max_steps=3,
),
),
(
"go",
SessionSpec(
"go", size="9x9", seed=0, max_steps=3
),
),
(
"tetris",
SessionSpec(
"tetris",
world="adversarial",
seed=0,
max_steps=2,
),
),
(
"hexxagon",
SessionSpec(
"hexxagon", seed=1, max_steps=3
),
),
(
"minesweeper",
SessionSpec(
"minesweeper", seed=6, max_steps=8
),
),
(
"2048",
SessionSpec(
"2048",
size="3x3",
world="evil",
seed=3,
max_steps=5,
),
),
(
"connect4",
SessionSpec(
"connect4", seed=0, max_steps=8
),
),
(
"breakout",
SessionSpec(
"breakout",
rules={"epsilon": 8},
seed=0,
max_steps=12,
),
),
)
),
golden=True,
)
Small replays of every game (a keyframe every 4 frames) and their frames as JSON.
RecordCommand
dataclass
¶
RecordCommand(
spec: SessionSpec, keyframe_interval: int | None = None
)
The grounded-decision record arguments that record a spec.
Attributes:
| Name | Type | Description |
|---|---|---|
spec |
SessionSpec
|
What is recorded (no person may sit in it). |
keyframe_interval |
int | None
|
|
arguments ¶
The argument list after grounded-decision.
Raises:
| Type | Description |
|---|---|
ValueError
|
A rule option |
Source code in src/grounded_decision/session/gallery.py
text ¶
CuratedReplay
dataclass
¶
CuratedReplay(
name: str,
title: str,
description: str,
spec: SessionSpec,
keyframe_interval: int = DEFAULT_KEYFRAME_INTERVAL,
)
One committed replay.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The file's name without |
title |
str
|
A short title for the gallery. |
description |
str
|
What to watch for, in a sentence. |
spec |
SessionSpec
|
What is recorded. |
keyframe_interval |
int
|
A whole frame every this many frames. |
entry ¶
entry(replay: Replay) -> JsonObject
This replay's line in a manifest (the gallery's or the ambient library's).
Source code in src/grounded_decision/session/gallery.py
ReplaySet
dataclass
¶
ReplaySet(
directory: str,
replays: tuple[CuratedReplay, ...],
golden: bool = False,
)
A directory of curated replays.
Attributes:
| Name | Type | Description |
|---|---|---|
directory |
str
|
Where the files go, relative to the repository root. |
replays |
tuple[CuratedReplay, ...]
|
The replays. |
golden |
bool
|
Also write each replay's frames as JSON ( |
files ¶
Every file of the set, as {repository-relative path: bytes}.
versions maps a replay's name to the package version to put in
its header (the committed one, when checking the set is current).
Source code in src/grounded_decision/session/gallery.py
payload
staticmethod
¶
A .gdr file's text (compressed bytes differ between zlib builds; text does not).
wire ¶
The protocol's wire form: dataclasses to JSON values, back, and to JSON Schema.
The protocol's messages are frozen dataclasses (:mod:.frame,
:mod:.protocol); this module is the one place that knows how their
Python types look in JSON, so encoding, decoding and the published
schema cannot disagree.
Concepts and the classes that own them:
:class:WireEncoder
Protocol values to JSON values, with each dataclass's wire fields
looked up once (:func:encode is the one-off form).
:class:WireType
One Python type annotation, as the wire sees it: how to encode a
value of it, decode (and validate) a JSON value into it, and describe
it in JSON Schema. Built from an annotation by :meth:WireType.of.
:class:SchemaBuilder
Collects the $defs of every dataclass and enumeration reachable
from the messages, in a stable order.
:class:FieldDocs
Field descriptions read from a dataclass's Attributes: docstring
section, so the schema (and the TypeScript generated from it) carries
the same documentation as the Python.
:class:WireError
A JSON value that does not fit its type, with the path to the problem.
The supported types are what the protocol needs and no more: str,
int, float, bool, None, unions of those and None,
Literal strings, string enumerations, tuple[X, ...] (an array),
Mapping[str, X] (an object) and frozen dataclasses. Anything else is
a programming error and fails when the schema is built, which a test does
for every message.
JsonValue
module-attribute
¶
A value json.loads can return.
WIRE_EXCLUDE
module-attribute
¶
Field metadata key: a field derived at construction, never sent.
RecordEncoder
module-attribute
¶
Encodes one dataclass value, given the encoder for its nested values.
WireError ¶
WireEncoder
dataclass
¶
Encodes protocol values as JSON values (what :func:encode does), planned per class.
Each dataclass gets an encoding function made once from its fields'
wire types (:meth:record_encoder, cached per class: a pure function
of the type): a field whose values are already JSON (a scalar, a
string enumeration, a union of those) is copied as it is, a tuple or
mapping of such values is copied in one call, and only the rest is
encoded recursively. The function is one dictionary display, compiled
the way :mod:dataclasses compiles __init__. A 20 x 20 snake
frame holds about sixty dataclasses and four hundred cell kinds, so
this is a large part of what the browser's engine pays per move.
encode ¶
encode(value: object) -> JsonValue
A protocol value (dataclass, enum, tuple, mapping, scalar) as a JSON value.
Source code in src/grounded_decision/session/wire.py
record_encoder
cached
staticmethod
¶
record_encoder(record: type[Any]) -> RecordEncoder
The function that encodes a value of the dataclass record.
Source code in src/grounded_decision/session/wire.py
FieldDocs
dataclass
¶
A dataclass's summary and per-field descriptions, from its docstring.
The summary is the docstring's first paragraph; field descriptions
come from a Google-style Attributes: section (name: text,
continued on more-indented lines).
WireType
dataclass
¶
WireType(
kind: str,
python: type[Any] | None = None,
items: tuple[WireType, ...] = (),
literals: tuple[str, ...] = (),
)
One Python type annotation, as the protocol puts it on the wire.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
|
python |
type[Any] | None
|
The primitive's or enumeration's or dataclass's class. |
items |
tuple[WireType, ...]
|
Element type ( |
literals |
tuple[str, ...]
|
The allowed strings ( |
discriminator
property
¶
A single string literal (a message's type): always sent, always required.
of
classmethod
¶
of(annotation: object) -> WireType
The wire type of an annotation (already resolved, not a string).
Raises:
| Type | Description |
|---|---|
TypeError
|
A type the protocol does not support. |
Source code in src/grounded_decision/session/wire.py
hints
cached
staticmethod
¶
A dataclass's resolved field annotations (resolved once per class).
plan
cached
staticmethod
¶
plan(
record: type[Any],
) -> tuple[tuple[str, bool, WireType], ...]
How to decode a record, worked out once per class: each wire field's name, whether a message must carry it, and its type.
Source code in src/grounded_decision/session/wire.py
schema ¶
schema(builder: SchemaBuilder) -> JsonObject
This type in JSON Schema (draft 2020-12); records and enums go to $defs.
Source code in src/grounded_decision/session/wire.py
decode ¶
decode(value: JsonValue, path: str = '') -> Any
A JSON value as this type, checked all the way down.
Raises:
| Type | Description |
|---|---|
WireError
|
The value does not fit (wrong type, unknown field, missing field, unknown enumeration value …). |
Source code in src/grounded_decision/session/wire.py
SchemaBuilder
dataclass
¶
SchemaBuilder(
definitions: dict[str, JsonObject] = dict[
str, JsonObject
](),
_classes: dict[str, type[Any]] = dict[str, type[Any]](),
)
Builds the $defs of every dataclass and enumeration a schema refers to.
Definitions are named by their class name (which must be unique) and added in the order they are first reached, so the schema is stable.
reference ¶
{"$ref": "#/$defs/Name"}, defining Name the first time.
Source code in src/grounded_decision/session/wire.py
wire_fields ¶
The fields of a protocol dataclass that travel on the wire, in order.
Source code in src/grounded_decision/session/wire.py
is_required ¶
Whether a field has no default (a sender must always give it).
encode ¶
encode(value: object) -> JsonValue
A protocol value (dataclass, enum, tuple, mapping, scalar) as a JSON value.
compatibility ¶
Semantic versioning for the protocol: what a schema change breaks.
:class:SchemaChanges compares the recorded schema of the last released
protocol version (protocol.baseline.schema.json) with the schema the
code generates now and sorts every difference into breaking (an
existing front end or engine could fail) or additive (old peers keep
working if, as the protocol requires, readers ignore fields they do not
know). :meth:SchemaChanges.problems then says whether
:data:~grounded_decision.session.protocol.PROTOCOL_VERSION was bumped
enough: a breaking change needs a new major version, an additive one a
new minor version. Descriptions never count.
The rules, per $defs entry (docs/reference/protocol.md):
- removing a type, a property, an enumeration value or a union member is breaking; changing a property's type is breaking;
- adding a type, an optional property, an enumeration value or a union member is additive;
- a newly required property is breaking in a request (old front ends do not send it) and additive in a reply (old front ends ignore it); a property that stops being required is the reverse.
Version
dataclass
¶
A semantic version.
Attributes:
| Name | Type | Description |
|---|---|---|
major |
int
|
Incompatible changes. |
minor |
int
|
Compatible additions. |
patch |
int
|
Documentation and fixes. |
parse
classmethod
¶
parse(text: str) -> Version
Raises:
ValueError: Not major.minor.patch with non-negative integers.
Source code in src/grounded_decision/session/compatibility.py
SchemaChanges
dataclass
¶
Every difference between two protocol schemas, sorted by what it breaks.
Attributes:
| Name | Type | Description |
|---|---|---|
breaking |
tuple[str, ...]
|
Changes an existing peer may not survive. |
additive |
tuple[str, ...]
|
Changes old peers can ignore. |
problems ¶
Why new_version is not a correct semver bump for these changes ([] if it is).
Source code in src/grounded_decision/session/compatibility.py
tokens ¶
Design tokens: the terminal's colours and glyphs, for every other front end.
The palette and glyphs of :mod:~grounded_decision.examples.games.views
are the one source (design decision D22). :class:DesignTokens turns them
into a JSON document (make protocol writes it to
web/packages/protocol/generated/tokens.json): a colour and a glyph for
every :class:~grounded_decision.examples.games.views.CellKind and
:class:~grounded_decision.examples.games.views.Overlay, in a dark theme
(the terminal's own colours) and a light theme derived from it by two
rules:
- a foreground (a piece, a mark, a digit) is darkened, by mixing in
black, until it has at least :data:
MIN_CONTRAST(WCAG's 3:1 for graphical objects) against the light board; - a dark background (a highlight, a covered tile) is lightened by mixing
in :data:
LIGHT_MIXof white, so highlights stay visible but soft; mid-tone materials (wood, checker squares, 2048 tiles) are kept.
Concepts and the classes that own them:
:class:Colour
An sRGB colour: hex text, luminance, contrast, mixing.
:class:Theme
One theme's colours.
:class:DesignTokens
Both themes and the glyphs, as one JSON document.
MIN_CONTRAST
module-attribute
¶
Least contrast of a foreground against the light board (WCAG 2.2, non-text).
LIGHT_MIX
module-attribute
¶
Share of white mixed into a dark background for the light theme.
DARK_BACKGROUND
module-attribute
¶
Backgrounds with less relative luminance than this are lightened.
Colour
dataclass
¶
An sRGB colour with 8-bit channels.
Attributes:
| Name | Type | Description |
|---|---|---|
red |
int
|
0-255. |
green |
int
|
0-255. |
blue |
int
|
0-255. |
mix ¶
This colour with share (0-1) of other mixed in.
Source code in src/grounded_decision/session/tokens.py
readable_on ¶
readable_on(
background: Colour, ratio: float = MIN_CONTRAST
) -> Colour
This colour, darkened by steps of 5% black until it reaches ratio on background.
Source code in src/grounded_decision/session/tokens.py
Theme
dataclass
¶
One theme: colours for every cell kind, overlay, seat and panel element.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
|
light |
bool
|
Whether foregrounds and backgrounds are adapted to a light board. |
registry ¶
The registered games as objects: loading, configuring and seating one.
Every way of starting a match (grounded-decision play, the session
layer in :mod:grounded_decision.session that the command line and the
web front end share, compare and audit) goes through
:class:GameModule, so a size, a world, a rule option or a line-up is
accepted or refused with the same words everywhere.
Concepts and the classes that own them:
:class:GameModule
One game module of :data:~grounded_decision.examples.games.GAME_MODULES
and what its module contract offers (see
:mod:~grounded_decision.examples.games.play): its agents, worlds,
variants, named sizes, checks, and how to build the game and its seats.
:class:GameEntry
A registered name and whether its module can be loaded.
:class:PlayError
A request the game cannot honour (unknown game, agent, size …); the
command line exits with status 2.
DEFAULT_MAX_STEPS
module-attribute
¶
The move budget of a match when none is given (--max-steps).
VARIANT_OPTIONS
module-attribute
¶
VARIANT_OPTIONS: tuple[tuple[str, str], ...] = (
("BOARDS", "--board"),
("STARTS", "--start"),
("LAYOUTS", "--layout"),
)
Module dictionaries of named variants and the option that picks one.
PlayError ¶
Bases: Exception
A problem with the request (unknown game, agent, size …); exit status 2.
HumanProposer ¶
Bases: Protocol
A person's seat as an agent: the terminal prompt or the session's submitted moves.
certify is set when the seat is guarded: whether the gate would
pass a move, so the seat can say which moves are safe before the
person picks one.
GameEntry
dataclass
¶
One registered game, and whether it can be loaded.
GameModule
dataclass
¶
A registered game module and everything its module contract offers.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The command-line name ( |
module |
ModuleType
|
The loaded module. |
factories
property
¶
Named agent factories, in display order.
check_names
property
¶
Every check the game's gate can run (CHECK_DESCRIPTIONS): one panel row each.
note_rows
property
¶
Panel rows for notes (PANEL_NOTE_ROWS), or None for the default.
worlds
property
¶
The game's world modes (WORLDS: mode -> description), or {}.
size_presets
property
¶
The game's named sizes (SIZE_PRESETS: name -> (width, height)), or {}.
sizes
property
¶
The sizes the board may take (SIZES), or None for a fixed size.
configure_options
property
¶
The keyword options of configure beyond the size (world, mines …).
option_rules
property
¶
What the game takes for each rule option (OPTIONS: keyword -> rule), or {}.
option_defaults
property
¶
configure's default for each keyword option (None: it depends).
find
staticmethod
¶
find(name: str) -> GameEntry
Load a registered game module, or explain why it is not available.
Source code in src/grounded_decision/examples/games/registry.py
load
classmethod
¶
load(name: str) -> GameModule
Raises: PlayError: The game is unknown or not available.
Source code in src/grounded_decision/examples/games/registry.py
of
classmethod
¶
of(module: ModuleType) -> GameModule
The registered game of an already loaded module.
Source code in src/grounded_decision/examples/games/registry.py
available
classmethod
¶
available() -> tuple[GameModule, ...]
Every registered game whose module loads, in registry order.
Source code in src/grounded_decision/examples/games/registry.py
constant ¶
A module-contract constant (GATED_AGENT, TITLE …), or default.
descriptions ¶
A {name: text} constant (CHECK_DESCRIPTIONS, WORLDS …), or {}.
check_names_for ¶
The checks a configured game's gate runs: its check_names if it declares
them (Tetris: fewer in placement mode than in real time), else :attr:check_names.
Source code in src/grounded_decision/examples/games/registry.py
variants ¶
parse_size ¶
--size: WxH or one of the game's named sizes.
Raises:
| Type | Description |
|---|---|
PlayError
|
Not |
Source code in src/grounded_decision/examples/games/registry.py
validate_world ¶
Parse --world for this game.
Raises:
| Type | Description |
|---|---|
PlayError
|
The game has no worlds, or the mode is unknown or malformed. |
Source code in src/grounded_decision/examples/games/registry.py
check_world ¶
The game's world, checked against the agents' regimes; None without worlds.
Raises:
| Type | Description |
|---|---|
PlayError
|
A clairvoyant agent in a world that depends on the agent's moves. |
Source code in src/grounded_decision/examples/games/registry.py
default_game ¶
The game at its default size and rules.
Source code in src/grounded_decision/examples/games/registry.py
make_game ¶
make_game(
size: tuple[int, int] | None = None,
until_length: int | None = None,
*,
players: int | None = None,
rules: Mapping[str, Any] | None = None,
world: str | None = None,
) -> TurnGame[Any, Any]
The game at its default size, or configured.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
tuple[int, int] | None
|
|
None
|
until_length
|
int | None
|
Snake's length goal ( |
None
|
players
|
int | None
|
Number of players ( |
None
|
rules
|
Mapping[str, Any] | None
|
Extra rule options passed to |
None
|
world
|
str | None
|
|
None
|
Raises:
| Type | Description |
|---|---|
PlayError
|
The size, length goal, number of players, world or a rule option is not supported. |
Source code in src/grounded_decision/examples/games/registry.py
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 | |
default_line_up ¶
The default agent names for players seats.
The module's default_agents(players) if it has one, else
DEFAULT_AGENTS (repeated round the table if the game has more seats).
Source code in src/grounded_decision/examples/games/registry.py
seat_index
staticmethod
¶
The seat --you-play names (a player's name or number), else the first.
Raises:
| Type | Description |
|---|---|
PlayError
|
No such player. |
Source code in src/grounded_decision/examples/games/registry.py
seat_names ¶
seat_names(
game: TurnGame[Any, Any],
agents: Sequence[str] | None,
*,
you: bool = False,
you_play: str | None = None,
) -> tuple[str, ...]
One agent name per seat (:data:HUMAN for a person).
agents empty or None means the default line-up; a single
name fills every seat, except you, which takes one seat
(you_play) beside the default line-up. you puts a person in
that seat whatever the line-up.
Raises:
| Type | Description |
|---|---|
PlayError
|
The wrong number of names (unknown names are refused
by :meth: |
Source code in src/grounded_decision/examples/games/registry.py
seat_agents ¶
seat_agents(
game: TurnGame[Any, Any],
names: Sequence[str],
*,
seed: int,
guarded: bool = False,
human: Callable[[], HumanProposer],
) -> list[Agent[Any, Any]]
One fresh agent per seat; seat k gets seed + k.
A :data:HUMAN seat is human(), behind the game's gate when
guarded (:meth:guard).
Raises:
| Type | Description |
|---|---|
PlayError
|
An unknown agent name, or |
Source code in src/grounded_decision/examples/games/registry.py
guard ¶
guard(
game: TurnGame[Any, Any],
proposer: HumanProposer,
guarded: bool,
) -> Agent[Any, Any]
A person's seat, behind the game's gate (guarded(proposer)) if asked.
Any move the person picks is then only a proposal: the gate checks
it and plays its certified fallback if a check fails. The
proposer's certify is set to the gate's checks.
Raises:
| Type | Description |
|---|---|
PlayError
|
|
Source code in src/grounded_decision/examples/games/registry.py
parse_dimensions ¶
parse_dimensions(
text: str,
presets: Mapping[str, tuple[int, int]] | None = None,
) -> tuple[int, int]
"20x20" → (20, 20), or a named size from presets.
Raises:
| Type | Description |
|---|---|
PlayError
|
Not |
Source code in src/grounded_decision/examples/games/registry.py
make_agent ¶
Call an agent factory, passing seed= if it accepts one.