Skip to content

feat(ahbg): extract validated presentation-only board - #17

Closed
erinepshovel-code wants to merge 21 commits into
mainfrom
repair/ahbg-presentation-only
Closed

erinepshovel-code wants to merge 21 commits into
mainfrom
repair/ahbg-presentation-only

Conversation

@erinepshovel-code

Copy link
Copy Markdown
Contributor

Replacement for presentation concept in #3

Built from current main@eaec7fd6ee4e829b6fae10a2c6d520b35857137d. This PR contains only the AHBG presentation surface plus its dedicated CI; it does not carry the engine, DeepSeek, or a0min scope that accumulated in #3.

Preserved concept

  • Seed-of-Life circles around tile centerpoints;
  • selection/inspection;
  • unit markers and feed;
  • dashed/animated traces for already-resolved motion events;
  • observation-to-presentation projection that does not decide legality.

Review repairs

  • Python and browser validators reject malformed/duplicate tile IDs and coordinates;
  • unit tile IDs and optional labels are validated before membership/rendering;
  • motion destinations must equal the unit's presented final tile;
  • tile inspection is keyboard-operable with focus and selected-state semantics;
  • multiple units on one tile receive distinct visual offsets;
  • selection is rendered as a ring above the unit layer, remaining visible on occupied tiles;
  • package-relative snapshot import is the only package path;
  • dedicated CI runs Python contract tests plus node --check using current Actions v6.

Boundary

Presentation geometry is explicitly not game adjacency authority. No engine, War, turn, RNG, permissions, or agent-harness behavior is defined here.

Usage

python -m unittest discover -s ahbg/presentation/tests -p 'test*.py'
node --check ahbg/presentation/board.js
cd ahbg/presentation && python -m http.server 8765 --bind 127.0.0.1

hmmm

The live engine-to-observation adapter remains owned by the eventual engine integration; construction animation awaits an owning mechanics event contract.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T22:56:02.045575Z 0b4ca55 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2f1da218b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ahbg/presentation/board.js Outdated
Comment on lines +30 to +31
x: RADIUS * (q + r / 2),
y: RADIUS * (Math.sqrt(3) / 2) * r,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use UCNS-supplied geometry in the renderer

For observations using any UCNS orientation, nesting, or future non-default construction, this formula independently reconstructs center positions from AHBG axial coordinates instead of rendering UCNS's exact centers. This conflicts with the authority contract in ahbg/README.md lines 13-21 and will silently flatten those valid geometries into this locally defined grid; the snapshot should carry UCNS-derived display positions and their source identity rather than recomputing them here.

AGENTS.md reference: AGENTS.md:L13-L15

Useful? React with 👍 / 👎.

Comment thread ahbg/presentation/board.html Outdated
<section class="board-wrap">
<h1>AHBG plane</h1>
<p class="note">Presentation only. Not mechanics. Each tile is a centerpoint; circles are Seed of Life geometry. Dashed traces show already-resolved motion. Click a center, or focus it and press Enter/Space, to inspect.</p>
<svg id="board" role="img" aria-label="Seed of Life tiles as centerpoints with resolved unit motion traces"></svg>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep interactive tiles out of an image role

For screen-reader users, role="img" makes the SVG's descendants presentational, so the dynamically created role="button" tile hit targets are not exposed as individual controls even though they remain keyboard-focusable. Use a container role that permits interactive descendants, or move the controls outside the image semantics, so the advertised tile inspection is accessible.

Useful? React with 👍 / 👎.

Comment thread ahbg/presentation/board.js Outdated
}

const motionUnits = new Set();
for (const motion of snapshot.motions || []) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-array motions in the browser validator

When a fetched snapshot contains motions: false, 0, or "", this fallback treats the malformed field as an empty motion list and accepts the document, whereas the Python contract rejects every present non-list value. That breaks the package's fail-closed validation boundary for browser consumers; distinguish an omitted field from a present value and require Array.isArray(snapshot.motions) before iterating.

