Conversation
- Board 2: canonical 40-tile Hasbro layout transcribed from Monopoly_origin.jpg (property/railroad/utility/tax/chance/CC/corners) - Board 1: 20-tile short game derived from Monopoly_short.png analysis, with monopoly_bonus_multiplier=1 (single tile per color group) - Board 3: 12-tile blank smoke board + board3_blank.svg (4x4 perimeter, clockwise numbering from START) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
16 cards per deck. Each card has a structured effect field (move_to_tile, collect, pay, go_to_jail, grant_jail_free_card, pay_per_building, collect_from_each_player, move_to_nearest, move_relative, pay_each_player) so the rules engine can apply them deterministically without LLM text interpretation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Self-produced SVG templates (property/railroad/utility) with
placeholder tokens. scripts/render_cards.py reads a board JSON and
stamps templates into one SVG per tile. Generated output committed
under static/assets/property_cards/board{1,2}/ so the UI can serve
them directly without a build step.
Hasbro trade-dress free (own color palette, layout, typography);
gameplay data (price, rent tables) mirrors the standard board.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PRD.md rewritten from the story-flow layout (Step 1..N) into a contract-first layout: Summary / Scope / Architecture / Data Model / API Surface / FSM / Game Rules / Adapters / UI / Config / NFR / Repo / Testing / CI / Milestones / Open Questions / Appendix. Flattens 10-1-3-1-2-2 deep nesting, centralises data schemas and API surface, and adds previously missing contracts: WS event envelope, REST error envelope with fixed error codes, latency budgets, concurrency model, observability, env/runtime config, ros2_node topic scope with Isaac card-spawn publisher. Integrates seven locked decisions in section 1.1: - Robot player decisions: Gemma for both players, greedy fallback - Intent schema: owned by this package, 12-entry vocabulary (section 8.2) - Whisper UX: push-to-talk + multipart upload - Card art: self-produced SVG templates + renderer - Board 1: 20-tile layout confirmed from image analysis - ros2_node: camera subs + Isaac card-spawn pub topic - Debug routes: MONOPOLY_DEBUG_ROUTES env guard PRD_easy.md slimmed from 316 to 124 lines: non-overlapping summary that points to PRD.md for details instead of restating them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the synthetic 4x4 square perimeter SVG with the real 3x5 rectangular perimeter used by the Dobot CR3A physical board (movensys-simulation/dobot_cr3a/monopoly_board.drawio.png). Tile count unchanged at 12: 4 corners + 1 middle top + 1 middle bottom + 3 per side column. START remains index 0 at bottom-right, clockwise numbering preserved. - board3_blank.svg: redrawn to 300x500 viewBox with merged center area - board3.json: adds layout hint (rectangle 3x5) and physical_image ref - board3_physical.png: copied in as reference asset - PRD.md 7.1: notes physical source and UI fallback SVG Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New section 16 splits M0..M8 into PR-sized checkboxes with PRD cross-references. Covers skeleton+CI, Board 3 E2E, Board 1/2 rule suites, camera+Isaac topic, three adapter integrations, optional chance art, and cross-cutting concerns (persistence, metrics, README, production Dockerfile, Playwright regression). Open Questions shifted to section 17, Appendix to 18. PRD_easy.md updated to point at the new checklist. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Minimum surface needed for CI to go green:
- main.py: FastAPI app with lifespan, event_id middleware, and
MONOPOLY_DEBUG_ROUTES guard (404 on /api/debug/* when disabled)
- router.py: /api/health plus per-adapter /api/{robot,stt,llm}/health
- adapters/{stt,llm,robot}.py: stub when env URL is empty, httpx client
otherwise. Robot fire-and-forget semantics per PRD 8.3; LLM enforces
the 12-intent vocabulary per PRD 8.2
- utils/logging.py: JSON log formatter with request-scoped event_id
- requirements.txt: fastapi, uvicorn, httpx, pydantic, python-multipart
- .gitignore: pycache, pytest cache, .venv
- scripts/ci_local.sh: honour PEP 668 so it runs on Ubuntu 24.04
Verified: ci_local passes, /api/health -> 200 {"status":"ok"},
/api/robot/health with empty ROBOT_SERVICE_URL -> {"mode":"stub"},
debug routes return 404 with error envelope including event_id when
MONOPOLY_DEBUG_ROUTES=false.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Slim Dockerfile on ros:${ROS_DISTRO}-ros-base. ROS 2 environment is
sourced in entrypoint.sh so ros2_node.py can be added at M4 without
changing the image. At M0 only Python deps are installed; camera/
imaging apt packages are deferred until camera subscribers land.
docker-compose.yml wires every env var from PRD 10.1 with stub-mode
defaults (empty STT/LLM/ROBOT_SERVICE_URL keeps adapters in stub), uses
host networking to share the DDS domain with the robot stack, and runs
on MONOPOLY_PORT (defaults to 8000).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ros2Bridge spins a single-threaded executor on a daemon thread. rclpy is imported lazily so uvicorn can start on machines without ROS 2 and in CI without the ros-tooling setup step. Import failure or init failure disables the bridge silently and logs a single warning; the rest of the app keeps running. At M0 the bridge does nothing besides expose /api/ros2/health so smoke tests can confirm lifespan wiring. Camera subscribers and the Isaac card-spawn publisher land in M4 (PRD 11.5). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GET / serves static/index.html and /assets/* mounts the board and card
SVG directory. The page polls /api/{health,stt,llm,robot,ros2}/health
every 5 s and shows mode badges (stub/live/on/off) in the header plus
the raw JSON below. Enough to sanity-check a deployment without
hitting the CLI; the real Board UI lands in M1.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All M0 items (16.1) are landed except the final GitHub Actions run. ci_local.sh passes locally with stub-mode invariant intact. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- game/state.py: pydantic GameState/PlayerState/FSM/RuntimeConfig per
PRD section 4. Canonical location = positions[player]; players[] holds
balance, jail status, color. Winner/pending_dice/turn_number tracked.
- game/boards.py: Board/Tile pydantic models, load_board() caches JSON
parses from static/assets/boards/board{1,2,3}.json.
- game/rules.py: start_game, submit_dice, apply_move (with TILE_MISMATCH
on from/to divergence), end_turn. Wrap detection drives Board 3 winner
(first lap wins -> GAME_OVER). RuleError carries a stable error code
so the API layer can map to PRD section 4.7 envelopes.
- tests/game/: 21 unit tests covering board loading, FSM guards, dice
validation, move mismatch branches, wrap-to-win for both players,
end-of-game idempotency.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- game/events.py: tiny in-process pub/sub with bounded per-subscriber
queues. Drops messages on slow clients rather than blocking the FSM.
make_envelope() produces PRD 4.6 envelopes (event_id, ts, type,
payload).
- game/manager.py: wraps GameState with asyncio.Lock (PRD 11.2) and
emits structured events around every transition: game_started,
dice_submitted, move_applied, lap_completed, fsm_transition, game_won.
- router.py: adds /game/{start,end_turn,state,winner,config,next_prompt},
/dice/{request,submit}, /move/apply with pydantic request models.
RuleError maps to 4xx/409 with the PRD 4.7 error envelope.
- main.py: instantiates GameManager + EventBus in lifespan and installs
an HTTPException handler that rewrites FastAPI's default {"detail":...}
into {"error": {code, message, details, event_id}}.
Verified end-to-end via curl: start -> submit dice -> apply move ->
RESOLVE_TILE, TILE_MISMATCH returns 409 with details, end_turn rotates
back to TURN_START.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
On connect the handler subscribes to the EventBus, pushes a hello envelope carrying the current GameState snapshot so clients can reconcile without a separate REST round-trip, then forwards every published event until the client disconnects. Cleanup is finally-based so the subscriber queue is always released. Verified with a websockets client: game_started and fsm_transition envelopes arrive in order after POST /api/game/start. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tests/e2e/test_board3_smoke.py exercises the public HTTP surface: - start game (board=3) - alternate turns: submit dice, apply move, end turn - user wraps back to START on the 4th move and wins - GET /api/game/winner and /api/game/state agree - TILE_MISMATCH path returns 409 with the PRD 4.7 error envelope - adapter stub invariant holds in the same boot as the game test pyproject.toml pins asyncio_mode=auto so plain `async def test_*` functions work without decorators. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- static/index.html: grid layout (board pane + sidebar). Board SVG host overlays two <circle> pieces; the sidebar shows game state, manual controls (start, submit dice, apply move, end turn), and a scrolling event log. - static/app.css: dark theme, mode badges at top, CSS transitions on circle cx/cy drive the 500 ms piece slide. - static/app.js: reconciles from /api/game/state on boot, opens WS /api/stream/game and re-fetches state on each event (simpler and robust to dropped messages). Per-board tile-center lookup table keeps the piece layer pure SVG. - main.py: mount /static for JS/CSS alongside existing /assets mount. Also ticks off M1 items in PRD 16.2 — all 12 checklist entries done, 24 tests green (21 unit + 3 E2E). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers the paths developers actually use day to day: uvicorn run/stop, docker compose up/down, pytest suites, the ci_local.sh reproduction, env var overrides for live adapter modes, quick health checks, a one-liner CLI game walk-through, and the handful of errors we've already hit (PEP 668 pip, port conflicts, wrong cwd). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two fixes for the same failure mode — RMW_IMPLEMENTATION is set to an
RMW whose shared library isn't installed, and rcl reacts by calling
exit(1) at the C level, which Python try/except can't catch.
- docker/Dockerfile: install ros-${ROS_DISTRO}-rmw-cyclonedds-cpp so
the container matches the rest of the movensys stack (movensys_vlm
already ships this), which runs with
RMW_IMPLEMENTATION=rmw_cyclonedds_cpp.
- ros2_node.py: preflight the RMW .so via ctypes.CDLL before calling
rclpy.init(). If the lib is missing we log a warning and disable the
bridge instead of crashing the FastAPI process. The existing Python
try/except around rclpy.init() stays as a second line of defence.
- Dockerfile COPY also switched from explicit file list to `*.py` so
new top-level modules get shipped automatically (matches movensys_vlm).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
game/properties.py owns the static-plus-dynamic stitching:
- property_id() slug rule shared with render_cards.py
- initial_properties() builds the dict keyed by property_id from a board
- compute_rent() branches on tile.kind:
property -> rent_table by houses/hotel; monopoly doubles only when
unimproved (PRD section 7.3.5)
railroad -> 25 * 2^(n-1) for n railroads the owner holds
utility -> dice_sum * 4 or * 10 (1 vs 2 owned)
mortgaged / unowned / missing dice for utility -> 0
- is_monopoly(), railroads_owned(), utilities_owned() helpers
- even_build_ok() enforces the 1-house max gap for Board 2
- render_card() projects Tile + PropertyState into the PRD section 4.4
PropertyCard shape for REST responses
game/state.py gains PropertyState and a properties dict on GameState.
20 unit tests cover every rent branch, monopoly detection, even-build,
slug stability, and the Board 1 single-tile-group edge case.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- game/decks.py: Deck/Card pair with draw/return-to-bottom (Hasbro rule) and seedable shuffle for reproducible tests. load_chance() and load_community_chest() pull from the JSON files committed earlier. - game/effects.py: apply_effect() pattern-matches the structured effect types from the decks (collect, pay, pay_each_player, collect_from_each_player, pay_per_building, move_to_tile, move_relative, move_to_nearest, grant_jail_free_card, go_to_jail) and returns a payload dict suitable for WS events. No LLM needed to execute cards — the deck JSON is the contract. - move_to_tile wrap detection stays correct for "Advance to X" cards (wrapped iff destination < current). - go_to_jail sets in_jail/jail_turns_left; the full jail exit FSM comes at M3 (PRD section 7.3.1), Board 1 uses this as a plain teleport. 19 unit tests cover deck shape, shuffle determinism, every effect branch, and assert every card JSON's effect.type is known by the dispatcher. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
game/rules.py gains the behavioural pieces needed to play beyond the blank smoke board: - resolve_tile() dispatches by the destination tile.kind. For property/railroad/utility it either triggers AWAIT_DECISION for an unowned tile the player can afford, an unaffordable-flag event, or a rent payment. Tax tiles deduct, chance/CC draw from a caller-supplied deck and apply the structured effect, go_to_jail teleports. When a card moves the player the routine chains to resolve the new tile up to max_chain hops. - buy_property / skip_purchase / build / sell_building / mortgage / unmortgage implement the full transaction set from PRD section 7.3. Board 2 gates building on monopoly and the even-build (+/-1 per group) rule; Board 1's monopoly_bonus_multiplier=1 short-circuits both. - _auto_liquidate sells buildings in descending house-cost order then mortgages unencumbered properties. _pay_rent_or_bankrupt and _pay_bank_or_bankrupt move to GAME_OVER when liquidation falls short; opponent bankruptcy transfers assets, bank bankruptcy clears them. start_game also seeds state.properties via initial_properties(board). 25 unit tests cover resolve routing (start, buyable, unaffordable, rent, self-owned, tax, chance draw, chance chain), the full buy/skip/build/ hotel/mortgage/unmortgage/sell cycle, monopoly and even-build gating, rent-driven auto-liquidation, rent bankruptcy (assets to opponent), and tax bankruptcy (assets to bank, opponent still wins 1v1). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GameManager
- Holds chance_deck and cc_deck instances, reshuffled on start_game.
- apply_move now auto-runs resolve_tile with those decks after a
successful move, emits a tile_<kind> event per resolution step, and
returns the list under `resolved.tiles` so the UI can render the
decision modal or rent notification in the same round-trip.
- Adds buy_property / skip_purchase / decide_property / build /
mortgage / unmortgage / sell_building / apply_card_effect async
wrappers. Each publishes an event on the bus and is serialised by
the same lock the rest of the game uses.
- list_properties() projects PRD §4.4 PropertyCard shape via
properties.all_cards.
router.py
- New REST endpoints per PRD §5.3-5.5: GET /properties, /properties/{id},
POST /properties/{id}/decide|buy|build|mortgage|unmortgage|sell_building,
GET /money, /money/{player}, POST /effects/{type}.
- WS aliases /stream/{board,money,properties} subscribe to the same bus
as /stream/game with a type allow-list so slow UI panels can tune in
to only what they render. /stream/game still forwards everything.
Verified manually: start board=1 → dice 1 → move 0→1 (Baltic) →
AWAIT_DECISION with property_arrival_buyable payload → /decide buy →
balance goes 1000→950, owner=user, fsm back to RESOLVE_TILE.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…dal (M2)
- static/assets/boards/board{1.png,2.jpg}: original Hasbro images
imported as the in-browser backdrop; board{1,2}.json point to them.
- static/app.js:
* squarePerimeterCenters() derives tile-center (cx, cy) for 20-tile
and 40-tile boards so piece layers work without hand-tuning.
* Three-column grid (user props | board | controls + robot props +
events). Board 3 keeps its 3x5 layout; 1/2 share the square path.
* Money widget flashes accent colour up/down for 600 ms on balance
changes (PRD §3.3).
* Property sidebar groups by color_group, shows house/hotel icons,
strikes through mortgaged tiles.
* Decision modal pops on `tile_property_arrival_buyable` event with
skip / buy / buy+build buttons; submits to /api/properties/{id}/decide.
- static/app.css: header money bar, board pane aspect ratios, modal
backdrop, property thumb styling.
Verified serving: / returns 3.6 kB HTML, static/app.{css,js} and
assets/boards/board{1.png,2.jpg} all 200.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tests/board1/test_board1_flow.py exercises the full Board 1 rule set through the public HTTP surface with ASGITransport: - buy + ownership transfer - skip keeps property unowned, no money moved - rent settles between players on a turn-of-arrival (no monopoly bonus because Board 1 multiplier is 1) - build on a single-tile color group works (no monopoly gate) - income tax deducts on landing - bankruptcy-to-bank on unpaid tax ends the game with robot winning - chance draw on tile 12 surfaces chance_drawn in resolved.tiles - mortgage cycle returns $25 and costs $27 to lift PRD section 16.3 (M2) marked complete: 14/14 checklist items shipped, 97 tests green (rules M1 21, properties 20, decks+effects 19, rules M2 25, board1 9, e2e board3 3). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Board 1 image analysis was off. Correct shape is 4 corners plus 3
tiles per side = 16 tiles, with corners ~1.5x the non-corner tile
width (standard Hasbro layout). Previous 20-tile layout caused the
piece to drift well past the actual tile edge on every move.
- static/assets/boards/board1.json: rewritten with 16 tiles, explicit
layout hint (shape, per_side, corner_ratio). Monopoly Short Game
tile list:
GO, Baltic, Reading RR, Connecticut,
Just Visiting,
Electric Co, Virginia, St. James Place,
Free Parking,
Illinois, Atlantic, Water Works,
Go To Jail,
Pennsylvania, Short Line, Boardwalk
- static/app.js squarePerimeterCenters: now takes a cornerRatio
argument. Board edge = 2*cornerW + perSide*sideW, so sideW =
size / (perSide + 2*cornerRatio). Corner centers sit at
cornerW/2 from the edge; side centers at cornerW + (i-0.5)*sideW.
- static/index.html + app.css: new .board-wrap positions the pieces
SVG directly on the board-host element, so the overlay always
matches the image bounds instead of the outer pane.
- static/assets/boards/board2.json: also gets the layout hint.
- Regenerated Board 1 property card SVGs (12 total).
- Dropped test_board1_{tax_tile_deducts,bankruptcy_to_bank_on_tax,
chance_draw_via_move,start_bonus_on_lap} — they assumed the removed
Income Tax / Chance tiles. Added test_board1_start_wraps_past_go
and test_board1_go_to_jail_tile_teleports for the 16-tile layout.
- Updated test_load_board_tile_counts parametrisation to expect 16.
95 tests green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
apply_move was incrementing lap_count but never crediting board.start_bonus ($100 on Board 1, $200 on Board 2). Non-Board-3 boards now add the bonus to the wrapping player's balance and the MoveResult carries a start_bonus_collected field. GameManager emits a new `start_bonus` event so the UI can animate it. +3 unit tests (Board 1 wrap $100, Board 2 wrap $200, no-wrap no bonus). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
go_to_jail was setting in_jail=True and jail_turns_left=3 on every board. Board 1's short game has no exit mechanism, so players stayed flagged as jailed forever with no way to clear it. The flag is now set only when board.monopoly_bonus_multiplier > 1 (Board 2), which is also the natural gate for the Board 2 jail exit FSM coming in M3. The teleport to jail_visit itself still runs on Board 1. Updated the Board 1 test to assert the new semantics (in_jail False, jail_turns_left 0 after landing on Go To Jail). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The "both balances went up" UX puzzle was caused by rent-driven auto-liquidation: - _auto_liquidate sold buildings from most-expensive first, which could refund much more than the rent required - the whole sequence fired as a single tile_rent_paid event, so the UI saw only the final balance change and missed the intermediate sell -> rent transitions Fix: - Sort the sell phase by price_building ascending (cheapest first) so a $14 rent tab never pulls a $200 hotel-tier house sale when a $50 house sale will do. - Sort the mortgage phase by price_buy ascending for the same reason. - _auto_liquidate now returns a list of per-step dicts; the rent/tax bankruptcy helpers propagate them up, resolve_tile passes them through payload["liquidation"], and GameManager publishes building_sold / property_mortgaged events one at a time before the final tile_rent_paid (or tile_tax_paid) envelope. The UI can now narrate "sold house -> paid rent" instead of showing a mystery balance bump. Test: cheapest-first overshoot — 1 brown house ($25 refund) covers a $25 rent, leaving dark-blue $200-tier houses intact. 99 tests green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
apply_effect() was accessing effect["amount"], effect["tile_index"],
etc. directly — a missing key raised KeyError which escaped the
router's RuleError/ValueError catch, surfacing as a generic 500.
Introduce _require() that raises EffectError("BAD_REQUEST") and wrap
the dispatch in a try/except that also converts TypeError/ValueError
(bad int casts etc.) into the same EffectError. router.effects_apply
now catches EffectError and returns the PRD section 4.7 error envelope
with status 400.
+2 tests: missing amount and non-numeric amount both raise BAD_REQUEST.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…flag Economy tuning: Board 1 starting at $1000 with max base rent $50 (Boardwalk) meant rent was ~5% of the bank — games dragged on for dozens of turns. Setting monopoly_bonus_multiplier from 1 to 2 doubles the base rent for every owned property (each color group is a single tile on the short game, so owning that tile is automatically a monopoly). Boardwalk now costs $100 to land on, Baltic $8, which matches Hasbro's Short Game intent without inventing new rent tables. Follow-on fix: the B2 commit's in_jail gate keyed off monopoly_bonus_multiplier > 1, which would now re-flag Board 1 players as jailed. The correct rule is "don't set in_jail until the jail-exit FSM exists (M3)", so go_to_jail is a pure teleport on every board for now. Once Board 2's jail FSM lands in M3 it will opt in explicitly. Tests updated: Board 1 rent assertion 4 -> 8, Board 1 single-tile group bonus now True, go_to_jail tests expect in_jail False on both boards. 101 tests green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fix/running script real
Feature/robopoly player
Fix/predefined pose
Release/release 0.2 rc1
…aacsim Feature/spawn dice and pieces isaacsim
@kyoungje I have changed the dry_run mode variable to 1.
Fix/bugs after taipei
remove vectordb, phoenix to reduce memory usage
Contributor
|
@LebahGanteng88 @LayNoah @mohsinarf |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I think it is time to release version 0.2. @kyoungje, please make it a public repository so I can add more reviewers