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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/ahbg-presentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: ahbg-presentation

on:
pull_request:
paths:
- "ahbg/presentation/**"
- ".github/workflows/ahbg-presentation.yml"
push:
branches: [main]
paths:
- "ahbg/presentation/**"
- ".github/workflows/ahbg-presentation.yml"

permissions:
contents: read

jobs:
presentation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Python presentation contract
run: python -m unittest discover -s ahbg/presentation/tests -p 'test*.py'
- name: Browser JavaScript syntax
run: node --check ahbg/presentation/board.js
95 changes: 95 additions & 0 deletions ahbg/presentation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# AHBG presentation

Grok-owned graphics surface. It renders validated presentation snapshots and
already-resolved motion traces. It does **not** define game mechanics or derive
UCNS geometry.

## Boundary

- Included: Seed-of-Life circle rendering, UCNS-supplied tile centerpoints,
unit markers, selection/inspection, public feed text, and visual traces of
already-resolved moves.
- Excluded: legal movement, adjacency authority, turns, War resolution,
construction, permissions, RNG, DM state, private prompt state, or agent policy.
- Snapshot standing is `ahbg.presentation.snapshot` / `not-mechanics`.
- Every snapshot carries an exact `geometry_source` identity plus each tile's
UCNS-derived `x`, `y`, and `source_slot`. The renderer only scales those
coordinates for SVG display; it never rebuilds centers from local axial rules.
- The current sample pins `The-Interdependency/ucns@1975fe70cf4e0826a8020c2da3047569e277af64`,
the canonical `libs/ucns/` identity in the stack manifest when this surface was
authored. Its `mobius_seed` module remains an explicit nonselecting UCNS
candidate; presentation consumption does not promote its standing.
- `project.py` accepts already-sanitized observation data, source-backed display
coordinates, and resolved move events. It never decides whether those moves
were legal and copies only declared browser-facing fields.
- `geometry.py` scales supplied source coordinates into display pixels only.

## Usage guidance

Validate the package:

```bash
python -m unittest discover -s ahbg/presentation/tests -p 'test*.py'
node --check ahbg/presentation/board.js
```

Serve the board locally:

```bash
cd ahbg/presentation
python -m http.server 8765 --bind 127.0.0.1
# open http://127.0.0.1:8765/board.html
```

Project a sanitized observation only after the owning adapter has attached the
UCNS-derived centers and exact source identity:

```python
from ahbg.presentation.project import snapshot_from_observation

geometry_source = {
"repository": "The-Interdependency/ucns",
"commit": "1975fe70cf4e0826a8020c2da3047569e277af64",
"module": "src/ucns/mobius_seed.py",
"schema_id": "ucns.mobius-seed-of-life",
"schema_version": "0.1.0",
"projection_id": "seed-of-life-seven-equal-circles",
"selection_effect": "none",
}
observation = {
"turn": 1,
"geometry_source": geometry_source,
"tiles": [
{"tile_id": "CENTER", "ucns_slot": "CENTER", "x": 0.0, "y": 0.0},
{"tile_id": "RING_0", "ucns_slot": "RING_0", "x": 1.0, "y": 0.0},
],
"units": [{"unit_id": "A0", "tile_id": "RING_0", "label": "A0"}],
}
move = {
"kind": "move",
"data": {"unit_id": "A0", "from_tile_id": "CENTER", "to_tile_id": "RING_0"},
}
snapshot = snapshot_from_observation(
observation,
plane_id="plane-0",
move_events=[move],
feed=[{"turn": 1, "text": "public display text only"}],
)
```

The snapshot validator is allowlist-based. Unknown root, geometry, tile, unit,
feed, or motion fields fail closed. The projector drops undeclared observation,
move-event, and feed metadata before validation, preventing a visual snapshot
from becoming a transport for private/internal fields.

The browser exposes tile inspection to pointer and keyboard input, permits
interactive descendants under an SVG `group` role, scales unit-marker spread by
occupant count, and renders the selection ring above the unit layer.

## hmmm

- whether later Flower-of-Life rings belong on this presentation surface;
- the exact live engine-to-observation adapter that attaches UCNS positions is
owned by the eventual engine integration, not by this graphics package;
- construction animation remains unavailable until an owning mechanics layer
emits an already-resolved construction event contract.
36 changes: 36 additions & 0 deletions ahbg/presentation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# === MODULE_BUILD ===
# id: ahbg_presentation_public_boundary
# module_name: presentation
# module_kind: adapter
# summary: exposes the validated presentation snapshot and projection boundary without exporting mechanics
# owner: AHBG presentation
# public_surface: PresentationSnapshotError, load_snapshot, snapshot_from_observation, validate_snapshot
# internal_surface: none
# auth_boundary: none
# storage_boundary: none
# network_boundary: none
# user_data_boundary: none
# admin_only: false
# tests: ahbg/presentation/tests/test_presentation.py
# rollout: import explicitly from ahbg.presentation
# rollback: remove package exports with presentation package
# requires: ahbg_presentation_snapshot_contract, ahbg_presentation_observation_projector
# since: 2026-08-31
# unresolved: none
# === END MODULE_BUILD ===

"""Presentation-only AHBG graphics boundary.

Usage guidance: import only validated presentation helpers from this package;
game mechanics remain outside this namespace.
"""

from .project import snapshot_from_observation
from .snapshot import PresentationSnapshotError, load_snapshot, validate_snapshot

__all__ = [
"PresentationSnapshotError",
"load_snapshot",
"snapshot_from_observation",
"validate_snapshot",
]
45 changes: 45 additions & 0 deletions ahbg/presentation/board.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
:root {
--ink: #1b1a17;
--paper: #f4efe4;
--tile: #e6dcc8;
--tile-stroke: #6b5d44;
--selected: #c45c26;
--unit: #1f4b99;
}

html,
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font: 16px/1.4 "Iowan Old Style", "Palatino Linotype", Palatino, serif;
}

main {
display: grid;
grid-template-columns: minmax(0, 1fr) 18rem;
min-height: 100vh;
}

.board-wrap { display: flex; flex-direction: column; padding: 1.5rem; }
h1, h2 { font-weight: 600; letter-spacing: 0.04em; margin: 0 0 0.75rem; }
.note { margin: 0 0 1rem; font-size: 0.95rem; }
svg { width: min(100%, 36rem); height: auto; align-self: center; }
.seed-circle { fill: none; stroke: var(--tile-stroke); stroke-width: 1.5; pointer-events: none; }
.tile-point { fill: var(--tile-stroke); stroke: var(--paper); stroke-width: 1.5; pointer-events: none; }
.tile-point.selected { fill: var(--selected); stroke: var(--selected); }
.motion-path { fill: none; stroke: var(--selected); stroke-width: 2.5; stroke-dasharray: 6 4; pointer-events: none; }
.tile-hit { fill: transparent; cursor: pointer; stroke: transparent; stroke-width: 3; }
.tile-hit:focus { outline: none; stroke: var(--selected); }
.tile-label { fill: var(--ink); font-size: 11px; pointer-events: none; text-anchor: middle; }
.unit { fill: var(--unit); stroke: var(--paper); stroke-width: 2; pointer-events: none; }
.unit-label { fill: var(--paper); font-size: 10px; pointer-events: none; text-anchor: middle; }
.selection-ring { fill: none; stroke: var(--selected); stroke-width: 3; pointer-events: none; }
.feed { background: #efe7d6; border-left: 1px solid var(--tile-stroke); padding: 1.5rem; }
.feed ol { margin: 0; padding-left: 1.2rem; }
.inspect { margin-top: 1rem; font-size: 0.95rem; }

@media (max-width: 720px) {
main { grid-template-columns: 1fr; }
.feed { border-left: 0; border-top: 1px solid var(--tile-stroke); }
}
24 changes: 24 additions & 0 deletions ahbg/presentation/board.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AHBG presentation board</title>
<link rel="stylesheet" href="board.css" />
</head>
<body>
<main>
<section class="board-wrap">
<h1>AHBG plane</h1>
<p class="note">Presentation only. Not mechanics. Tile centers are supplied by the declared UCNS source; the browser only scales them for display. Dashed traces show already-resolved motion. Click a center, or focus it and press Enter/Space, to inspect.</p>
<svg id="board" role="group" aria-label="Interactive Seed of Life presentation with inspectable tile centers and resolved unit motion traces"></svg>
</section>
<aside class="feed">
<h2>Feed</h2>
<ol id="feed-list"></ol>
<p class="inspect" id="inspect" aria-live="polite"></p>
</aside>
</main>
<script src="board.js"></script>
</body>
</html>
Loading