Useful? React with 👍 / 👎.

@@ -0,0 +1,95 @@
"""Project a sanitized observation into an AHBG presentation snapshot.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Declare the new behavior modules in MODULE_BUILD

These new behavior-bearing Python and JavaScript modules start directly with implementation and omit the MODULE_BUILD declarations required by the pinned skill-lib/meta-module-build/SKILL.md doctrine. Consequently the module-build runner reports them as coverage gaps and cannot audit their declared owner, public surface, boundaries, dependencies, rollout, or rollback; add the module declarations before treating this package as a bounded build.

AGENTS.md reference: AGENTS.md:L47-L49

Useful? React with 👍 / 👎.

Comment on lines +276 to +278
const angle = (Math.PI * 2 * index) / group.length - Math.PI / 2;
const spread = UNIT_RADIUS * 1.35;
return { x: Math.cos(angle) * spread, y: Math.sin(angle) * spread };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scale unit offsets to prevent marker overlap

When four or more units share a tile, placing every center on a fixed 1.35 * UNIT_RADIUS ring no longer separates the markers: with four units adjacent centers are about 21 pixels apart while each marker is 22 pixels wide, and the overlap rapidly worsens as more units are added. Since snapshots impose no occupancy cap and the README promises separation for multiple occupants, size the spread by group length or use additional rings.

Useful? React with 👍 / 👎.

Comment thread ahbg/presentation/project.py Outdated
"tiles": tiles,
"units": units,
"selected_tile": selected_tile,
"feed": [dict(item) for item in feed],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Copy only presentation fields from feed entries

When a feed item contains internal metadata alongside its public turn and text—for example a private prompt, DM state, or certification field—dict(item) copies all of it into the returned presentation snapshot, and the validator permits those unknown keys. Any caller that serializes this supposedly visual-only snapshot to the browser therefore leaks fields that are never rendered; explicitly project only the declared feed fields just as the tile, unit, and motion loops do.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a4464f7293

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ahbg/presentation/snapshot.py Outdated
Comment on lines +128 to +131
raise PresentationSnapshotError(f"tile {tile_id} y must be numeric and nonboolean")
position = (float(x), float(y))
if position in positions:
raise PresentationSnapshotError(f"tile source position repeats: {position}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-finite tile coordinates

When load_snapshot reads Python-supported NaN or Infinity constants, or the projector receives non-finite floats directly, these type checks accept them and the snapshot is reported as validated. Such coordinates produce invalid display geometry and are rejected by the browser validator, while sufficiently large integers can instead raise an uncaught OverflowError during float(x). Convert safely and require math.isfinite so the Python and browser contracts agree.

Useful? React with 👍 / 👎.

Comment on lines +287 to +289
const motion = motionByUnit[unit.id];
const fromCenter = motion ? sourceToPixel(byId[motion.from]) : center;
const origin = { x: fromCenter.x + offset.x, y: fromCenter.y + offset.y };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compute motion origins from initial occupancy

When a moving unit and a stationary unit initially share a tile but end on different tiles, offset is calculated from the moving unit's destination group and then reused at its origin. Both markers therefore start at the same center even though the multiple-occupant layout is intended to keep them separated; build a separate initial grouping from motion from tiles plus stationary units and use its offsets for origin.

Useful? React with 👍 / 👎.

Comment on lines +178 to +182
const minX = Math.min(...pixels.map((p) => p.x)) - RADIUS * 1.2;
const minY = Math.min(...pixels.map((p) => p.y)) - RADIUS * 1.2;
const maxX = Math.max(...pixels.map((p) => p.x)) + RADIUS * 1.2;
const maxY = Math.max(...pixels.map((p) => p.y)) + RADIUS * 1.2;
svg.setAttribute("viewBox", `${minX} ${minY} ${maxX - minX} ${maxY - minY}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include displaced unit markers in the viewBox

With the new count-scaled spread, a tile containing roughly 16 or more units places marker edges beyond the fixed 1.2 * RADIUS padding, especially on an outermost tile, so SVG viewport clipping hides part of the otherwise separated markers. The snapshot contract has no occupancy cap; compute the bounds after unit offsets or enlarge them to cover the maximum displaced marker extent.

Useful? React with 👍 / 👎.

Comment thread ahbg/presentation/snapshot.py Outdated
Comment on lines +159 to +160
if label is not None and (not isinstance(label, str) or not label):
raise PresentationSnapshotError(f"unit {unit_id} label must be exact non-empty text when present")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject explicit nulls in optional scalar fields

When a JSON snapshot explicitly contains label: null on a tile or unit, or turn: null on a feed item, the use of .get() treats the field as absent and accepts the document even though these fields must be non-empty text or a non-negative integer when present. The browser validator rejects the same snapshot, so a Python-validated document can fail at its consumer; distinguish key absence from an explicitly null value while retaining the separately documented nullable selected_tile behavior.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b4ca55333

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +78 to +80
function plainInteger(value) {
return Number.isInteger(value);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject integers outside the browser-safe range

When a Python-validated snapshot contains a turn above Number.MAX_SAFE_INTEGER (for example 9007199254740993), JSON parsing rounds it to 9007199254740992, and this predicate still accepts the rounded value. The board then displays a different root or feed turn than the producer supplied; enforce the same safe-integer range in both validators rather than using Number.isInteger alone.

Useful? React with 👍 / 👎.

Comment on lines +49 to +50
raise ValueError("y must be numeric and nonboolean")
if isinstance(scale, bool) or not isinstance(scale, (int, float)) or scale <= 0:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-finite display-transform inputs

When callers pass NaN or infinity as the scale, this guard accepts it because comparisons with NaN are false and positive infinity is greater than zero, causing source_to_display to return non-finite coordinates. The same remains true for non-finite x and y; since this is declared as a public transform and already validates numeric inputs, require math.isfinite for all three values before producing display geometry.

Useful? React with 👍 / 👎.

Comment on lines +58 to +61
try:
payload = json.loads(target.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise PresentationSnapshotError(f"cannot load snapshot: {exc}") from exc

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wrap snapshot decoding failures

When the snapshot file contains invalid UTF-8, Path.read_text raises UnicodeDecodeError, which is not covered by this exception handler. Any caller that handles the package's advertised PresentationSnapshotError for malformed snapshots will instead receive an unexpected exception and can crash; wrap decoding errors alongside I/O and JSON parse failures.

Useful? React with 👍 / 👎.

@@ -0,0 +1,164 @@
"""Regression tests for the AHBG presentation-only boundary.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Declare presentation contracts and executable checks

Although the repair added MODULE_BUILD declarations, fresh inspection shows that the behavior modules still declare no CONTRACTS and this executable test module owns no CHECKS entries, so the required test-build audit cannot link any of these nine tests to source obligations or detect orphaned behavior. Add source-owned contracts and test-owned checks rather than relying only on unittest discovery.

AGENTS.md reference: AGENTS.md:L47-L49

Useful? React with 👍 / 👎.

erinepshovel-code added a commit that referenced this pull request Sep 1, 2026
…bounds (#20)

Recovered the presentation-only board from closed #17 (built from current
main). Repairs the two remaining renderer defects:

- initial vs final multi-unit occupancy offsets: a unit now uses the initial
  occupancy group of its motion origin for the start marker and the final
  group of its presented tile for the end marker, instead of applying the
  final-tile offset to both endpoints.
- SVG viewBox bounds: viewBox now derives from seed circles plus every
  displaced unit marker extent at both motion endpoints, so offset markers
  cannot clip at the board edge.

Keeps UCNS as geometry authority (presentation only scales supplied centers).
Regression tests mirror the frozen offset math and assert the corrected
source structure.

Co-authored-by: erinepshovel-code <250928284+erinepshovel-code@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant