Purpose: This file is a high-context architecture guide for coding agents working in this repo.
Primary goal:
- Give enough architectural context that agents can act with minimal repo re-discovery.
- Reduce token usage by centralizing decisions, constraints, and tradeoffs.
- Keep implementation aligned with the 28-day target:
5+ games + simplified prediction marketon a modular single core program.
Status note:
- Cayed started as Battleship-first.
- The direction is now an on-chain consumer arcade.
- Route system should use
/games/*for all game and/marketfor markets.
Cayed is positioning as a consumer on-chain arcade, not a DeFi terminal.
Key UX thesis:
- Users should understand game loops in seconds.
- Wager flow should feel simple and transparent.
- Settlement should be trustless and verifiable.
- Prediction is a lightweight spectator/player layer, not a full exchange.
Core technology thesis:
- One modular Anchor program gives the best sprint velocity for a duo team.
- MagicBlock ER is used for private state and responsive gameplay.
- Game-specific logic lives in adapters/modules, while custody/settlement/privacy remain core.
Must-have outcomes:
- 5+ games with full create/join/play/settle lifecycle.
- Simplified prediction market tied to game outcomes.
- Frontend routes and codebase organized under
/games/*. - Reusable architecture for adding new games without reworking core.
Definition of done for each game:
- On-chain flow works: create session, join session, play actions, settle session.
- Frontend flow works under
/games/<slug>and/games/<slug>/<sessionId>. - Outcome maps to a
result_codeconsumed by market settlement. - Error states are user-readable and mapped from program errors.
Decision:
- Use a single modular core program for this sprint.
Alternatives considered:
- One program per game.
- Core program plus separate market program now.
- Keep current monolithic Battleship-centric pattern.
Why single modular core wins now:
- Fastest execution path for a small team.
- One deploy path, one IDL generation path, one client package.
- Shared fee/wager/privacy logic avoids duplicated security-critical code.
- Lowest frontend integration overhead in a 28-day window.
Known cost of this decision:
- Shared upgrade blast radius.
- Larger binary and IDL over time.
- Greater need for internal module boundaries and tests.
Risk controls:
- Hard boundaries in folder/module layout.
- Per-game adapter test suites.
- Feature flags in game registry.
- Freeze schema boundaries after week 1.
- Keep state and instruction naming game-agnostic in core.
- Keep game rule logic isolated in
games/*adapters. - Keep ER permission/delegation logic in core only.
- Keep generated client usage inside service layers, not pages/components.
- Add games by composing contracts, not by branching core flow.
- Avoid frontend route hardcoding; use registry-driven mounting.
Current coupling hotspots:
programs/cayed/src/state/game.rsuses battleship-specific fields.programs/cayed/src/instructions/hide_ships.rsandmake_move.rsembed battleship rules.web/src/App.tsxandweb/src/services/game-service.tsare battleship-oriented.
Refactor target:
- Replace
Gameconcept with genericSession. - Replace
PlayerBoardwith generic delegatedPlayerStateblob. - Replace battleship action instructions with generic
submit_actionand adapter dispatch. - Move UI and service layering to
/games/*modules.
Recommended shape in programs/cayed/src/:
src/
lib.rs
errors.rs
instructions/
core/
init_config.rs
create_session.rs
join_session.rs
submit_action.rs
settle_session.rs
create_permission.rs
delegate_pda.rs
market/
create_market.rs
place_bet.rs
resolve_market.rs
claim_payout.rs
mod.rs
state/
config.rs
vault.rs
session.rs
player_state.rs
market.rs
bet_position.rs
mod.rs
games/
mod.rs
adapter.rs
battleship.rs
coinflip_duel.rs
high_card.rs
tic_tac_toe.rs
dice_hi_lo.rs
Why this split:
instructions/core= shared lifecycle/custody/security logic.instructions/market= simplified market flow without contaminating game core.games/*= rule engines per game.state/*= stable account model boundaries.
Pros:
- Easier ownership of concerns.
- Easier code reviews and regression analysis.
- Easier extension to new games.
Cons:
- More files and plumbing initially.
- Requires discipline in where logic lives.
Suggested fields:
pub struct Session {
pub id: u64,
pub game_type: GameType,
pub player_1: Pubkey,
pub player_2: Option<Pubkey>,
pub wager_lamports: u64,
pub next_actor: Pubkey,
pub move_count: u16,
pub status: SessionStatus,
pub winner: Option<Pubkey>,
pub result_code: Option<u8>,
pub bump: u8,
}Why this shape:
- Works for turn-based and instant-settlement games.
result_codeunifies game settlement with market settlement.game_typeis the dispatch anchor.
Pros:
- Stable generic lifecycle.
- Easy market linkage.
- Easy frontend representation.
Cons:
- Some games need richer metadata in private state blobs.
- Validation complexity moves into adapters.
Suggested fields:
pub struct PlayerState {
pub session_id: u64,
pub player: Pubkey,
pub game_type: GameType,
pub state_version: u16,
pub state_blob: Vec<u8>,
pub bump: u8,
}Why blob-based private state:
- Supports hidden info games (Battleship, card games).
- Keeps core accounts stable while game internals evolve.
- Enables adapter-local schema evolution (
state_version).
Pros:
- Very flexible.
- Prevents core account explosion.
Cons:
- Requires strict decode guards.
- Corruption risk if versioning is ignored.
Simplified market model:
- One market references one session.
- Two outcomes only.
- Bet positions are per bettor and side.
Pros:
- Minimal implementation risk.
- Easy UX and settlement.
Cons:
- Not as expressive as order-book/AMM systems.
- Limited market design in v1.
Core adapter interface:
pub trait GameAdapter {
fn init_state(params: &[u8]) -> Result<Vec<u8>>;
fn validate_action(state: &[u8], action: &[u8], actor: Pubkey) -> Result<()>;
fn apply_action(state: &[u8], action: &[u8], actor: Pubkey) -> Result<ApplyResult>;
fn is_terminal(state: &[u8]) -> Result<bool>;
fn resolve_outcome(state: &[u8]) -> Result<Outcome>;
}Dispatch style:
match session.game_type {
GameType::Battleship => battleship::apply(...),
GameType::CoinflipDuel => coinflip_duel::apply(...),
GameType::HighCard => high_card::apply(...),
GameType::TicTacToe => tic_tac_toe::apply(...),
GameType::DiceHiLo => dice_hi_lo::apply(...),
}Pros:
- Compile-time deterministic module selection.
- Keeps instruction surface stable while adding games.
- Easy to test per adapter.
Cons:
- Program grows as game count grows.
- Care needed to avoid adapter side effects bleeding into core.
Core instructions:
init_config(max_state_bytes, fee_bps, min_wager)create_session(id, game_type, wager, game_init_params)join_session()submit_action(action_blob)settle_session()create_permission(account_type, members)delegate_pda(account_type)
Market instructions:
create_market(market_id, session_id, outcome_a_code, outcome_b_code)place_bet(side_code, amount)resolve_market()claim_payout()
Design note:
- Keep action payloads compact.
- Keep serialization contract stable and versioned.
- Prefer error enums that are adapter-prefixed but routed through common user-facing mapping.
Stable seeds:
Config:["config"]Vault:["vault"]Session:["session", id.to_le_bytes()]PlayerState:["player_state", session_id.to_le_bytes(), player.as_ref()]Market:["market", market_id.to_le_bytes()]BetPosition:["bet", market_id.to_le_bytes(), bettor.as_ref(), side_code]
Why this seed plan:
- Human-readable and debuggable.
- Predictable client derivation.
- Works cleanly for account indexing.
Core rule:
- ER-related account privacy and delegation belong to core instructions.
- Game adapters do not perform permission/delegation orchestration.
Session lifecycle with ER:
- Create/join on base layer and fund vault.
- Create permissions and delegate private state accounts.
- Run hidden or rapid gameplay actions on ER.
- Settle by resolving outcome, clearing permissions, and commit/undelegate.
Pros:
- Uniform privacy/security path across games.
- Less risk of per-game ER misconfiguration.
Cons:
- Requires robust shared utilities and tests.
- ER integration bugs can affect all games.
Mitigation:
- Keep ER logic isolated in
instructions/core. - Add ER privacy regression tests per game.
web/src/
core/
context/
providers/
rpc/
wallet/
games/
registry.ts
battleship/
index.ts
routes.tsx
service.ts
hooks/
pages/
components/
types.ts
coinflip-duel/
high-card/
tic-tac-toe/
dice-hi-lo/
prediction/
shared/
components/
hooks/
tx/
account/
App.tsx
main.tsx
Why this split:
corefor platform concerns.games/<slug>for isolated game modules.sharedfor reusable UI and data hooks.
Pros:
- New game onboarding is predictable.
- Router and app shell remain stable.
- Easier lazy-loading and feature flags.
Cons:
- Requires discipline to avoid leaking game logic into
core. - Some duplication acceptable for speed.
Required route shape:
/arcade launcher./games/:sluglobby/detail entrypoint./games/:slug/:sessionIdactive session page./games/predictionmarket flows.
Rules:
- No hardcoded battleship routes in app shell.
- Every game route must be registry-generated.
web/src/games/registry.ts:
type GameDefinition = {
slug: string;
title: string;
isEnabled: boolean;
serviceFactory: () => GameService;
routes: React.ReactNode;
marketOutcomeMap: Record<string, number>;
};Pros:
- Feature-flag rollout control.
- Launcher UI generated from metadata.
- No repeated router edits.
Cons:
- Bad registry metadata can break route discovery.
- Need simple runtime validation.
Split existing web/src/services/game-service.ts into:
web/src/core/rpc/core-session-service.ts.web/src/games/<slug>/service.ts.
Core service owns:
- connection selection (devnet vs ER).
- tx assembly/send lifecycle.
- auth token management.
- common PDA derivation helpers.
Game service owns:
- game action encoding/decoding.
- game state view-model mapping.
- game-specific command methods.
Anti-pattern to avoid:
- Calling generated instruction builders directly from page components.
Scope constraints for v1 market:
- Two outcomes only.
- Session-linked only.
- No orderbook and no complex market making.
- Payout based only on
Session.result_code.
Pros:
- Deliverable in sprint timeline.
- Clear user understanding.
- Lower audit and implementation risk.
Cons:
- Limited strategy expressiveness.
- Not suitable for advanced traders.
Reason this is acceptable now:
- Cayed is consumer-first game platform.
- Prediction layer is augmentative, not primary product.
Test layers:
- Core lifecycle tests.
- Per-game adapter tests.
- ER privacy tests.
- Market tests.
- Cross-game smoke test.
Proposed layout:
tests/
harness/
validator.ts
fixtures.ts
tx.ts
games/
battleship.test.ts
coinflip-duel.test.ts
high-card.test.ts
tic-tac-toe.test.ts
dice-hi-lo.test.ts
market/
prediction.test.ts
smoke/
full-arcade.test.ts
Why this matters:
- Guards against regression while adding games quickly.
- Keeps confidence high with shared-core architecture.
- Localizes failures to game adapters when possible.
Week 1:
- Introduce generic state (
Session,PlayerState) and adapter interface. - Keep Battleship functionality intact via adapter wrapper.
- Freeze initial schema boundaries.
Week 2:
- Add
coinflip-duelandhigh-cardadapters. - Move frontend to
/games/*registry and routes. - Split service layers.
Week 3:
- Add
tic-tac-toeanddice-hi-loadapters. - Add shared frontend hooks/components.
- Expand E2E matrix and ER regressions.
Week 4:
- Add simplified market accounts/instructions.
- Add
/games/predictionUI flow. - Stabilize, polish, and demo-hardening.
Recommended lineup:
battleship: flagship hidden-state game and ER showcase.coinflip-duel: fastest low-risk game for throughput.high-card: simple PvP with strong wagering intuition.tic-tac-toe: familiar turn-based benchmark.dice-hi-lo: quick risk/reward loop and replayability.
Why this set:
- Mixes hidden-state, turn-based, and instant games.
- Good demo coverage for architecture flexibility.
- Balanced implementation complexity for a duo.
Guideline:
- Keep adapter errors precise, but map to concise user language.
- Include shared errors for lifecycle and wagering constraints.
Examples of user-facing mapping categories:
- session state invalid.
- unauthorized actor.
- invalid action format.
- not your turn.
- market not resolvable.
Hot paths likely to need optimization:
submit_actiondispatch and state decode/encode.- settle path with ER commit/undelegate.
- market payout scans.
Controls:
- Keep blobs compact.
- Avoid unnecessary vector growth.
- Profile adapter-specific CU costs.
- Prefer fixed-size or bounded structures where feasible.
Key concerns:
- Blob decode safety and version checks.
- Outcome tampering between session settle and market resolve.
- Unauthorized private-state reads during active sessions.
- Re-entrancy-like sequencing mistakes in settlement/claim logic.
Controls:
- strict state versioning.
- deterministic result-code mapping.
- enforce session terminal status before market resolve.
- comprehensive negative-path tests.
When an agent starts work:
- Read this file first.
- Confirm target is still single-program modular architecture.
- Verify routes are
/games/*. - Check for latest schema names in state/instructions before coding.
When proposing changes:
- Preserve core-vs-game-vs-market boundaries.
- Avoid introducing game-specific fields into core accounts.
- Keep generated client usage inside service wrappers.
- Add tests in the right layer.
When reviewing PRs:
- Ask whether change belongs in core or adapter.
- Check if new game can be added without changing core lifecycle logic.
- Check if frontend uses registry rather than route hardcoding.
- Check if result_code mapping remains stable for market settlement.
- Adding new game-specific fields directly to
Sessionfor one game only. - Hardcoding game routes in
App.tsxrepeatedly. - Embedding Codama generated builders inside UI pages.
- Implementing ER permission logic separately per game module.
- Expanding market scope to orderbook/AMM in this sprint.
On-chain:
- Add
GameTypeenum andSessionaccount. - Add
PlayerStateand migrate from board-specific assumptions. - Add adapter trait and Battleship adapter implementation.
- Introduce
submit_actionandsettle_session. - Add market accounts and instructions with simplified constraints.
Frontend:
- Add
web/src/games/registry.ts. - Migrate routing to
/games/:slugand/games/:slug/:sessionId. - Move battleship code under
web/src/games/battleship. - Create game scaffolds for the other four games.
- Add
/games/predictionflow.
Testing:
- Split monolithic tests into harness + per-game + market + smoke.
- Add ER privacy regressions for each hidden/private-state game.
Highest priority on-chain files:
programs/cayed/src/lib.rsprograms/cayed/src/state/game.rsto be replaced bysession.rsprograms/cayed/src/state/player_board.rsto evolve towardplayer_state.rsprograms/cayed/src/instructions/make_move.rsto evolve towardsubmit_action.rsprograms/cayed/src/instructions/reveal_winner.rsto evolve towardsettle_session.rs
Highest priority frontend files:
web/src/App.tsxweb/src/main.tsxweb/src/services/game-service.tsweb/src/pages/battleship/*to migrate underweb/src/games/battleship/*web/src/games/registry.tsnew
- Which simplified payout model first: pari-mutuel-lite or fixed-odds?
- Should single-player games be allowed in v1 market, or only PvP sessions?
- What is the exact maximum
state_blobsize per game to cap compute/storage? - Which game-specific fields belong in
Sessionversus encoded state blob?
If unresolved, default choices:
- pari-mutuel-lite for simple fairness and no external odds dependency.
- market enabled first for PvP sessions only.
- conservative capped blob size with adapter-level validation.
- keep
Sessionminimal and adapter state in blob.
Cayed should feel like an arcade first:
- fast loops.
- clean wager UX.
- trustless settlement.
- prediction as optional enhancement.
If an implementation choice improves technical purity but hurts this consumer feel or timeline, prefer the pragmatic option that ships within 28 days.