Welcome to Cougr
Cougr is an on-chain game engine built for the Stellar network and Soroban smart contracts. It provides a full Entity-Component-System (ECS) runtime alongside account abstraction and zero-knowledge primitives in a single crate.
This documentation site is generated automatically from the salazarsebas/Cougr repository.
Start
Welcome to the Start section. If you're new to Cougr, this is the right place to begin.
- Getting Started - install the toolchain and run your first test in under two minutes.
- Build Your First Game - a step-by-step walkthrough from an empty directory to a deployed game on Stellar Testnet.
Getting Started
This page is synced from the main Cougr repository. To edit it, open a PR against
salazarsebas/Cougr.
Prerequisites
Before you can build a Cougr game, you need the following tools installed:
| Tool | Install command |
|---|---|
| Rust | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
| Stellar CLI | cargo install stellar-cli --features opt |
| wasm32 target | rustup target add wasm32-unknown-unknown |
Clone and run the starter example
git clone https://github.com/salazarsebas/Cougr
cd Cougr/examples/spawn_and_move
cargo test
All tests should pass within two minutes on a machine that has Rust installed.
Next step
Once the tests pass, head over to Build Your First Game for the full sequential tutorial.
Build Your First Game
Welcome to Cougr! This tutorial will take you from an empty directory to a fully tested, testnet-deployed game.
This guide is designed for developers who already know Rust but have no prior experience with Cougr, Soroban, or Stellar. By the end, you'll understand how Cougr's Entity-Component-System (ECS) architecture translates into safe, efficient smart contracts.
We are going to build a simple 2D grid game where a player can spawn into the world and walk in four directions.
1. Project Setup
Since we're building a smart contract, we start with a standard Rust library rather than a binary.
cargo new --lib my_first_game
cd my_first_game
Add cougr-core and the soroban-sdk to your Cargo.toml. We enable the testutils feature to get access to Cougr's built-in testing harness later.
[package]
name = "my_first_game"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
soroban-sdk = "25.3.2"
cougr-core = "1.1.0" # or the latest version from crates.io
[features]
testutils = ["soroban-sdk/testutils", "cougr-core/testutils"]
2. The Mental Model: ECS on Soroban
If you've used an ECS in a game engine like Bevy or Unity, you already know the basics: Entities are just IDs, Components hold data, and Systems run logic.
However, building for a blockchain introduces new constraints you must consider:
[!WARNING] Soroban-Specific Constraints
- Storage is not a normal database: You cannot freely iterate over millions of rows. State must be loaded into memory, modified, and saved back efficiently.
- Execution costs money: Every instruction, memory allocation, and storage write costs "gas". Infinite loops or massive arrays will cause your transaction to exceed resource limits and fail.
- Instance Storage vs Persistent Storage: Cougr uses Soroban's "Instance Storage" by default for your hot-loop game state. This means all active gameplay components are loaded in a single read, making operations extremely cheap and fast, but it requires you to be mindful of total state size.
Cougr abstracts the heavy lifting of storage management, but you still need to write code with these constraints in mind.
3. Defining Components
Let's open src/lib.rs. First, we clear out the default code and define our game's data.
We need two components: a Position to track where the player is, and Moves to track how many steps they have left.
#![no_std]
use cougr_core::game::SorobanGame;
use cougr_core::{impl_component, impl_component_observed, impl_soroban_game};
use soroban_sdk::{contract, contractimpl, contracttype, Env};
// `Position` emits an indexed event on every change (so a UI can watch it)
#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct Position {
pub x: i32,
pub y: i32,
}
impl_component_observed!(Position, "position", Table, { x: i32, y: i32 });
// `Moves` is kept private; it doesn't need to emit an event on every step
#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub struct Moves {
pub remaining: u32,
}
impl_component!(Moves, "moves", Table, { remaining: u32 });
Notice the two different macros:
impl_component_observed!tells Cougr to emit a Soroban event every time this component is modified. This is crucial for off-chain clients (like your web frontend) to track movement in real-time without polling the blockchain.impl_component!is for standard data that doesn't need to be broadcasted to indexers, saving you gas on event emissions.
4. Writing the Game Contract (Systems)
Next, we define our contract. In Cougr, the contract acts as the outer shell that loads the ECS world, runs your system logic (the functions), and saves the world back.
Add this to the bottom of src/lib.rs:
#[contract]
#[derive(Clone)]
pub struct MyFirstGame;
// This macro wires up the `load_world` and `save_world` boilerplate.
impl_soroban_game!(MyFirstGame, "world");
#[contractimpl]
impl MyFirstGame {
/// Spawns a new player entity into the world.
pub fn spawn(env: Env) -> u32 {
// 1. Load the ECS world from Soroban storage
let mut world = MyFirstGame::load_world(&env);
// 2. Spawn an entity and attach our components
let entity = world.spawn_entity();
world.set_typed_observed(&env, entity, &Position { x: 0, y: 0 });
world.set_typed(&env, entity, &Moves { remaining: 10 });
// 3. Save the ECS world back to Soroban storage
MyFirstGame::save_world(&env, &world);
entity
}
/// Moves a player entity in a given direction (0=North, 1=East, 2=South, 3=West).
pub fn move_entity(env: Env, entity_id: u32, direction: u32) {
let mut world = MyFirstGame::load_world(&env);
let mut pos = world.get_typed::<Position>(&env, entity_id).unwrap();
let mut moves = world.get_typed::<Moves>(&env, entity_id).unwrap();
if moves.remaining == 0 {
panic!("no moves left");
}
match direction {
0 => pos.y += 1, // North
1 => pos.x += 1, // East
2 => pos.y -= 1, // South
3 => pos.x -= 1, // West
_ => panic!("invalid direction"),
}
moves.remaining -= 1;
// Apply changes
world.set_typed_observed(&env, entity_id, &pos);
world.set_typed(&env, entity_id, &moves);
MyFirstGame::save_world(&env, &world);
}
/// Query a player's current position.
pub fn get_position(env: Env, entity_id: u32) -> Option<Position> {
let world = MyFirstGame::load_world(&env);
world.get_typed::<Position>(&env, entity_id)
}
}
This pattern - Load World -> Query/Modify -> Save World - is the backbone of every Cougr contract entry point.
5. Local Testing
Testing smart contracts on an actual network is slow. Cougr provides a powerful local GameHarness to run tests instantly in memory.
Create a new file src/test.rs and add it to your module tree by adding #[cfg(test)] mod test; to the very bottom of src/lib.rs.
In src/test.rs:
#![cfg(test)]
use super::*;
use cougr_core::test::{GameHarness, Scenario};
use soroban_sdk::Env;
#[test]
fn test_spawn_and_move() {
let env = Env::default();
// Register our contract using Cougr's test harness
let harness = GameHarness::new(env, MyFirstGame);
// The macro generated a "MyFirstGameClient" for us automatically
let client = MyFirstGameClient::new(harness.env(), harness.contract_id());
// 1. Spawn the entity
let entity_id = client.spawn();
let pos = client.get_position(&entity_id).unwrap();
assert_eq!(pos.x, 0);
assert_eq!(pos.y, 0);
// 2. Use Cougr's Scenario builder to simulate turns/moves
Scenario::new("move north")
.turns(1)
.run(&harness, |_player, _turn, h| {
let c = MyFirstGameClient::new(h.env(), h.contract_id());
// Move North (direction = 0)
c.move_entity(&entity_id, &0);
let pos = c.get_position(&entity_id).unwrap();
assert_eq!(pos.x, 0);
assert_eq!(pos.y, 1);
});
}
Run your tests to verify your game works locally:
cargo test
If it passes, you are ready to deploy!
6. Deploying to Testnet
To deploy, we need to compile our game to a WebAssembly (WASM) binary and use the Stellar CLI.
[!TIP] If you haven't installed the Stellar CLI yet, check out the Stellar Quickstart.
1. Build the WASM file:
cargo build --target wasm32-unknown-unknown --release
Your compiled game is now located at target/wasm32-unknown-unknown/release/my_first_game.wasm.
2. Configure your testnet identity:
stellar keys generate alice --network testnet
3. Deploy the contract:
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/my_first_game.wasm \
--source alice \
--network testnet
If successful, the CLI will return a C... contract address. Congratulations! Your game is live on the Stellar Testnet.
7. Next Steps
You've built and deployed a basic ECS contract. However, real games require more advanced mechanics like access control, hidden information (Fog of War), or multi-contract plugin architectures.
- Learn the architecture: Read Cougr Patterns to understand how to structure larger, production-ready games.
- Get inspired: Browse the Showcase (like
murdokuorbattleship) in theexamples/directory to see full-stack implementations.
Learn
The Learn section takes you from a working first game to understanding the design decisions that make Cougr work the way it does.
| Guide | Status |
|---|---|
| Architecture | β Available |
| Game Patterns | β Available |
| On-Chain / Off-Chain Boundary Guide | π Coming soon |
| Smart Contract Patterns | π Coming soon |
| Testing Guide | π Coming soon |
| Deployment Guide | π Coming soon |
Architecture
High-level overview of how Cougr is organized. For usage, see README.md.
Layers
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β game::SorobanGame (contract integration) β Contract layer
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β app::GameApp β Default runtime surface
βββββββββββββ¬ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ€
β ECS β Accounts β Standards β ZK Proofs β
βββββββββββββ΄ββββββββββββββββ΄ββββββββββββββββββ΄βββββββββββββββββ€
β soroban-sdk 25.1.0 (no_std, WASM) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
game::SorobanGame (src/game.rs) bridges the ECS and Soroban contract models.
The SorobanGame trait provides load_world and save_world as default methods,
eliminating repetitive storage-key boilerplate from contract entrypoints. Wire up
once with impl_soroban_game!(MyContract, "key").
The companion helpers SimpleWorld::load_from_instance and save_to_instance
are the underlying primitives when you want finer control.
GameApp (src/plugin/mod.rs) is the default onboarding layer for complex
games. It owns a SimpleWorld, the scheduler, plugin registration, and runtime
resources in one place.
ECS
Two storage backends, same ComponentTrait interface:
| Backend | File | Strategy | Best for |
|---|---|---|---|
| SimpleWorld | src/simple_world/ | Map<(EntityId, Symbol), Bytes> with dual Table/Sparse indexes | General use, small entity counts |
| ArchetypeWorld | src/archetype_world/ | Groups entities by component signature | Large entity counts, batch queries |
Both support typed access (get_typed<T>, set_typed<T>) and raw access (get_component, add_component).
Supporting systems:
- Query cache (
src/query/) - version-tagged, invalidates on world mutation - Hooks (
src/hooks.rs) - callbacks on component add/remove - Observers (
src/observers.rs) - event-driven reactions - Commands (
src/commands.rs) - deferred mutations during system execution - Scheduler (
src/scheduler/) - stage-based, dependency-aware system ordering - Change tracker (
src/change_tracker.rs) - per-component dirty flags - Plugins (
src/plugin/) - modular game logic bundles - Incremental storage (
src/incremental/) - only persist dirty entities
Component definition
Three macros cover every component case:
| Macro | When to use |
|---|---|
impl_component! | Fixed-size primitives (i32, u32, u64, u128, u8, bool, bytes32) |
impl_component_observed! | Same as above, plus structured Soroban events on every set |
impl_rich_component! | Complex types via XDR codec: Address, Vec, String, Option, nested structs |
impl_rich_component! requires #[contracttype] on the struct. The XDR serialisation is handled entirely by the Soroban SDK - no manual serialize/deserialize implementation is needed.
Rich components are stored in Soroban instance storage (not the ECS Map) but share the same entity ID space.
ZK Proofs (src/zk/)
All ZK operations use Stellar Protocol 25 (X-Ray) host functions - the heavy crypto runs on the host, not in WASM.
- Groth16 (
groth16.rs) - proof verification via BN254 pairing - BLS12-381 (
bls12_381.rs) - G1 add/mul/MSM, pairing checks - Poseidon2 (
crypto.rs) - ZK-friendly hashing, behindhazmat-cryptofeature - Merkle trees (
merkle/) - SHA256 and Poseidon variants, sparse trees, on-chain proofs - Pedersen (
commitment.rs) - commitment scheme for hidden state - Game circuits (
circuits.rs,traits.rs) -GameCircuittrait + pre-built circuits (Movement, Combat, Inventory, TurnSequence) +CustomCircuitBuilder - ECS integration (
components.rs,systems.rs) -CommitReveal,HiddenState,ProofSubmissioncomponents with verification systems
Accounts (src/accounts/)
Account abstraction layer with pluggable implementations:
CougrAccount (trait)
βββ ClassicAccount - standard Stellar keypair
βββ ContractAccount - smart contract wallet
βββ SessionStorage - persistent session keys
βββ RecoveryStorage - guardian-based recovery
βββ DeviceStorage - multi-device key management
βββ Secp256r1Storage - WebAuthn/Passkey keys
Key traits: CougrAccount, SessionKeyProvider, RecoveryProvider, MultiDeviceProvider.
SessionBuilder provides a fluent API for constructing scoped session keys. authorize_with_fallback handles graceful degradation from session keys to direct authorization. See ADR 0005.
Standards (src/standards/)
Reusable contract standards for integrations that need explicit operational controls:
OwnableandOwnable2Stepfor owner-managed authorityAccessControlfor role-based authorization with delegated adminsPausablefor emergency stopsExecutionGuardfor serialized critical sectionsRecoveryGuardfor blocking sensitive paths during recovery windowsBatchExecutorfor bounded multi-operation flowsDelayedExecutionPolicyfor time-delayed operation queues
Each standard instance is keyed by a caller-supplied Symbol, which keeps storage deterministic and avoids collisions when a contract composes multiple modules.
Competitive Layers (workspace subcrates)
Three layers ship inside the single cougr-core crate. Implementation lives in
src/{circuits,session,test}/; internal/cougr-core-* workspace members use
stubs for isolated cargo check -p runs.
| Public module | Source | Maturity | Feature |
|---|---|---|---|
cougr_core::circuits | src/circuits/ | Experimental | always |
cougr_core::session | src/session/ | Beta | always |
cougr_core::test | src/test/ | Beta | testutils |
Circuit builders: hidden_cards, fog_of_war, fair_dice, sealed_bid β
GameCircuitSpec. Examples: hidden_hand, fog_explorer, dice_duel,
blind_auction. See ADR 0006.
The test sandbox uses no_std + alloc with Soroban testutils - not std.
Enable with cougr-core feature testutils. Modules: GameHarness, Scenario,
WorldFixture, ReplayLog, SnapshotAssert. See ADR 0004 and ADR 0007.
Feature Flags
| Flag | Enables |
|---|---|
hazmat-crypto | Poseidon2 hash, BN254 curve ops (via soroban-sdk/hazmat-crypto) |
testutils | cougr_core::test sandbox, MockAccount, Soroban test helpers |
debug | Runtime introspection, metrics, state snapshots (src/debug/) |
Build
Release builds are configured with LTO, opt-level = "z", and overflow-checks = true to keep artifacts optimized for constrained execution environments.
Primary target: wasm32v1-none.
Cougr Patterns
Purpose
This document captures the recommended architectural patterns for new Soroban game contracts built on Cougr.
The goal is to standardize how teams structure worlds, systems, stages, and storage choices instead of relying on ad-hoc example interpretation.
Find a pattern by problem
Start here if you know what you're trying to build but not which Cougr module answers it. Each row links to the module-level doc for full detail, and to a concrete, current example that demonstrates it.
| I want... | Use | Example | Read more |
|---|---|---|---|
| Fairness (a roll, a draw, an outcome no one can predict or bias) | circuits::FairDiceBuilder - on-chain Groth16-verified randomness (Experimental) | dice_duel | Hidden Information Guidance below, PRIVACY_MODEL.md |
| Hidden information (cards, ship positions, sealed bids - state some players shouldn't see) | privacy::stable commit-reveal + Merkle primitives | battleship (canonical), rock_paper_scissors, hidden_hand, blind_auction | Hidden Information Guidance below, PRIVACY_MODEL.md |
| To gate an action behind a role (admin-only, minter-only, etc.) | AccessControl - role-based authorization with per-role admin delegation | - | STANDARDS_LAYER.md Β§ AccessControl |
| Off-chain-friendly real-time movement (clients track state without polling) | impl_component_observed! - emits a (COUGR, set, <component>) event on every change | spawn_and_move (start here), snake | System Design and Query Guidance below, docs/ECS_CORE.md |
| A passwordless sign-in (Face ID / Touch ID instead of a seed phrase) | secp256r1 passkey signer, composed through AccountKernel | guild_arena | ACCOUNT_KERNEL.md Β§ Signers |
| A session players approve once, not per-transaction | Session signer + SessionPolicy (scope, expiry, operation budget) | session_arena | ACCOUNT_KERNEL.md Β§ Session Model |
| Account recovery if a device is lost | GuardianPolicy + ActiveDevicePolicy | guild_arena | ACCOUNT_KERNEL.md Β§ Policies |
| An emergency stop / pause switch | Pausable | - | STANDARDS_LAYER.md Β§ Pausable |
| To serialize mutations / guard against reentrancy-like issues | ExecutionGuard | - | STANDARDS_LAYER.md Β§ ExecutionGuard |
| Delayed or timelocked execution | DelayedExecutionPolicy | - | STANDARDS_LAYER.md Β§ DelayedExecutionPolicy |
| To batch several operations safely | BatchExecutor | - | STANDARDS_LAYER.md Β§ BatchExecutor |
| To decide what belongs on-chain at all (which state and rules justify their cost, and which should stay client-side) | The five-question boundary framework, applied per piece of state | battleship, snake, blind_auction | ONCHAIN_OFFCHAIN_BOUNDARY.md |
| To know whether I even need ECS | Direct contract model for small/config-driven contracts | - | When Not To Use ECS below |
| To pick table vs. sparse storage | Table for hot-loop state, sparse for infrequent markers | - | Storage Guidance below |
| A thin, explicit contract entrypoint / gameplay loop | GameApp + explicit stage placement | spawn_and_move, snake | Default Entry Point and Stage Layout below |
Everything below this point is the module-level architectural guidance the table above links into - organized by Cougr's internal structure rather than by problem, for readers who already know which area they're working in.
Default Entry Point
Use GameApp as the default runtime entrypoint.
Recommended shape:
- build the app
- register plugins and startup systems
- register tick systems into explicit stages, preferably with
named_system(...)/named_context_system(...) - run one schedule tick per contract invocation that advances gameplay
This keeps the "contract entrypoint" thin and the gameplay loop explicit.
Stage Layout
Cougr's recommended schedule is:
Startup: one-time entity/resource setupPreUpdate: input decoding, action validation, turn preparationUpdate: core gameplay state transitionsPostUpdate: scoring, derived-state maintenance, indexing side effectsCleanup: despawns, expiry handling, transient marker removal
Do not use cross-stage before / after dependencies. Stage order is already the primary contract between phases.
System Design
Prefer small systems with one responsibility:
- validation systems should reject or mark invalid intent
- update systems should apply game-state transitions
- cleanup systems should remove expired markers or entities
Use context-aware systems when you need deferred structural changes:
- queue spawns during iteration
- queue despawns after collision passes
- queue marker additions that should apply after the current scan
Use plain world/env systems when the system only needs direct mutation and no command buffering.
Query Guidance
Prefer SimpleQueryBuilder for gameplay queries that need:
- multiple required components
- negative filters
- sparse-component inclusion
- "any-of" matching
Guidelines:
- default to table-only queries for tight loops
- opt into sparse inclusion only when marker/tag data must participate
- choose required components carefully so the scheduler can use the narrowest candidate set
Hidden Information Guidance
For hidden-state or commit-reveal contracts:
- keep the contract entrypoints thin and verification-oriented
- use
privacy::stableMerkle and commit-reveal primitives instead of example-local crypto formats - treat proof verification as a boundary concern, not as something every gameplay system needs to understand
- keep public derived state separate from private commitments and Merkle roots
battleship is the canonical reference for this pattern.
Storage Guidance
Use table storage for:
- frequently scanned gameplay state
- canonical state that participates in core loops
- components used by
Updatesystems on most ticks
Use sparse storage for:
- infrequent markers
- administrative tags
- components mostly accessed by targeted lookups instead of broad scans
If a component becomes part of the hot loop, move it to table storage instead of compensating with more complex query logic.
Recommended Separation
Keep modules separated by concern:
- ECS/gameplay core
- account/auth flows
- privacy/ZK
- standards/operational controls
Do not let auth or ZK concerns leak into every system by default. Compose them at the boundaries where they are needed.
When Not To Use ECS
Do not force ECS into contracts that are:
- tiny and single-entity
- mostly configuration/state-machine driven
- dominated by one-off administrative flows
If the problem is closer to a fixed state machine than a world simulation, a direct contract model may be simpler and cheaper.
On-Chain / Off-Chain Boundary Guide
β³ This page is being written.
Named as the second-highest-priority missing document in
docs/strategy/08-ux-strategy.mdanddocs/strategy/12-documentation-architecture.md.Tracked in:
salazarsebas/Cougrissues
What this guide will cover
Developers coming from traditional game development or web backends often hit the same wall: what logic actually needs to be on-chain, and what doesn't? Getting this wrong is expensive - literally, in gas fees.
This guide will answer:
- Which game state must live in Soroban contract storage vs. what can stay off-chain
- How Cougr's observed components (
impl_component_observed!) bridge the gap with real-time events - Patterns for off-chain movement with on-chain settlement
- Resource cost intuition: what makes a transaction cheap vs. expensive
Check back soon - or watch the repository to be notified when this page goes live.
Smart Contract Patterns
β³ This page is being written.
Will consolidate Soroban-specific patterns from
PATTERNS.mdandSTANDARDS_LAYER.mdinto one place, distinguishing "Cougr patterns" from "general Soroban patterns."Tracked in:
salazarsebas/Cougrissues
What this guide will cover
- Access control with
AccessControlandOwnable - Emergency stop patterns with
Pausable - Time-delayed operations with
DelayedExecutionPolicy - Batch operations with
BatchExecutor - How these standards compose with the ECS world
Check back soon - or watch the repository.
Testing Guide
β³ This page is being written.
GameHarness,Scenario, andSnapshotAssertexist in the codebase and are used across thousands of lines of tests, but have no standalone guide yet. Named explicitly indocs/strategy/08-ux-strategy.mdStage 5.Tracked in:
salazarsebas/Cougrissues
What this guide will cover
- Setting up
GameHarnessfor unit tests - Writing
Scenario-based integration tests - Using
SnapshotAssertto lock in expected world state - How the Soroban test sandbox works (and how it differs from running
cargo testfor a normal library) - Estimating resource costs before you deploy
Check back soon - or watch the repository.
Deployment Guide
β³ This page is being written.
The README has dev commands, but there's no standalone guide walking through production deployment to Stellar Testnet and Mainnet.
Tracked in:
salazarsebas/Cougrissues
What this guide will cover
- Configuring the Stellar CLI for Testnet and Mainnet
- Building a release
.wasmbinary with the correct flags (LTO,opt-level = "z") - Deploying with
stellar contract deploy - Understanding what a deployment costs (resource fees)
- Upgrading a contract after deploy
Check back soon - or watch the repository.
Reference
The Reference section contains low-level documentation for Cougr's modules, the API surface, and architectural decision records.
| Document | Description |
|---|---|
| ECS Core | The core ECS primitives: Entity, Component, Query, System, Scheduler |
| Account Kernel | Account abstraction layer - session keys, recovery, passkeys |
| Standards Layer | Reusable contract standards: AccessControl, Pausable, Ownable, etc. |
| Privacy Model | ZK proofs, Pedersen commitments, hidden state |
| Feature Flags | hazmat-crypto, testutils, debug - what each enables |
| Performance Guide | Resource cost intuition, benchmarks, optimization patterns |
| API Contract | Public API guarantees and stability promises |
| Compatibility Promises | What Cougr will and won't break between releases |
| Migration Guide | How to update your game for new cougr-core versions |
| CLI Reference | π Ships with the CLI |
| Client SDK Reference | π Ships with the TypeScript SDK |
| ADRs | Architecture Decision Records |
Cougr ECS Core
Purpose
This document defines the defended conceptual model for Cougr's ECS runtime.
It is the answer to "what are the actual core primitives?" and "which path is the one new users should learn first?"
Core Model
The stable conceptual model is:
Entity: an opaque runtime identityComponent: typed or raw data attached to entitiesQuery: a declarative selection over entities by component presenceSystem: logic that reads or mutates the worldCommandQueue: deferred structural mutationsGameApp: app-level orchestration over world + scheduler + pluginsRuntimeWorld/RuntimeWorldMut: the shared backend contract for Soroban-first worlds
For Soroban gameplay contracts, the recommended path is:
appSimpleWorldSimpleQuerySimpleSchedulerGameApp
ArchetypeWorld is the alternate backend for heavier query workloads.
The shared stable overlap between those backends lives in:
ecs::RuntimeWorldecs::RuntimeWorldMut
Backend Roles
SimpleWorld
Use when:
- entity counts are modest
- table-backed scans dominate
- operational simplicity matters more than archetype migration costs
Cost profile:
- cheap add/remove/update
- indexed table and all-storage component lookups
- predictable query path for common gameplay loops
ArchetypeWorld
Use when:
- multi-component queries dominate
- entity composition is relatively stable
- migration cost is acceptable in exchange for tighter query scopes
Cost profile:
- more expensive structural changes
- more selective scans for multi-component queries
Learnability Rule
A new user should be able to learn the main Cougr runtime from:
README.mdGameAppSimpleWorldSimpleQueryBuilder- one or two canonical examples
If a concept requires diving outside the Soroban-first runtime path to understand basic gameplay flow, that is a product bug.
Account Kernel
Purpose
The goal is to make authorization explicit, modular, and replay-safe while keeping the accounts namespace outside Cougr's frozen 1.0 stable contract.
Core Model
The account subsystem is now organized around:
AccountKernel- the orchestrator that runs signer verification, policy checks, and replay protection
- signer interfaces
AccountSigner- base implementations: direct owner auth, session auth, secp256r1 passkey auth
- policy interfaces
- generic
Policy<C> - base implementations for intent expiry, session enforcement, active device checks, and guardian checks
- generic
- signed intent schema
SignedIntent,SignerRef,IntentProof
- structured auth results
AuthResult,AuthMethod
Signed Intent Schema
SignedIntent binds:
- target account
- signer reference
- action payload
- nonce
- expiry
- deterministic
action_hash - proof material
The deterministic hash is derived from:
- nonce
- expiry
- signer identity fields
- action system name
- action bytes
Replay Protection
Cougr uses two replay domains:
- per-account nonce tracking for direct owner auth and passkey auth
- per-session nonce tracking for session intents
The replay implementation lives in:
Session Model
Session state now includes:
- unique
key_id - scoped allowed actions
- operation budget
- expiration timestamp
next_nonce
Session enforcement requires all of:
- session exists
- action is in scope
- session is not expired
- operation budget remains
- intent nonce matches
next_nonce
On success the session consumes one operation and advances next_nonce.
Signers
Current base signer implementations:
- direct owner signer
- uses
require_auth
- uses
- session signer
- explicit non-fallback session path evaluated by the kernel
- secp256r1 passkey signer
- verifies signatures against registered passkeys
Policies
The policy layer is intentionally reusable across account features.
Current base policies:
IntentExpiryPolicySessionPolicyActiveDevicePolicyGuardianPolicy
This is how device and recovery support now live under the same policy model instead of ad hoc checks.
Auth Results
AuthResult returns structured information instead of only Result<(), AccountError>.
Current fields:
- method used
- nonce consumed
- session key id, when applicable
- remaining operations, when applicable
Integration Note
The account kernel is now consumed through the curated accounts / auth
surface directly.
The previous GameWorld wrapper was removed so 1.0.0 does not freeze an
extra orchestration layer. Authorization should be composed explicitly at the
application layer around GameApp, SimpleWorld, and the account primitives.
Standards Layer
Purpose
The standards layer introduces reusable, storage-aware contract primitives in the style of OpenZeppelin building blocks, but shaped for Cougr's Soroban-oriented single-crate model.
These modules are meant to be composed into application contracts and account flows without depending on any example project.
Included Standards
Ownable
- single-owner access primitive
- explicit initialization
- direct transfer and renounce flows
- typed ownership transition events
Ownable2Step
- staged ownership handoff
- pending-owner tracking in storage
- explicit acceptance requirement before ownership changes
- cancellation support for abandoned handoffs
AccessControl
- role-based authorization keyed by
Symbol - per-role admin delegation
- explicit grant, revoke, and renounce semantics
- default admin role for bootstrapping new modules
Pausable
- storage-backed emergency stop flag
- explicit paused and unpaused transitions
- guard methods for mutating entrypoints
ExecutionGuard
- storage-backed execution lock
- suited for reentrancy-like protection and mutation serialization
- can be used as explicit enter/exit calls or as a scoped closure wrapper
RecoveryGuard
- blocks sensitive flows while a recovery window is active
- generic enough to compose with account recovery or application-defined incident response
BatchExecutor
- reusable batch length validation
- single-path execution semantics for collections of operations
- explicit empty and oversize rejection
DelayedExecutionPolicy
- storage-backed delayed operation queue
- deterministic operation IDs
- readiness and expiry checks
- cancellation and execution events
Storage and Namespacing
Each standards module is instantiated with a Symbol identifier.
That identifier becomes part of the storage key, which allows a single contract to host multiple independent instances of the same standard without collisions.
Authorization Model
These modules do not assume hidden caller semantics.
Where authorization matters:
OwnableandOwnable2Steprequire an explicit caller addressAccessControlchecks the caller against the relevant admin rolePausable,RecoveryGuard, and similar state machines leave the surrounding authorization decision to the integrating contract
This is intentional. Cougr keeps authorization visible at the integration boundary instead of burying it in generic helpers.
Error Semantics
The standards layer uses StandardsError for consistent negative-path behavior across integrations.
Important failure modes include:
- unauthorized caller
- duplicate initialization
- missing or mismatched pending owner
- duplicate role grant or missing role during revoke
- paused versus not-paused guard failures
- execution lock contention
- recovery-active guard failure
- empty or oversized batches
- delayed operation not ready, expired, already executed, or missing
Maturity
Status: Stable
The standards layer is part of Cougr's frozen 1.0 stable contract. Integrators should still supply their own caller-auth composition where required, but the module interfaces and documented failure semantics are now part of the defended public surface.
Cougr Privacy Model
Purpose
This document defines Cougr's privacy and proof-verification contract after the 1.0 release gate.
Its job is to separate the stable privacy subset from experimental proof systems so that the repository can make a smaller, stronger claim about what is safe to depend on in the stable contract.
Stable Privacy Surface
The stable privacy subset in Cougr is:
- commitments
- commit-reveal flows
- hidden-state encoding interfaces
- Merkle inclusion verification
- sparse Merkle utilities
- privacy interfaces:
CommitmentSchemeMerkleProofVerifierHiddenStateCodecProofVerifieras an interface contract only
These are exposed through:
cougr_core::privacy::stablecougr_core::zk::stableas the compatibility alias
Experimental Privacy Surface
The following remain Experimental:
- Groth16 proof verification flows
- proof-submission execution helpers
- prebuilt verification circuits
- fog-of-war Merkle exploration orchestration
- multiplayer ZK state-channel transition contracts
- recursive proof-composition descriptors
- advanced hidden-state automation
- hazmat Poseidon-based privacy helpers
- broader confidential-state abstractions
These are exposed through:
cougr_core::privacy::experimentalcougr_core::zk::experimentalas the compatibility alias
Compatibility note:
Experimental modules may still be re-exported from cougr_core::zk for transition
convenience, but they are not part of Cougr's stable privacy promise. New application
code should prefer cougr_core::privacy::experimental so the product-level intent is
obvious at the import site.
1.0 Privacy Freeze
The frozen 1.0 privacy contract is exactly:
- commitments
- commit-reveal flows
- hidden-state codec interfaces
- Merkle inclusion verification
- sparse Merkle utilities
- the interface contracts re-exported from
cougr_core::privacy::stable
The following are explicitly excluded from the 1.0 stable privacy contract:
zk::experimental- proof-submission orchestration that depends on experimental verification
- Groth16 verifier implementations
- prebuilt advanced circuit helpers
- state-channel, recursive, and fog-of-war orchestration helpers
Privacy Maturity Table
| Surface | Status | Notes |
|---|---|---|
| Commitments | Stable | Explicit interface and verification contract |
| Commit-reveal | Stable | Explicit component semantics and deadline behavior |
| Hidden-state encoding | Stable | Stable codec interface; fixed-width codecs can be defended |
| Merkle inclusion and sparse Merkle utilities | Stable | Malformed proof behavior and inclusion semantics are explicit |
| Proof submission systems | Beta | Useful orchestration, but still coupled to experimental verification flows |
| Groth16 verification and prebuilt circuits | Experimental | Assumptions are explicit, but not yet strong enough for a stable promise |
Proof Verification Contract
Cougr's experimental Groth16 verifier makes these explicit guarantees:
- verification keys must satisfy
vk.ic.len() == public_inputs.len() + 1 - malformed verification-key shape returns
ZKError::InvalidVerificationKey - malformed pairing inputs return
ZKError::InvalidInput - a well-formed but invalid proof returns
Ok(false)only when the pairing check fails
Cougr does not currently claim stronger guarantees for Groth16 around:
- subgroup validation beyond Soroban host-type decoding
- normalization guarantees beyond fixed-width typed wrappers
- broader proof-system maturity for production confidentiality claims
That is why the implementation remains Experimental even though the verifier interface is explicit.
Merkle Verification Contract
Cougr's stable Merkle verification guarantees:
- malformed proofs with
siblings.len() != depthreturnZKError::InvalidProofLength - well-formed but non-matching proofs return
Ok(false) - sparse Merkle utilities produce the same on-chain proof representation used by the stable SHA256 verifier
Hidden-State Encoding Contract
Stable hidden-state codecs must:
- define an exact byte-level representation
- reject malformed encoded state with
ZKError::InvalidInput - avoid silent truncation or padding
The built-in Bytes32HiddenStateCodec satisfies this by requiring an exact
32-byte payload in both directions.
Relationship to Public Surface
This model works with:
Any future claim that advanced proof verification is Stable should add stronger input-validation guarantees, clearer host-assumption boundaries, and tighter negative-path coverage than exists today.
Cougr Feature Flags
Purpose
This document groups Cougr feature flags by maturity and intended usage.
Current Flags
| Flag | Maturity | Intended use | Notes |
|---|---|---|---|
debug | Support-only | Local diagnostics and introspection | Exposes runtime snapshots and metrics that are not part of the stable product contract |
hazmat-crypto | Experimental | Advanced ZK and cryptographic integrations | Enables low-level host crypto helpers; do not treat as part of the stable privacy promise |
testutils | Non-contract support surface | Tests and explicit test-utility consumers | Enables testing helpers such as MockAccount |
Policy
- feature flags do not automatically promote a surface into the stable contract
- test-only or support-only flags remain outside compatibility guarantees
- new security-sensitive flags should default to Beta or Experimental until their contracts are written down
Relationship To Public Surface
The maturity of the feature flag should be interpreted together with:
In the product-level facade:
authmirrors the Betaaccountssurfaceprivacy::stableandprivacy::experimentalmirror the split insidezkopsmirrors the stablestandardssurface
Cougr Performance Guide
Purpose
This document explains the current performance model for Cougr's Soroban-first ECS path.
It is not a promise of fixed gas costs. It is a guide to the data structures and tradeoffs that determine query and scheduling behavior.
The practical question it should answer is:
- which backend should I use
- where should a component live
- what kinds of mutations are cheap versus expensive
SimpleWorld Query Model
SimpleWorld now maintains direct component indexes:
table_indexfor table-backed componentsall_indexfor table + sparse lookups
That changes the expected behavior of the common query paths:
get_table_entities_with_component()uses the direct table indexget_all_entities_with_component()uses the all-storage indexSimpleQueryselects the narrowest available required component index before filtering
This is the default performance story for gameplay loops.
Use SimpleWorld by default when:
- your hot loop is dominated by one- or two-component scans
- you mutate entity composition often
- you rely on table vs sparse placement to control scan scope
Use ArchetypeWorld when:
- your hot loop is dominated by repeated multi-component queries
- entity compositions are relatively stable after setup
- you are willing to pay more for add/remove migrations to get tighter query scopes
Storage Tradeoffs
Table storage:
- optimized for repeated scans
- should back components that appear in hot gameplay loops
Sparse storage:
- better for infrequent markers or tags
- excluded from table-only scans by default
If a sparse component starts showing up in tick-critical queries, it is usually a signal that the component belongs in table storage.
Prescriptive rule:
- if you scan it every tick, it probably belongs in table storage
- if you mostly address it directly or use it as a sparse marker, keep it sparse
Scheduler Tradeoffs
SimpleScheduler now validates stage-local dependencies before execution.
Costs introduced by the stronger model:
- dependency validation during run planning
- topological ordering within each stage
Benefits:
- explicit execution order
- early detection of invalid schedules
- safer composition as system counts grow
This is a good trade in Soroban-oriented contracts because schedule size is typically small relative to the cost of incorrect execution order.
Benchmark Focus Areas
Benchmarks should answer these practical questions:
- how many entities can the indexed query path scan efficiently
- when does
ArchetypeWorldoutperformSimpleWorld - what is the cost of adding/removing indexed components
- what is the cost of stage validation and deferred command application
The current benchmark suite in benches/ecs_bench.rs covers these paths directly.
It now includes:
- entity spawn cost
- component insert / lookup cost
- indexed query vs sparse-inclusive query cost
- cache warm-read vs invalidated-read behavior
- scheduler validation + execution cost
SimpleWorldvsArchetypeWorldmulti-component query comparisonSimpleWorldvsArchetypeWorldstructural mutation comparison
Reading The Current Benchmarks
Interpret the benchmark output in this order:
Query PathsIf plain indexed queries and cached queries are already cheap enough, stay onSimpleWorld.Backend Query ComparisonIfArchetypeWorldis materially better on your real multi-component query shape, it may be worth adopting.Backend Structural Mutation ComparisonIf archetype migration is significantly more expensive for your workload, do not switch just because query numbers look better in isolation.Query Cache InvalidationIf your world mutates every tick, cache benefits may collapse; optimize data shape first.
Decision Heuristics
Choose SimpleWorld when:
- gameplay writes are frequent
- entity compositions change often
- table/sparse separation gives you enough control
- your queries are broad but predictable
Choose ArchetypeWorld when:
- the same multi-component query runs constantly
- compositions are mostly fixed after startup
- entity migration cost is amortized over many reads
Keep GameApp, SimpleWorld, and SimpleQuery as the default performance story for new Soroban gameplay code.
Interpretation Rules
Use benchmark output to compare patterns, not to claim universal throughput numbers.
For real contracts, evaluate:
- data shape
- component cardinality
- table vs sparse placement
- how often the world mutates between repeated queries
Performance guidance should always be tied back to those conditions.
If benchmark results and your data shape disagree, trust the data shape first.
Related
This guide answers where a component should live once you have decided it belongs on-chain. For the prior decision, whether a piece of state or logic justifies being on-chain in the first place, see ONCHAIN_OFFCHAIN_BOUNDARY.md.
Cougr Public API Contract
Purpose
This document defines how Cougr presents its public Rust API for 1.0.
It answers four practical questions:
- which entrypoints are central to the product
- which surfaces are usable but still evolving
- which modules should not be interpreted as production commitments
- which compatibility shims or testing helpers are intentionally outside the long-term contract
API Positioning
Cougr exposes a broad crate surface, but only a scoped subset is part of the defended 1.0 contract.
The current product story is:
cougr-coreis primarily an ECS framework for Soroban-compatible applicationsappis the default gameplay runtime surface for new projectsauth,privacy, andopsare the clearest product-level domain namespaces- accounts remain Beta, while privacy is split between a stable primitive subset and experimental proof systems
- ECS onboarding/runtime surfaces and
standardsare part of the1.0stable contract - helper APIs that exist only for compatibility or transition should remain clearly demoted
Recommended Public Contract
This file now serves as the explicit 1.0 stable API list for cougr-core.
Core entrypoints
These are the frozen entrypoints for the 1.0 stable contract:
SimpleWorldArchetypeWorldecs::{RuntimeWorld, RuntimeWorldMut, WorldBackend}- typed and raw component operations
- command queues
- scheduling primitives
- events, hooks, and observers
- incremental persistence utilities
Concrete frozen root-level contract:
SimpleWorldArchetypeWorldCommandQueueComponent,ComponentTrait,ComponentStorage,ComponentIdSimpleQuery,SimpleQueryBuilderRuntimeWorld,RuntimeWorldMut,WorldBackendResourceruntime::ChangeTracker,runtime::TrackedWorldPlugin,PluginGroup,GameAppScheduleStage,SystemConfig,SimpleScheduler,SystemGrouppreluderuntimeappopsas the clearest Stable standards namespacestandardsas a Stable namespaceprivacy::stableas the clearest stable privacy namespacezk::stableas the stable privacy namespaceauthas the clearest Beta account namespaceaccountsas a Beta namespaceprivacy::experimentalas an explicitly non-contract namespacezk::experimentalas an explicitly non-contract namespace
Supported but evolving surfaces
These surfaces are useful and implemented, but should continue to be presented as Beta:
accounts- higher-level query helpers
- higher-level scheduler helpers
- proof-submission helpers in
zk
Stable privacy subset
These privacy surfaces are intentionally narrower and can be presented as Stable:
- commitments
- commit-reveal
- hidden-state codec interfaces
- Merkle inclusion and sparse Merkle utilities
zk::stable
Non-contract surfaces
These surfaces are public today, but they must not be interpreted as stable commitments:
- testing-only helpers
- advanced proof-verification APIs whose assumptions are still being hardened
zk::experimental- compatibility shims retained for transition
- internals-heavy modules whose invariants are not yet documented as stable guarantees
Top-Level Surface in src/lib.rs
Public modules
Current top-level modules:
appauthaccountsarchetype_worldcommandscomponentdebugbehind feature flagerroreventopsprivacypluginqueryresourceschedulersimple_worldzk
Internal implementation modules such as hidden scheduler helpers, storage
internals, and entity internals are no longer part of
the intended default public surface. They may still exist in the repository,
but the root crate is not meant to advertise them as onboarding entrypoints.
Advanced runtime support such as hooks, observers, change tracking, and
incremental storage is exposed through curated re-exports and runtime
instead of direct top-level module entrypoints.
Public re-exports
Current top-level re-exports emphasize:
- worlds:
SimpleWorld,ArchetypeWorld - backend contracts:
RuntimeWorld,RuntimeWorldMut,WorldBackend - ECS data:
Component,ComponentId,ComponentStorage,ComponentTrait,Position,Resource - orchestration:
CommandQueue,GameApp, schedulers - queries:
SimpleQuery,SimpleQueryBuilder - domain access through explicit namespaces:
auth,privacy,ops,accounts,zk::stable,zk::experimental
Public top-level helper functions
There are no root-level placeholder helper functions in the supported contract.
The sanctioned onboarding path is the curated root surface itself:
appauthprivacyopsSimpleWorldArchetypeWorldCommandQueueGameAppapp::{named_system, named_context_system}andadd_systems
Compatibility Exceptions
Public API Risks
The main public API risks before this cleanup were:
- the crate exports more surface area than it can reasonably defend as stable
- some internals-heavy modules are public before their long-term contract is clearly documented
- some privacy and verification surfaces are easy to overread as production guarantees
- accounts and privacy modules still include beta-grade behavior that is intentionally documented outside the stable story
Freeze Direction
The 1.0 freeze is intentionally narrower than the full public module graph:
appis the clearest default runtime namespace for new gameplay codeauthis the clearest Beta auth namespace for application codeprivacyis the clearest domain namespace for privacy adoption, with stability determined by submoduleopsis the clearest stable namespace for operational standards in application code- root re-exports and
preludeare the default onboarding path runtimeis the supported namespace for advanced ECS integrations that are not part of the smallest onboarding contractqueryandarchetype_worldretain their cache/state helpers outside the smallest root onboarding surfacestandardsis a supported stable namespaceaccountsremains a public Beta namespacezk::stableis the only privacy namespace treated as Stablezk::experimentalremains public for explicit opt-in use, but outside compatibility guarantees
Cougr Compatibility Promises
Purpose
This document defines the compatibility story Cougr is prepared to defend at 1.0.
It turns the maturity model into explicit expectations for adopters, contributors, and maintainers.
1.0 Baseline
Cougr 1.0.0 freezes a scoped stable surface inside a broader public crate.
That means:
- compatibility promises are scoped by maturity, not by visibility alone
- stable, beta, and experimental namespaces can coexist in the same crate
- the stable guarantee is the documented contract, not every public symbol
Stable Surfaces
The following surfaces are treated as Cougr's strongest 1.0 compatibility commitments:
- root ECS onboarding and runtime entrypoints documented in API_CONTRACT.md
preluderuntimeopsstandardsprivacy::stablezk::stable- the contracts documented in PRIVACY_MODEL.md for commit-reveal, hidden-state codecs, and Merkle verification
For these surfaces, maintainers should preserve:
- type and function intent unless there is a documented breaking reason
- documented failure behavior
- documented malformed-input behavior where applicable
- byte-level or proof-shape contracts already written in the privacy model
Beta Surfaces
The following surfaces are supported but intentionally not frozen:
- higher-level ECS helpers outside the frozen root/runtime contract
authaccounts- proof-submission orchestration that depends on experimental verification flows
For Beta surfaces, maintainers commit to:
- keep the product direction coherent
- document meaningful semantic changes
- avoid gratuitous churn
- preserve the curated onboarding path where practical
For Beta surfaces, maintainers do not yet promise:
- SemVer-stable signatures
- unchanged storage layouts for every helper
- unchanged auth or orchestration semantics across all releases
Experimental Surfaces
The following surfaces are explicitly outside compatibility guarantees:
privacy::experimentalzk::experimental- hazmat cryptographic helpers
- advanced proof-verification helpers and descriptors
- any public support surface documented as test-only or transition-only
These may:
- change shape
- move namespace
- be removed
- gain stronger validation that changes edge-case behavior
Non-Contract Support Surfaces
Support-only surfaces such as MockAccount are not part of the default product contract.
They exist for tests and explicit utility consumers, not as long-term framework guarantees.
Change Management Rules
When changing a Stable or Beta public surface, update at minimum:
- MATURITY_MODEL.md if the classification changes
- API_CONTRACT.md if the recommended contract changes
- PUBLIC_GAPS.md if a known gap is closed or newly introduced
- THREAT_MODEL.md if trust assumptions or security posture change
1.0 Freeze Decisions
The 1.0 release gate decisions are:
- ECS onboarding and runtime surfaces are in the stable contract
opsis the stable domain alias for standardsstandardsis in the stable contractauthis a Beta domain alias and is not part of the stable guaranteeaccountsremains Beta and is not part of the stable guaranteeprivacy::stablemaps to the frozen privacy contractzk::stableis the frozen privacy contractprivacy::experimentalremains outside compatibility guaranteeszk::experimentalremains outside compatibility guarantees
Migration Guide
Purpose
This guide explains how to move existing Cougr integrations toward the curated 1.0 product surface.
It is not a promise that every older pattern disappears immediately. It is the recommended direction for users who want to converge on the defended path.
Core Direction
Prefer these namespaces in new or updated code:
appfor gameplay runtimeauthfor account and session flowsprivacy::stablefor stable privacy primitivesopsfor operational standards
Runtime Migration
From direct world/scheduler wiring
If you currently do something like:
let mut world = SimpleWorld::new(&env);
let mut scheduler = SimpleScheduler::new();
prefer:
let mut app = cougr_core::app::GameApp::new(&env);
and register systems through GameApp.
When multiple systems belong to the same phase, prefer the declarative path:
use cougr_core::app::{named_context_system, named_system, GameApp, ScheduleStage};
let mut app = GameApp::new(&env);
app.add_systems((
named_system("spawn", |world, env| {
let entity = world.spawn_entity();
world.set_typed(env, entity, &Position::new(0, 0));
})
.in_stage(ScheduleStage::Startup),
named_context_system("cleanup_tags", |context| {
let entities = context
.world()
.get_entities_with_component(&symbol_short!("expired"), context.env());
for i in 0..entities.len() {
let entity = entities.get(i).unwrap();
context
.commands()
.remove_component(entity, symbol_short!("expired"));
}
})
.in_stage(ScheduleStage::Cleanup),
));
Why:
- clearer lifecycle
- explicit stages
- one onboarding surface instead of several loose primitives
- a single system registration model for plain and context-aware systems
From the removed pre-1.0 ECS model
If you were previously on the removed pre-1.0 World / System path, port directly to
GameApp, SimpleWorld, and SimpleQuery.
Query Migration
If you still do ad-hoc scans or manual component filtering, prefer:
SimpleQueryBuilderSimpleQueryStateSimpleQueryCache
Both SimpleQueryBuilder and ArchetypeQueryBuilder now support:
with_components(...)without_components(...)with_any_components(...)
If you need backend-agnostic gameplay helpers across Soroban-first worlds, prefer:
RuntimeWorldRuntimeWorldMut
These are the shared contracts between SimpleWorld and ArchetypeWorld.
Domain Migration
Accounts
If you currently import from accounts directly in application code:
use cougr_core::accounts::SessionBuilder;
prefer:
use cougr_core::auth::SessionBuilder;
The semantics are the same today. The change is about product clarity.
Privacy
If you rely on stable privacy primitives, prefer:
use cougr_core::privacy::stable::...
instead of:
use cougr_core::zk::stable::...
If you rely on advanced proof tooling, prefer:
use cougr_core::privacy::experimental::...
and treat it as an explicit opt-in to non-frozen APIs.
Standards
If you currently import standards directly:
use cougr_core::standards::Pausable;
prefer:
use cougr_core::ops::Pausable;
Again, this is a namespace migration for clarity, not a semantic rewrite.
Example-Level Migration
Use these examples as references:
snakeforapp::GameAppand stage-based gameplay loopsbattleshipforprivacy::stableand hidden-information patternsguild_arenafor account/session/recovery patterns
What Does Not Need Immediate Migration
You do not need to rewrite everything at once if:
- the contract still needs a focused port from the removed pre-1.0 runtime path
- you are preserving an older example or integration
- your current code already sits behind a stable local abstraction
The main goal is to stop growing new code on top of older default imports.
Migration Checklist
-
move runtime entrypoints to
appwhere practical -
move account imports to
auth -
move stable privacy imports to
privacy::stable -
move standards imports to
ops - update local docs/examples to use the curated namespaces
CLI Reference
β³ This page does not exist yet because the
cougr-clicrate does not exist yet.Per
docs/strategy/12-documentation-architecture.md: "CLI reference - Does not exist because the CLI does not exist yet; ships alongside it."When the CLI ships, this page will document all
cougr new,cougr add, andcougr checksubcommands.Tracked in:
salazarsebas/Cougrissues
Client SDK Reference
β³ This page does not exist yet because the TypeScript client SDK does not exist yet.
Per
docs/strategy/12-documentation-architecture.md: "Client SDK reference - Ships alongside the SDK described in 06-product-strategy.md."When the SDK ships, this page will document the TypeScript API for connecting a frontend to a Cougr game contract, including wallet integration and session key management.
Tracked in:
salazarsebas/Cougrissues
Architecture Decision Records
Architecture Decision Records (ADRs) document significant technical decisions made in Cougr's design. They are kept in the main salazarsebas/Cougr repository under docs/adr/ and synced here automatically.
Each ADR follows the format: context β decision β consequences.
| ADR | Title |
|---|---|
| 0001 | Public API Surface |
| 0002 | Accounts Beta |
| 0003 | Privacy Model Split |
| 0004 | Standards Layer Stable |
| 0006 | Game Circuit Suite |
| 0007 | Workspace Subcrates |
ADR 0001: Curated Public Surface
Status
Accepted
Context
Cougr exposes a broad API. Without curation, adopters can easily mistake public visibility for stable-contract inclusion.
Decision
Cougr keeps a curated onboarding path at the crate root and explicitly separates:
- root-level ECS onboarding re-exports
standardsas a Stable namespacezk::stableas the stable privacy namespaceaccountsas a Beta namespacezk::experimentalas the explicit non-contract privacy namespace
Consequences
- docs can name the golden path without pretending the whole crate is frozen
- advanced but useful namespaces remain available
- public visibility alone is no longer the compatibility signal
ADR 0002: Keep Accounts Out Of The Stable 1.0 Contract
Status
Accepted
Context
The account kernel, typed intents, replay domains, passkey support, and session enforcement are implemented. However, account abstraction remains a security-sensitive area with meaningful design-space risk.
Decision
Cougr will keep accounts as a Beta namespace at 1.0 even though the kernel exists and is tested.
Consequences
- the repo can truthfully document real implementation value
- maintainers keep room to tighten signer, policy, and integration contracts
- adopters are warned not to treat the current account API as SemVer-frozen
ADR 0003: Stable Privacy Subset With Experimental Verification
Status
Accepted
Context
Cougr contains both defensible privacy primitives and faster-moving proof-verification helpers. Treating them as one maturity tier would overclaim guarantees.
Decision
Cougr formally splits privacy into:
zk::stablefor commitments, commit-reveal, hidden-state codecs, and Merkle verificationzk::experimentalfor advanced proof verification, circuits, channels, recursive layouts, and hazmat helpers
Consequences
- privacy claims can stay narrow and defendable
- advanced ZK work can continue without blocking the stable subset
- compatibility promises can be scoped precisely by namespace
ADR 0004: Sandbox Design
Status
Accepted
Context
We need a secure testing and simulation environment for contracts to allow developers to validate logic (like ECS events and ZK proofs) locally without full node deployments. The sandbox should emulate the target environment accurately.
Decision
We introduce a test sandbox utilizing no_std and alloc alongside the Soroban testutils feature. This sandbox provides core modules for testing games (such as GameHarness, Scenario, WorldFixture, ReplayLog, and SnapshotAssert).
Consequences
- Developers can write fast local tests with a familiar testing API.
- We must maintain parity between sandbox behavior and on-chain execution.
- Relies on the
testutilsfeature flag being managed correctly in the crate.
ADR 0004: Include Standards In The Stable 1.0 Contract
Status
Accepted
Context
The standards layer is documented, integration-tested, and intentionally designed as a reusable framework surface rather than example glue.
Decision
Cougr includes standards in the stable 1.0 contract.
Consequences
- integrators can treat
standardsas part of the defended public framework surface - future changes to these modules now carry stable-contract weight
- authorization composition remains explicit at the integration boundary rather than hidden inside the primitives
ADR 0005: Session UX
Status
Accepted
Context
Repeatedly signing transactions degrades the user experience for on-chain games, especially those requiring frequent interactions (like real-time or turn-based strategy). Players expect a seamless experience akin to Web2 gaming without constantly engaging with a wallet prompt.
Decision
We introduce session keys with a fluent SessionBuilder API to authorize specific game actions over a time-bound window without requiring repeated user prompts. authorize_with_fallback will be used for graceful degradation, allowing operations to fall back to direct authorization when a session is expired or unavailable.
Consequences
- Massively improved gameplay experience for end users.
- Integration becomes slightly more complex to handle session lifecycles.
- Wallets and client integrations must support and manage session key delegation.
ADR 0006: Game Circuit Suite (cougr_core::circuits)
Status
Accepted
Context
Game developers need fog-of-war, hidden cards, fair dice, and sealed-bid mechanics
without authoring Circom circuits and wiring Groth16 verification by hand. Cougr
already exposes low-level verifiers under zk::experimental, but the onboarding
path requires weeks of ZK specialization.
Decision
-
Ship four pre-built circuit builders under
cougr_core::circuits(always available, Experimental maturity):Builder Public inputs hidden_cards(deck_size, hand_size)deck_root, hand_commitment, player_id, deck_size, hand_size fog_of_war(w, h, radius)map_root, prior/next explored roots, origin, tile, radius fair_dice(sides, seed_commitment)seed_commitment, roll_result, sides, nonce sealed_bid(max_bid)auction_id, bid_commitment, revealed_bid, max_bid -
Each builder returns
GameCircuitSpecwith a frozenPublicInputLayout, placeholder VK (correct IC length), and typed verify methods that delegate tozk::experimental::verify_groth16. Production deploys replace the VK viawith_verification_key. -
fog_of_warreusesFogOfWarCircuitinzk::advanced- no duplicate verification logic. -
Circom scaffolds and off-chain scripts live in
internal/cougr-core-circuits/(publish = false). Rust implementation lives insrc/circuits/. -
Canonical examples demonstrate each builder:
examples/hidden_hand/examples/fog_explorer/examples/dice_duel/examples/blind_auction/
Consequences
- Developers integrate common game privacy patterns in hours, not weeks.
- Public-input layouts are versioned by
CircuitIdand must not change without a new ADR. - On-chain builders ship an unbound VK; load test/production keys from
internal/cougr-core-circuits(bun run pipelineβexported/*_vk.json). - Circuits use Poseidon + game constraints (~325β13.6k R1CS); pot14 trusted setup.
zk::stableremains unchanged; all new surface stays Experimental until external audit.
ADR 0007: Workspace Subcrates Compiled Into cougr-core
Status
Accepted
Context
Cougr is adding three competitive layers (ZK circuit builders, session UX, game
testing sandbox). They need compile-time isolation without publishing separate
crates.io packages, so download metrics stay unified under cougr-core.
Decision
-
Add a Cargo workspace with three internal members under
internal/:cougr-core-circuitscougr-core-sessioncougr-core-test
-
Each internal member sets
publish = false. -
Each layer's implementation lives in
src/{circuits,session,test}/inner.rs. The public modulesinclude!that file; internal workspace members point their[lib] pathat the sameinner.rsfor isolatedcargo check -pruns. This avoids:- circular dependencies (session needs
authfrom the same crate) cargo publishfailures on unpublished path deps- missing files in the published tarball
- circular dependencies (session needs
-
Public API:
cougr_core::circuits- always availablecougr_core::session- always availablecougr_core::test-testutilsfeature only
-
The test sandbox uses
no_std+alloc, notstd. It runs in Sorobantestutilsenvironments the same way contract tests do today.
Consequences
- One
cargo add cougr-corefor all capabilities - Internal folders can still be checked with
cargo check -p cougr-core-session cargo publishshipsinternal/**sources inside thecougr-coretarball- Feature
testutilskeeps sandbox code out of contract WASM builds
ADR 0008: Include Standards In The Stable 1.0 Contract
Status
Accepted
Context
The standards layer is documented, integration-tested, and intentionally designed as a reusable framework surface rather than example glue.
Decision
Cougr includes standards in the stable 1.0 contract.
Consequences
- integrators can treat
standardsas part of the defended public framework surface - future changes to these modules now carry stable-contract weight
- authorization composition remains explicit at the integration boundary rather than hidden inside the primitives
Showcase
The Showcase is a live directory of games and demos built with Cougr. It is
generated automatically from the example catalog
and each example's own README.md β no manual duplication.
Browsing the gallery
- Example Gallery β filter by category and maturity to find the right reference for your use case.
- Click any card to see the full detail page, pulled directly from that
example's
README.md.
How it works
The gallery is a static, build-time-generated set of pages β zero backend,
zero database. The generator (cougr-site/generate-showcase.py) reads:
examples/catalog.tomlβ structured metadata (category, maturity, Cougr features, optional screenshot/testnet contract).- Each example's
README.mdβ the description and full documentation. packages/tokens/tokens.jsonβ design tokens (colors, typography, spacing) consumed as CSS custom properties, so the showcase never visually diverges from the docs site.
To generate locally:
python3 cougr-site/generate-showcase.py
mdbook serve cougr-site
Preview images
Examples with a preview.svg in their directory display it in the gallery
card and detail page. Examples without a preview image render cleanly without
any broken image tags.
Submit your game
Once your example is cataloged in examples/catalog.toml and meets the
quality standard,
it will appear in the gallery automatically. A "Cougr Verified" badge can
be earned by opening a PR β the catalog's verified field controls whether the
badge renders.
Battleship with Hidden Board
A two-player Battleship game demonstrating hidden information using commit-reveal pattern and Merkle proofs on Stellar Soroban. Players commit their board layouts cryptographically, then prove hit/miss results without revealing unattacked positions.
This example is Cougr's canonical hidden-information reference. It intentionally leans on the stable privacy surface in cougr_core::privacy::stable instead of re-defining Merkle verification inside the example.
Status
Canonical - maintained reference implementation for commit-reveal + selective disclosure on Soroban. Uses cougr-core = "1.1.0", privacy::stable Merkle primitives, and impl_component! macros for standardized serialization.
The Hidden Information Problem
Traditional on-chain games face a challenge: all data is public. In Battleship, if boards are stored directly on-chain, opponents can see ship positions and cheat.
Solution: Commit-Reveal + Merkle Proofs
SETUP PHASE (hide boards)
ββ Player A: commitment = SHA256(board || salt)
ββ Player A: merkle_root = MerkleTree(board).root
ββ Submit (commitment, merkle_root) on-chain
ββ Player B: same process
ATTACK PHASE (selective reveal)
ββ Attacker: attack(x, y)
ββ Defender: reveal_cell(x, y, value, merkle_proof)
ββ Contract: verify proof against merkle_root
ββ Record hit/miss (other cells remain hidden)
END PHASE (anti-cheat)
ββ Winner declared when all ships sunk
ββ Full board can be verified against commitment
Key Properties:
- β Hiding: Unattacked cells remain secret
- β Binding: Can't change board after commitment
- β Selective Reveal: Prove one cell without revealing others
- β Verifiable: Merkle proofs ensure honesty
Game Flow
1. Setup Phase
#![allow(unused)] fn main() { new_game(player_a, player_b) }
Each player computes off-chain:
#![allow(unused)] fn main() { // 1. Create 10x10 board (0=water, 1=ship) let board = [0u32; 100]; board[0] = 1; // Ship at (0,0) // 2. Compute commitment let commitment = SHA256(board || salt); // 3. Build Merkle tree let merkle_root = MerkleTree::new(board).root(); // 4. Submit on-chain commit_board(player, commitment, merkle_root) }
2. Attack Phase
Alternating turns:
Attacker:
#![allow(unused)] fn main() { attack(attacker, x, y) }
Defender:
#![allow(unused)] fn main() { // Off-chain: get Merkle proof for cell (x,y) let proof = merkle_tree.get_proof(x, y); // On-chain: reveal with proof reveal_cell(defender, x, y, value, proof) }
Contract verifies:
- Proof is valid against stored
merkle_root - Records hit (value=1) or miss (value=0)
- Updates ship count
- Switches turn
3. Win Condition
Game ends when one player's ships are all sunk (17 hits total: 5+4+3+3+2).
Contract API
| Function | Parameters | Description |
|---|---|---|
new_game | player_a: Addressplayer_b: Address | Initialize game |
commit_board | player: Addresscommitment: BytesN<32>merkle_root: BytesN<32> | Commit board layout |
attack | attacker: Addressx: u32, y: u32 | Attack coordinates (0-9) |
reveal_cell | defender: Addressx: u32, y: u32value: u32proof: OnChainMerkleProof | Reveal cell with Merkle proof |
get_state | - | Get current game state |
Data Structures
Phase
#![allow(unused)] fn main() { enum Phase { Setup, // Waiting for board commitments Attack, // Game in progress Finished, // Winner declared } }
CellResult
#![allow(unused)] fn main() { enum CellResult { Unknown, // Not yet attacked Miss, // Attacked, no ship Hit, // Attacked, ship present } }
BoardCommitment
#![allow(unused)] fn main() { struct BoardCommitment { commitment: BytesN<32>, // SHA256(board || salt) merkle_root: BytesN<32>, // Root of Merkle tree } }
Stable Merkle Verification
The contract uses Cougr's stable SHA256 Merkle proof contract:
#![allow(unused)] fn main() { use cougr_core::privacy::stable::{MerkleProofVerifier, Sha256MerkleProofVerifier}; let verifier = Sha256MerkleProofVerifier; assert!(verifier.verify(&env, &proof, &merkle_root)?); }
The leaf payload still binds index || value, but the inclusion proof format and verification rules come from Cougr's stable privacy API.
When to Use Commit-Reveal vs ZK Circuits
| Pattern | Best For | Cost | Complexity |
|---|---|---|---|
| Commit-Reveal + Merkle | Hidden boards, card hands, fog-of-war | O(log n) proof verification | Low - uses standard SHA256 |
| ZK Circuits (Groth16/Poseidon) | Private game logic evaluation, hidden card deals | Single on-chain verification | High - requires circuit compilation |
Use commit-reveal when you need to hide state but reveal it incrementally with verifiable proofs. Use ZK circuits when the game logic itself must remain private (e.g., proving a move is valid without revealing the move).
For a reference ZK implementation, see hidden_hand, which demonstrates circuit-based hidden card dealing with Groth16 proofs.
Storage Model
Committed State
Stored on-chain during setup:
commitment_a/b- SHA256 hash of each player's board with random saltmerkle_root_a/b- root of the Merkle tree built from cell hasheshas_commitment_a/b- flags tracking which players have committed
Both commitments must be submitted before the game transitions to Phase::Attack.
Revealed State
Updated during the attack phase:
attack_grid_a/b- maps cell indices toCellResult(hit/miss)ship_status- tracks remaining ship cells per player (starts at 17)turn_state- tracks current player, phase, and pending reveals
Proven State
The reveal_cell function verifies that disclosed cell values match the original commitment:
- Constructs the expected leaf hash from
(index, value)using the same SHA256 scheme as the commit phase - Validates the
OnChainMerkleProofagainst the storedmerkle_rootusingSha256MerkleProofVerifier - Only if the proof verifies is the hit/miss recorded on-chain
Component Serialization
BoardCommitment uses the impl_component! macro for standardized serialization:
#![allow(unused)] fn main() { impl_component!(BoardCommitment, "board", Table, { commitment: bytes32, merkle_root: bytes32 }); }
This replaces manual byte-level serialization with a type-safe macro that handles big-endian encoding/decoding automatically.
Building & Testing
Prerequisites
- Rust 1.88.0+
- Stellar CLI 25.0.0+ (optional)
Build
cargo build
cargo build --release --target wasm32v1-none
Test
cargo test
Test Coverage (12 tests):
Recommended Testing Approach:
For verification of complex setup commitment sequences and attack/reveal turn-based interactions, utilize GameHarness and Scenario (see sandbox_tests.rs). This ensures cryptographic commitments and Merkle proofs verify correctly across turns.
Test Coverage (10 tests):
- β Game initialization
- β Board commitment
- β Attack and reveal (miss)
- β Attack and reveal (hit)
- β Invalid proof rejection
- β Cannot attack same cell twice
- β Turn enforcement
- β Win condition
- β
Component trait serialization (via
impl_component!) - β Turn switching
- β Reveal without pending attack
- β Attack before commit phase
Example Usage
Off-Chain (Player)
#![allow(unused)] fn main() { // Build Merkle tree from hashed cell payloads let mut leaves = Vec::new(); for (idx, &value) in board.iter().enumerate() { let leaf = sha256(idx || value); leaves.push(leaf); } let tree = MerkleTree::from_leaves(&env, &leaves)?; let root = tree.root(); // Get proof for specific cell let proof = to_on_chain_proof(&tree.proof(x * 10 + y)?, &env); // Submit on-chain client.reveal_cell(&player, &x, &y, &value, &proof); }
On-Chain (Contract)
#![allow(unused)] fn main() { let verifier = Sha256MerkleProofVerifier; assert!(verifier.verify(&env, &proof, &stored_merkle_root)?); }
Security Considerations
Secure
- Commitment binding: SHA256 prevents changing board
- Selective reveal: Merkle proofs reveal only attacked cells
- Proof verification: Invalid proofs rejected
- Turn enforcement: Players alternate attacks
οΈ Important
- Salt randomness: Use 32 cryptographically random bytes
- Merkle tree depth: 7 levels for 100 cells (padded to 128)
- Proof ordering: Siblings must be in correct order
Best Practices
#![allow(unused)] fn main() { // β Good: Random salt let salt = generate_random_bytes(32); // β Bad: Predictable salt let salt = BytesN::from_array(&env, &[0u8; 32]); // β Good: Verify proof before revealing if !verify_proof(root, index, value, proof) { panic!("Invalid proof"); } }
ECS Architecture
Components
| Component | Fields | Purpose |
|---|---|---|
BoardCommitment | commitment: BytesN<32>merkle_root: BytesN<32> | Cryptographic board commitment |
AttackGrid | cells: Map<u32, CellResult> | Public record of attacks |
ShipStatus | remaining_a: u32remaining_b: u32 | Ship cell counts |
TurnState | current_player: Addressphase: Phasehas_pending: bool | Game state management |
Systems
| System | Responsibility |
|---|---|
| CommitSystem | Validates and stores commitments |
| AttackSystem | Records attack coordinates |
| RevealSystem | Verifies stable OnChainMerkleProof, updates grid |
| WinConditionSystem | Detects when all ships sunk |
Why Merkle Proofs?
Merkle trees enable selective disclosure:
| Approach | Reveal Cost | Privacy |
|---|---|---|
| Full board on-chain | O(1) | β None |
| Reveal entire board per attack | O(n) | β None |
| Merkle proof | O(log n) | β Only attacked cells |
For a 10x10 board:
- Full reveal: 100 cells
- Merkle proof: ~7 hashes (logβ 128)
Deployment
# Deploy to testnet
stellar keys generate battleship-deployer --network <NETWORK> --fund
stellar contract deploy \
--wasm target/wasm32v1-none/release/battleship.wasm \
--source battleship-deployer \
--network <NETWORK>
Resources
- Cougr Repository
- Merkle Trees
- Commitment Schemes
- hidden_hand - ZK circuit example
- Soroban Documentation
License
MIT OR Apache-2.0
blind_auction
Canonical ZK circuit example demonstrating circuits::sealed_bid for sealed-bid reveals.
Purpose and pattern
This example demonstrates how to run a blind auction on-chain. Bidders commit their encrypted bids, and during the reveal phase, submit a ZK proof to verify their bid value matches the original commitment and is below the maximum allowed bid limits, without revealing bids early or leaking bid range details.
Public contract API
| Function | Parameters | Returns | Description |
|---|---|---|---|
init_auction | max_bid: u32, auction_id: BytesN<32> | AuctionConfig | Starts a new auction config with maximum bid limits and an auction identifier. |
reveal_bid | bidder: Address, bid_commitment: BytesN<32>, revealed_bid: u32, proof: Groth16Proof | bool | Verifies a bidder's reveal proof, storing the bid record if valid. |
bid_reveal | bidder: Address | BidReveal | Retrieves the revealed bid details for a bidder. |
Architecture overview
βββββββββββββββββ
β Bidder β
ββββββββ¬βββββββββ
β Submits Bid & ZK Proof
ββββββββββββΌβββββββββββ
β BlindAuction β
β (Soroban Contract) β
ββββββββββββ¬βββββββββββ
β Loads Spec
ββββββββββββΌβββββββββββ
β circuits:: β
β sealed_bid β
βββββββββββββββββββββββ
The bidder submits their bid value and proof that verifies their bid corresponds to the committed hash. The contract runs the sealed_bid verifier logic to register the bid.
Storage model
Auction configuration records and active bid listings are stored in Instance Storage on-chain.
Main gameplay flow
- Setup: Call
init_auctionto setup maximum bid constraints and the auction ID. - Commit Phase: Bidders record hash commitments of their bids (handled off-chain or via standard storage).
- Reveal Phase: Bidders call
reveal_bidwith their bids and ZK proofs to unlock and register their bid weights.
Cougr APIs used
circuits::sealed_bid: Handles verify calculations for sealed on-chain bidding logic.zk::Groth16Proof: Contains the proof struct representation.
Recommended testing approach
Use GameHarness and standard test_fixtures to execute happy-path reveals and invalid proof rejection flows.
Build and test commands
cargo test
stellar contract build
Known limitations
- Simple single-item auction model.
- Winner computation logic is not included (focuses on verification of bid validity).
dice_duel
Canonical ZK circuit example demonstrating circuits::fair_dice for verifiable dice rolls.
Purpose and pattern
This example showcases a verifiable on-chain dice rolling game. Players generate random numbers off-chain and submit ZK proofs to prove that their roll result is deterministic, within bounds, and bound to the initial committed seed, preventing manipulation of on-chain randomness.
Public contract API
| Function | Parameters | Returns | Description |
|---|---|---|---|
init_duel | sides: u32, seed_commitment: BytesN<32> | DuelConfig | Registers the dice parameters and the starting cryptographic seed commitment. |
submit_roll | player: Address, roll_result: u32, nonce: u32, proof: Groth16Proof | bool | Verifies a dice roll ZK proof and updates the roll record if valid. |
roll_record | player: Address | RollRecord | Retrieves the stored roll result for a player. |
Architecture overview
βββββββββββββββββ
β Player β
ββββββββ¬βββββββββ
β Rolls & Generates ZK Proof
ββββββββββββΌβββββββββββ
β DiceDuel β
β (Soroban Contract) β
ββββββββββββ¬βββββββββββ
β Loads Spec
ββββββββββββΌβββββββββββ
β circuits:: β
β fair_dice β
βββββββββββββββββββββββ
The player commits a random seed and runs a deterministic calculation. They submit a Groth16 proof showing the calculation output matches the result of their roll.
Storage model
Dice duel config and player roll history are stored in Instance Storage on-chain via Soroban instance key-value associations.
Main gameplay flow
- Setup: Call
init_duelto bind the dice properties and seed commitment. - Roll: Players roll dice off-chain and calculate proof.
- Submit: Players call
submit_rollto verify and record the roll on-chain.
Cougr APIs used
circuits::fair_dice: Implements the dice roll verification boundary specifications.zk::Groth16Proof: Holds proof payload.
Recommended testing approach
Integrate GameHarness and Scenario runner combined with test_fixtures::pipeline_proof to simulate individual rolls and multi-player roll scenarios.
Build and test commands
cargo test
stellar contract build
Known limitations
- Simple single-roll mechanics.
- The seed is assumed to be cryptographically secure and generated off-chain.
fog_explorer
Canonical ZK circuit example demonstrating circuits::fog_of_war for private exploration.
Purpose and pattern
This example showcases a verifiable "fog of war" map exploration. Players move around a private map commitment, proving that they only reveal cells within their current line-of-sight visibility radius, without publishing the full map layout or their exact positions on the public blockchain.
Public contract API
| Function | Parameters | Returns | Description |
|---|---|---|---|
init_map | width: u32, height: u32, visibility_radius: u32 | MapConfig | Initializes the map configuration and builds the ZK circuit specs. |
register_explorer | player: Address, explored_root: BytesN<32> | ExplorerState | Registers a player with their starting empty explored map commitment. |
explore | player: Address, map_root: BytesN<32>, prior_explored_root: BytesN<32>, next_explored_root: BytesN<32>, origin_x: i32, origin_y: i32, tile_x: i32, tile_y: i32, proof: Groth16Proof | bool | Verifies a transition proof, updating the player's explored map root if valid. |
explorer_state | player: Address | ExplorerState | Retrieves the current exploration state of the given player. |
Architecture overview
βββββββββββββββββ
β Player β
ββββββββ¬βββββββββ
β Moves & Generates ZK Proof
ββββββββββββΌβββββββββββ
β FogExplorer β
β (Soroban Contract) β
ββββββββββββ¬βββββββββββ
β Loads Spec
ββββββββββββΌβββββββββββ
β circuits:: β
β fog_of_war β
βββββββββββββββββββββββ
The player proves off-chain that the update from prior_explored_root to next_explored_root only uncovers the tile (tile_x, tile_y) within a visibility radius of their player coordinates. The contract verifies this transition against the map commitment.
Storage model
Player exploration commitments and map layouts are stored in Instance Storage on-chain. Using instance storage guarantees fast state reads and writes during hot loops.
Main gameplay flow
- Map Config: Call
init_mapto configure map dimensions and visibility boundaries. - Registration: Explorer calls
register_explorerto record their initial map state. - Exploration: Explorer calls
explorewith a Groth16 proof to update their visible tiles and progress.
Cougr APIs used
circuits::fog_of_war: Configures and handles verification of private line-of-sight map calculations.zk::experimental::{FogOfWarSnapshot, FogOfWarTransition}: Input structs for ZK state transition logic.zk::Groth16Proof: Cryptographic proof data container.
Recommended testing approach
Use GameHarness and test_fixtures to mock ZK inputs and pipeline proof bytes. This allows testing successful proof verification paths without running full proving ceremonies in unit tests.
Build and test commands
cargo test
stellar contract build
Known limitations
- Grid coordinates are simplified.
- Map generation and parsing are managed off-chain.
Guild Arena
PvP arena game on Soroban demonstrating guild-based social recovery and multi-device play using Cougr-Core.
Overview
On-chain gaming has a key risk: players losing access to accounts holding progress, items, and currency. Guild Arena solves this with two Cougr-Core account patterns:
- Social Recovery - guild members act as guardians who can collectively restore account access after a timelock period
- Multi-Device - players register multiple device keys (desktop, mobile) with per-device permission policies
How It Works
Account Setup
Player registers β sets 3 guild members as guardians (threshold 2-of-3)
β adds desktop key (Full permissions)
β adds mobile key (PlayOnly permissions)
Gameplay
Players queue for PvP matches. Combat is turn-based with three actions:
- Attack - standard damage
- Defend - reduced damage
- Special - high damage
Elo-style ratings update after each match. Every 3 wins triggers a level-up with stat boosts.
Recovery Flow
Player loses key β Guardian 1 initiates recovery
β Guardian 2 approves (threshold met)
β 7-day timelock starts
β After timelock: finalize_recovery()
β New key active, old key revoked
β All stats, rating, history preserved
Contract API
| Function | Description |
|---|---|
register_player | Register with guardians and recovery config |
add_device | Add a device key with policy (Full or PlayOnly) |
remove_device | Revoke a device key |
start_match | Queue for or start a PvP match |
submit_action | Submit combat action (Attack/Defend/Special) |
initiate_recovery | Guardian starts recovery process |
approve_recovery | Guardian approves recovery |
finalize_recovery | Complete recovery after timelock |
get_player | Query player profile |
get_match | Query current arena state |
Device Policies
| Level | Play | Trade/Admin |
|---|---|---|
| Full | β | β |
| PlayOnly | β | β |
Building
cargo build
stellar contract build
Testing
cargo test
Tests cover:
- Player registration with guardians
- Multi-device management
- Device policy enforcement
- Full combat match resolution
- Rating updates after matches
- Complete recovery lifecycle (initiate β approve β timelock β finalize)
- Recovery with insufficient approvals (rejected)
- Game state preservation through recovery
Architecture
Uses Cougr-Core ECS patterns:
Components: Fighter, MatchRecord, GuildMembership, ArenaState
Systems: Matchmaking, Combat, Rating, Recovery, Device authorization
Storage: Soroban persistent storage keyed by player/device addresses. Recovery and device state managed through RecoverableAccount and DeviceManager from cougr-core.
Reference Role
This example is the canonical reference for Cougr's account-oriented flows:
- social recovery
- multi-device authorization
- gameplay permissions separated from full admin authority
Unlike the arcade examples, this one is intentionally more account-centric than GameApp-centric.
Prerequisites
- Rust 1.89+
rustup target add wasm32v1-none- Stellar CLI (optional, for deployment)
License
MIT OR Apache-2.0
hidden_hand
Canonical ZK circuit example demonstrating circuits::hidden_cards for private card deals.
Purpose and pattern
This example demonstrates how to implement private card dealing (e.g., poker hand setups) on-chain. It uses ZK proofs to verify that a deck was shuffled and a hand of cards was dealt honestly, without revealing the cards to other players or the public on-chain ledger.
Public contract API
| Function | Parameters | Returns | Description |
|---|---|---|---|
init_table | deck_size: u32, hand_size: u32 | TableConfig | Configures the game table with the specified deck and hand size, initializing the ZK verifier. |
verify_deal | player: Address, deck_root: BytesN<32>, hand_commitment: BytesN<32>, proof: Groth16Proof | bool | Verifies a Groth16 deal proof against the configured hidden-cards layout. |
Architecture overview
ββββββββββββββββ
β Card Dealer β
ββββββββ¬ββββββββ
β Generates ZK Proof
ββββββββββββΌβββββββββββ
β HiddenHand β
β (Soroban Contract) β
ββββββββββββ¬βββββββββββ
β Loads Spec
ββββββββββββΌβββββββββββ
β circuits:: β
β hidden_cards β
βββββββββββββββββββββββ
The dealer generates a Groth16 proof off-chain, verifying that the hand commitment was indeed created from a valid slice of the shuffled deck. The contract verifies the proof using the hidden_cards circuit spec.
Storage model
The TableConfig containing the card configuration is stored in Instance Storage. The ZK verification keys are built dynamically or stored securely within the contract instance.
Main gameplay flow
- Setup: The contract calls
init_table(deck_size, hand_size)to prepare the ZK circuit parameters. - Dealing: The dealer generates a hand commitment and a proof off-chain.
- Verification: The contract calls
verify_deal(player, deck_root, hand_commitment, proof)to validate that the deal is correct before proceeding with gameplay.
Cougr APIs used
circuits::hidden_cards: Fetches the circuit layout, verification key, and verify wrapper for Groth16 hidden cards.zk::Groth16Proof: Data structure representing the cryptographic proof.
Recommended testing approach
Utilize GameHarness and test_fixtures to mock ZK inputs and pipeline proof bytes. This allows testing successful proof verification paths without running full proving ceremonies in unit tests.
Build and test commands
cargo test
stellar contract build
Known limitations
- Simple single-deck configuration.
- Proving is done entirely off-chain.
session_arena
feat/session-manager-fresh
Canonical Cougr example for the SessionManager gameplay lifecycle: approve once, play frictionlessly, renew on expiry, fallback when stale.
Related example: For the full mobile-first flow with passkey auth, combos, and rounds, see
tap_battle.
Purpose and pattern
session_arena is the minimal reference implementation of cougr-core session UX. It strips away game mechanics so you can focus on:
- Approve - owner signs once to create a scoped session key
- Play - many
tapcalls without wallet prompts - Renew - extend session before expiry (owner re-approves)
- Fallback - continue playing via direct owner auth when the session expires
Session lifecycle
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β approve_ ββββββΆβ tap (many ββββββΆβ renew_ ββββββΆβ fallback_ β
β session β β times) β β session β β tap β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
owner auth session key owner auth session or owner
creates scope no wallet prompt extends expiry auth fallback
Duration semantics:
expires_inandexpires_atuse ledger timestamps (seconds).SessionBuilder::expires_in(n)sets expiry toledger.timestamp() + n.
Public contract API
| Function | Parameters | Returns | Description |
|---|---|---|---|
approve_session | owner, max_taps, expires_in | ActiveSession | One-time owner approval creating a scoped session |
tap | owner, key_id | u32 | Gameplay action via active session (no wallet prompt) |
renew_session | owner, key_id, expires_in | ActiveSession | Extend session lifetime (owner must re-approve) |
fallback_tap | owner, key_id | u32 | Tap via session first, fall back to direct owner auth |
score | owner | u32 | Current tap count for the owner |
Cougr APIs used
SessionBuilder- declare allowed actions, max operations, and expirySessionManager::approve- create scoped session after owner authSessionManager::execute_action- gasless gameplay via session keySessionManager::status- poll remaining ops and renewal hintsSessionManager::renew- extend absoluteexpires_attimestampSessionManager::fallback_execute- session-first with direct-auth fallbackSessionStorage- load session keys by owner and key IDMockSession(testutils) - helper for unit tests
Build and test
Canonical example demonstrating SessionManager session lifecycles, scoped session keys, and fallback authorization.
Purpose and pattern
This example showcases the onboarding pattern for friction-free session keys on Soroban. A player approves a session key once, enabling them to play without wallet confirmation prompts for subsequent transactions. If the session expires, the client can fall back to direct owner authentication.
Public contract API
| Function | Parameters | Returns | Description |
|---|---|---|---|
approve_session | owner: Address, max_taps: u32, expires_in: u64 | ActiveSession | Approves a new session key with specific action scopes and expiration constraints. |
tap | owner: Address, key_id: BytesN<32> | u32 | Increments the player's score, verified using the active session key (no wallet prompt). |
renew_session | owner: Address, key_id: BytesN<32>, expires_in: u64 | ActiveSession | Extends the active session key expiration window (requires owner wallet authorization). |
fallback_tap | owner: Address, key_id: BytesN<32> | u32 | Performs a tap action, falling back to direct owner wallet authorization if the session key is expired. |
score | owner: Address | u32 | Retrieves the current tap score of the player. |
Architecture overview
ββββββββββββββββββββ
β Player Owner β
ββββββββββ¬ββββββββββ
β Approves
βββββββββββββΌββββββββββββ
β Active Session β
β (Temporary Key File) β
βββββββββββββ¬ββββββββββββ
β Authorizes (No Prompts)
βββββββββββββΌββββββββββββ
β SessionArena β
β (Soroban Contract) β
βββββββββββββ¬ββββββββββββ
β Updates
βββββββββββββΌββββββββββββ
β Score β
β (Component) β
βββββββββββββββββββββββββ
The game utilizes Cougr's session authentication module. The owner delegates authority for a specified duration and subset of actions to a local keypair. The contract verifies each transaction signature against the delegated session key metadata.
Storage model
Session parameters and player score components are stored in Instance Storage on-chain. Session keys are designed to be temporary, so their lifecycle is optimized for minimum storage fee footprints.
Main gameplay flow
- Authorization: Owner calls
approve_sessionfrom their wallet to authorize a temporary key for thetapaction. - Gameplay: Client signs and executes calls to
tapusing the session key, bypassing any ledger signature popups. - Renewal / Fallback: If the session expires, the client calls
renew_session(wallet-prompted) or callsfallback_tap(which handles direct-auth fallback).
Cougr APIs used
SessionManager: Manages approval, status, execution verification, and direct-auth fallbacks.SessionBuilder: Scopes maximum operations and timestamps for the generated key.SessionStorage: Loads active session state from the environment.impl_component!: Declares the player'sScorecomponent.
Recommended testing approach
Use the GameHarness and MockSession to simulate wallet authorization, session approvals, key rotation, and operation expirations. Verification covers the happy-path tapping, time-bound key expiry, and fallback routing checks.
Build and test commands
cargo test
stellar contract build
feat/session-manager-fresh
Test coverage
| Test | Description |
|---|---|
approve_and_tap_without_reauth | Multiple taps after single approval |
renew_session_extends_play_window | Renew increases absolute expires_at |
fallback_tap_uses_direct_auth_after_session_expires | Fallback after timestamp expiry |
mock_session_helper_matches_manager_flow | MockSession matches manager flow |
When to use which example
Use session_arena when⦠| Use tap_battle when⦠|
|---|---|
| Learning SessionManager basics | Building a real game with passkeys |
| Prototyping session UX in a new game | Need combo mechanics and rounds |
| Writing integration tests for sessions | Demonstrating mobile-first auth flow |
License
MIT
Known limitations
- Simple score counter used for demonstrating the session auth wrapper; no actual gameplay engine included.
- Minimal session storage configuration.
Snake On-Chain Game
Classification: Canonical example. This is the maintained arcade reference for Cougr examples. New arcade contracts should copy its GameApp wiring, components.rs / systems.rs split, README shape, and test coverage.
Purpose and pattern
Snake demonstrates a deterministic arcade loop on Soroban using cougr-core's basic ECS and GameApp tick model. The contract keeps persistent game state on chain, stores ECS entities in a SimpleWorld, and runs ordered systems for movement, collision, growth, and food spawning.
Public contract API
| Function | Parameters | Return type | Description |
|---|---|---|---|
init_game | none | () | Initializes a new game on the default 10Γ10 grid. |
init_game_with_size | grid_size: i32 | () | Initializes a new game on a custom square grid. |
change_direction | direction: u32 | bool | Changes the snake direction (0 up, 1 down, 2 left, 3 right); returns false for invalid values, reversals, or game-over state. |
update_tick | none | () | Advances the game by one GameApp tick. |
get_score | none | u32 | Returns the current score. |
check_game_over | none | bool | Returns whether the game has reached a terminal state. |
get_head_pos | none | (i32, i32) | Returns the current snake-head position. |
get_snake_length | none | u32 | Returns the number of snake entities. |
get_food_pos | none | (i32, i32) | Returns the current food position. |
get_snake_positions | none | Vec<(i32, i32)> | Returns all snake segment positions. |
get_grid_size | none | i32 | Returns the configured grid size. |
Architecture overview
contract entrypoint
ββ loads GameState + SimpleWorld from persistent storage
ββ builds a GameApp around the world
ββ schedules systems by stage
β ββ Update: move_snake
β ββ PostUpdate: self_collision -> food_collision
ββ writes GameState + SimpleWorld back to storage
lib.rscontains the Soroban contract entrypoints, storage access, andGameAppwiring.components.rscontains serializable ECS components such asPosition,DirectionComponent,SnakeHead,SnakeSegment, andFood.systems.rscontains reusable game systems for movement, direction validation, collision checks, growth, and food spawning.
Storage model
| Storage class | Data | Why |
|---|---|---|
| Instance storage | none | The example does not need contract-wide configuration shared across games. |
| Persistent storage | state: GameState, world: SimpleWorld | Game progress must survive across transactions. GameState stores compact scalar data; SimpleWorld stores entities and component bytes. |
| Temporary storage | none | No per-ledger cache is needed for deterministic gameplay. |
Within the SimpleWorld, dense components such as positions and directions use table-style access, while marker-style components such as food/head/segment are queried as needed.
Main gameplay flow
- A player calls
init_gameorinit_game_with_size. - Startup systems spawn the snake head at the grid center and create one food entity.
- The player calls
change_directionto submit a valid non-reversing input. - The player or a relayer calls
update_tick. GameAppruns movement first, then collision and food checks.- A wall/self collision sets
game_over; eating food grows the snake, increments score, and spawns new food. - Query functions expose score, positions, grid size, and terminal state.
Cougr APIs used
| API | Why it is used |
|---|---|
GameApp | Provides the maintained arcade-loop pattern and owns scheduled system execution per tick. |
ScheduleStage / SystemConfig | Ensures movement runs before post-update collision and food systems. |
SimpleWorld | Stores snake, food, and component data in a Soroban-serializable ECS container. |
SimpleQueryBuilder | Scans entities by component type for food, head, and segment queries. |
ComponentTrait | Gives each custom component deterministic serialization and a stable component type. |
This example does not use Cougr auth, privacy, ZK, or standards modules because Snake is intentionally a single-player arcade-loop reference.
Build and test commands
cargo test
stellar contract build
Known limitations
Recommended Testing Approach:
For comprehensive testing, use the GameHarness and Scenario APIs provided by cougr-core's testutils feature (see sandbox_tests.rs). This allows writing replayable multi-turn scenarios to verify movement trajectories, direction change validation, and tick updates.
Expected Output:
running 31 tests
test result: ok. 31 passed; 0 failed; 0 ignored
4. Lint
cargo fmt --check
cargo clippy -- -D warnings
Contract Functions
Initialization Functions
| Function | Parameters | Returns | Description |
|---|---|---|---|
init_game | - | - | Start with 10Γ10 grid |
init_game_with_size | grid_size: i32 | - | Start with custom grid |
Control Functions
| Function | Parameters | Returns | Description |
|---|---|---|---|
change_direction | direction: u32 | bool | Change movement direction |
update_tick | - | - | Advance game one step |
Direction Values:
| Value | Direction | Delta (x, y) |
|---|---|---|
| 0 | Up | (0, -1) |
| 1 | Down | (0, +1) |
| 2 | Left | (-1, 0) |
| 3 | Right | (+1, 0) |
Query Functions
| Function | Returns | Description |
|---|---|---|
get_score | u32 | Current score |
check_game_over | bool | Game ended status |
get_head_pos | (i32, i32) | Head coordinates |
get_snake_length | u32 | Total length |
get_food_pos | (i32, i32) | Food coordinates |
get_snake_positions | Vec<(i32, i32)> | All positions |
get_grid_size | i32 | Grid dimensions |
Deployment
Testnet Deployment
# 1. Generate keypair
stellar keys generate --global alice --network <NETWORK>
stellar keys address alice
# 2. Fund account (visit URL with your address)
# https://friendbot.stellar.org/?addr=<YOUR_ADDRESS>
# 3. Deploy contract
stellar contract deploy \
--wasm target/wasm32v1-none/release/snake.wasm \
--source alice \
--network <NETWORK>
# Save the returned Contract ID!
Playing the Game
CONTRACT_ID="<your-contract-id>"
# Initialize
stellar contract invoke --id $CONTRACT_ID --source alice --network <NETWORK> -- init_game
# Change direction (0=Up, 1=Down, 2=Left, 3=Right)
stellar contract invoke --id $CONTRACT_ID --source alice --network <NETWORK> -- change_direction --direction 0
# Advance game
stellar contract invoke --id $CONTRACT_ID --source alice --network <NETWORK> -- update_tick
# Check score
stellar contract invoke --id $CONTRACT_ID --source alice --network <NETWORK> -- get_score
Deployed Contract
| Network | Contract ID | Explorer |
|---|
Project Structure
examples/snake/
βββ Cargo.toml # Dependencies (cougr-core, soroban-sdk)
βββ README.md # This documentation
βββ .gitignore # Ignore rules (test_snapshots/, target/)
βββ src/
βββ lib.rs # Contract entry points (11 functions)
βββ components.rs # Components using cougr-core::ComponentTrait
βββ systems.rs # Game logic systems
βββ simple_world.rs # Entity-component storage
| File | Purpose |
|---|---|
lib.rs | Soroban contract with public functions and tests |
components.rs | Component definitions implementing ComponentTrait |
systems.rs | Game mechanics (movement, collision, spawning) |
simple_world.rs | Entity and component storage layer |
Creating Components
Using cougr-core's ComponentTrait
#![allow(unused)] fn main() { use cougr_core::component::{Component, ComponentStorage, ComponentTrait}; use soroban_sdk::{symbol_short, Bytes, Env, Symbol}; pub struct MyComponent { pub value: u32, } impl ComponentTrait for MyComponent { // Unique identifier for this component type fn component_type() -> Symbol { symbol_short!("mycomp") } // Serialize to bytes for on-chain storage fn serialize(&self, env: &Env) -> Bytes { let mut bytes = Bytes::new(env); bytes.append(&Bytes::from_array(env, &self.value.to_be_bytes())); bytes } // Deserialize from bytes fn deserialize(_env: &Env, data: &Bytes) -> Option<Self> { if data.len() != 4 { return None; } let value = u32::from_be_bytes([ data.get(0)?, data.get(1)?, data.get(2)?, data.get(3)? ]); Some(Self { value }) } // Choose storage strategy fn default_storage() -> ComponentStorage { ComponentStorage::Table // For dense data // ComponentStorage::Sparse // For marker components } } }
Converting to Component
#![allow(unused)] fn main() { impl MyComponent { pub fn to_component(&self, env: &Env) -> Component { Component::new(Self::component_type(), self.serialize(env)) } } }
Troubleshooting
| Issue | Solution |
|---|---|
| Rust version errors | rustup update && rustup default stable |
| WASM target missing | rustup target add wasm32v1-none |
| Stellar CLI not found | brew install stellar-cli (macOS) |
| Dependency conflicts | cargo update && cargo clean && cargo build |
| Test snapshots issues | Delete test_snapshots/ directory |
Full Verification
cargo fmt --check && cargo clippy -- -D warnings && cargo test && stellar contract build
References
| Resource | Link |
|---|---|
| Soroban Docs | developers.stellar.org |
| Stellar CLI | CLI Documentation |
| Cougr Repository | github.com/salazarsebas/Cougr |
| Rust Testing | Rust Book Ch. 11 |
License
- Food spawning is deterministic and suitable for examples, not adversarial randomness.
- There is one game state per contract instance.
- No authentication or ownership model is included.
- Rendering and real-time scheduling are out of scope; callers drive ticks through contract invocations.
spawn_and_move
Canonical example demonstrating SorobanGame, impl_component_observed!, and typed ECS.
Purpose and pattern
This example showcases a starter 2D grid world. A player can spawn an entity and move it in four directions. It demonstrates how to declare components, use observed components that automatically emit indexing events when modified, and load/save world state via SorobanGame.
Public contract API
| Function | Parameters | Returns | Description |
|---|---|---|---|
spawn | - | u32 | Spawns a new entity at origin (0,0) and returns its generated entity ID. |
move_entity | entity_id: u32, direction: u32 | - | Moves the entity in the specified direction if moves remain. |
position | entity_id: u32 | Option<Position> | Retrieves the current Position of the given entity. |
moves | entity_id: u32 | Option<Moves> | Retrieves the current Moves component of the given entity. |
entity_count | - | u32 | Retrieves the total number of entities spawned in the world. |
Architecture overview
ββββββββββββββββββββββββββ
β spawn_and_move Client β
βββββββββββββ¬βββββββββββββ
β Calls
ββββββββββββΌβββββββββββ
β SpawnAndMove Game β
β (Soroban Contract) β
ββββββββββββ¬βββββββββββ
β Loads / Saves
ββββββββββββΌβββββββββββ
β SimpleWorld β
ββββββββββββ¬βββββββββββ
β Stores
ββββββββββββββββββ΄βββββββββββββββββ
ββββββββΌβββββββ ββββββββΌβββββββ
β Position β β Moves β
β (Observed) β β (Standard) β
βββββββββββββββ βββββββββββββββ
When a client calls move_entity, the contract loads the SimpleWorld using SpawnAndMove::load_world, queries/modifies the Position and Moves components of the entity using typed ECS accessors, and saves the world state back using SpawnAndMove::save_world.
Storage model
All game components and entity metadata are stored in Soroban Instance Storage via the underlying SimpleWorld. This keeps the hot-loop gameplay state localized and loaded efficiently in a single storage read/write lifecycle per transaction.
Main gameplay flow
- Initialization / Spawn: The user calls
spawn. An entity is spawned at(0,0)with 10 moves remaining. A("COUGR", "set", "position")event is emitted. - Action / Movement: The user calls
move_entitywithdirection(0=North, 1=East, 2=South, 3=West). The remaining moves decrement, the position updates, and a position set event is emitted. - Query: The user reads the entity's position or moves remaining.
Cougr APIs used
SorobanGameandimpl_soroban_game!: Wires up standard boilerplate for loading/saving world state from instance storage.impl_component_observed!: Implements component layout with automated indexer events on set.impl_component!: Defines standard components without indexing event overhead.SimpleWorld: Provides structured, entity-component key-value management.
Recommended testing approach
Tests in this project should utilize the cougr-core testutils feature, specifically GameHarness and Scenario. The GameHarness registers the contract, while the Scenario allows executing multi-step and multi-turn movement verification with intermediate assertions.
Build and test commands
cargo test
stellar contract build
Known limitations
- Simple grid coordinates without map size constraints or collision checks.
- Unauthenticated entity movement (any caller can move any entity ID).
Tic Tac Toe
Transitional example: This example uses an older Cougr pattern and is preserved for compatibility reference. For the current recommended approach, see
snake.
An on-chain Tic Tac Toe game built with the Cougr ECS framework on
Stellar Soroban. Note: although this example already uses cougr-core's newest macro-based
component pattern (impl_rich_component! / impl_component! / impl_soroban_game!), it is
not on the canonical examples list, so it is marked transitional per the project standard - see "Cougr APIs used" below for why these specific macros were chosen over the manual
ComponentTrait implementations used in other transitional examples like reversi.
Purpose and pattern
This example demonstrates a two-player, perfect-information board game with a single shared
entity for all game state. It showcases Cougr's SimpleWorld entity/component storage driven
through the macro-generated RichComponentTrait/ComponentTrait implementations
(impl_rich_component!, impl_component!) and the SorobanGame trait
(impl_soroban_game!), which together remove the need to hand-write serialize/deserialize
or repeat the storage key in every contract function.
Public contract API
| Function | Parameters | Returns | Description |
|---|---|---|---|
init_game | player_x: Address, player_o: Address | GameState | Spawns the single game entity, seeds an empty 9-cell board, and sets X to move first. Overwrites any previous game. |
make_move | player: Address, position: u32 | MoveResult | Validates and applies a move at position (0β8). Returns success: false with a status message symbol instead of panicking on illegal input. |
get_state | - | GameState | Current board, both player addresses, whose turn it is, move count, and status. |
is_valid_move | position: u32 | bool | Whether position is a legal move right now (in range, empty cell, game still in progress). |
get_winner | - | Option<Address> | The winning player's address, or None if the game is in progress or drawn. |
reset_game | - | GameState | Re-initializes the board with the same two players, discarding moves. |
Status codes
GameState.status: 0 = in progress, 1 = X wins, 2 = O wins, 3 = draw.
MoveResult.message: ok, invalid (out of range), occupied, notturn, notplay,
gameover.
Architecture overview
src/
βββ lib.rs # #[contract] struct, SorobanGame wiring, #[contractimpl] entrypoints
βββ components.rs # Board, Players, TurnState component structs + macro invocations
βββ systems.rs # detect_winner: pure win/draw detection over board cells
make_move runs a fixed validation/execution sequence synchronously on each call - there is
no GameApp tick loop:
make_move
ββ load Players, TurnState, Board from the single game entity
ββ validate: game not over, position in range, caller is a player, caller's turn, cell empty
ββ apply the mark, call systems::detect_winner over the updated board
ββ persist updated Board and TurnState, save the world
components.rs owns the three SimpleWorld components and the shared GAME_ENTITY
constant; systems.rs holds the one pure function (detect_winner) that contains no
storage access; lib.rs owns the #[contract] struct, the SorobanGame wiring, and is the
only module that loads/saves the SimpleWorld.
Storage model
All state lives in instance storage, managed transparently by impl_soroban_game!
through SorobanGame::load_world/save_world under a single key ("ttt_world"). Within
that one SimpleWorld, all three components (Board, Players, TurnState) are attached
to a single fixed entity id (GAME_ENTITY = 1), since there is exactly one game per contract
instance and no dynamic entity population to query. Instance storage is appropriate here
because the game state must live for the lifetime of the contract instance with no per-entry
TTL management.
Main gameplay flow
- Deployer calls
init_game(player_x, player_o); a new entity is spawned, the board is seeded with 9 empty cells, andTurnStateis set to X's turn, move count 0, status in-progress. - X calls
make_move(player_x, position). The contract checks the game isn't over, the position is in range, the caller is a registered player, and it's their turn. - On a legal move, the cell is marked,
systems::detect_winnerre-checks all 8 winning lines plus the draw condition, and turn state is updated (status, move count, whose turn is next). - Players alternate
make_movecalls untildetect_winnerreports a win (status1or2) or a draw (status3), after which further moves returngameover. - Either player can call
get_stateorget_winnerto read the outcome, orreset_gameto start over with the same two players.
Cougr APIs used
cougr_core::{impl_rich_component!}- used forBoard(holds aVec<u32>) andPlayers(holds twoAddressvalues). Both fields require Soroban's XDR codec rather than fixed-size byte packing, soimpl_rich_component!was chosen to getRichComponentTraitfor free from the#[contracttype]derive, avoiding a hand-writtenserialize/deserializepair forVec<u32>andAddresslike the onereversi'scomponents.rsstill carries.cougr_core::{impl_component!}- used forTurnState, which is three fixed-size plain fields (bool,u32,u32).impl_component!generates a compact, fully typedComponentTraitimplementation (byte-packed, not XDR) since there are noAddress/Vecfields needing the heavier rich-component codec.cougr_core::game::SorobanGame/impl_soroban_game!- generatesload_world/save_worldfor the#[contract]struct so every entrypoint can read and persist theSimpleWorldwithout repeating the instance-storage key ("ttt_world") or its get/set boilerplate.cougr_core::simple_world::SimpleWorld- used as the single source of truth for the game's three components, attached to one fixed entity rather than a dynamic population, since tic-tac-toe has exactly one game per contract instance.
This example does not use GameApp, ScheduleStage, SimpleQueryBuilder, auth, or
privacy - see Known limitations.
Build and test commands
cargo test
stellar contract build
Known limitations
- Does not use
GameApporScheduleStage- validation and win detection run synchronously insidemake_movesince a turn-based game with one decision point per call has no need for staged scheduling. - Does not use
SimpleQueryBuilder- there is exactly one game entity per contract instance, so there is no entity population to scan by component type. - No timeout/forfeit mechanism for an unresponsive player.
- No spectator or replay API beyond the read-only getters.
Design
β³ This section is being built. All design documents are gaps identified in
docs/strategy/12-documentation-architecture.mdand will be produced alongside the design system rollout.
The Design section contains guidelines for anyone building a client application against Cougr, contributing visual work, or working on the docs site itself.
| Guide | Status |
|---|---|
| Branding Guide | π Coming soon |
| UI Guidelines | π Coming soon |
| UX Guidelines | π Coming soon |
| Accessibility | π Coming soon |
Branding Guide
β³ This page is being written.
Will cover: logo usage, color palette, typography, design tokens, and the "Cougr Verified" badge. Full specification in docs/strategy/09-design-strategy.md in the main repository.
UI Guidelines
β³ This page is being written.
Will cover UI patterns for building a client application against a Cougr game contract. The murdoku frontend in the main repo is the first worked reference this guide will draw from.
UX Guidelines
β³ This page is being written.
Will cover player-facing UX patterns - wallet connection, session key UX, and transaction feedback - distinct from the developer UX covered in the Learn section.
Accessibility
β³ This page is being written.
Will cover accessibility requirements for both this documentation site and for client applications built on Cougr. Currently unaddressed anywhere in the project - flagged in docs/strategy/12-documentation-architecture.md.
Community
Welcome to the Community section. This is where you'll find everything about how to contribute, how decisions are made, and how to stay up to date.
| Document | Description |
|---|---|
| Contributing | How to open issues, write code, and get PRs merged |
| Code of Conduct | Expected behaviour in all project spaces |
| Governance | How decisions are made, who can merge, how disputes are resolved |
| Security | How to report vulnerabilities |
| Roadmap | Where the project is headed |
| RFC Process | How to propose significant changes before implementing them |
| Changelog | What changed in each release |
Contributing
Contributions should improve the framework, the example catalog, or the supporting documentation with a clear purpose. This repository is structured to be useful both as a reusable library and as a reference codebase, so changes should optimize for correctness, clarity, and maintainability.
Scope
Good contributions typically fall into one of these categories:
| Area | Expected outcome |
|---|---|
| Core framework | Improved ECS, scheduling, storage, authorization, or zero-knowledge capabilities |
| Examples | New game patterns, better reference implementations, or tighter example documentation |
| Documentation | Clearer architecture, setup, or usage guidance aligned with the current codebase |
| Quality | Better tests, tooling, validation, or CI coverage |
Development Standards
- Keep changes focused. Avoid mixing unrelated refactors with feature work.
- Update documentation when behavior, structure, or public APIs change.
- Prefer clear names and straightforward control flow over clever abstractions.
- Preserve repository consistency. New files should fit the existing layout and conventions.
- Do not add generated reports, ad hoc summaries, or temporary planning documents to the repository root.
Local Validation
Run the relevant checks before opening a pull request:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
If you modify an example project, also run that example's local checks from its own directory. If the example supports Soroban contract builds, validate that flow as well.
Documentation Expectations
Documentation should be professional, current, and proportionate:
- avoid stale exact counts when the repository is expected to grow
- explain decisions and usage patterns without turning every page into a long-form essay
- use tables when they improve scanability, not as a default for all content
- keep root-level documentation limited to material with clear long-term value
- follow the terminology and voice rules in docs/VOICE_GUIDE.md for all doc, example, and marketing copy
Pull Requests
Pull requests should make it easy to review technical intent. A strong PR description usually covers:
- what changed
- why the change was needed
- how it was validated
- any follow-up work or constraints reviewers should know about
Adding Examples
When adding a new example:
- make the example self-contained
- include a local
README.md - keep the example focused on one or two clear patterns
- add CI coverage when the example is meant to remain a maintained reference
Review Criteria
Changes are more likely to be accepted when they:
- solve a real problem in the framework or examples
- keep the API and repository structure coherent
- include appropriate validation
- improve the repository without increasing maintenance noise
Public API Checklist
Changes that touch public Rust APIs should be reviewed against this checklist before merge:
- the symbol belongs to the curated onboarding path or an intentional namespace such as
accounts,zk::stable, orzk::experimental - stable, beta, experimental, and test-only surfaces are not mixed in the same default entrypoint
- new public names do not duplicate an existing public concept
- root-level re-exports are intentional and minimal
- examples and integration tests use the sanctioned public path instead of deep internal module paths
- documentation is updated to match the actual exported API
Code of Conduct
All contributors are expected to follow the project's Code of Conduct. Reports of unacceptable behavior can be sent to the project maintainers through GitHub.
Code of Conduct
β οΈ This document is an urgent gap identified in
docs/strategy/12-documentation-architecture.md: "Missing despite 25+ active external contributors. Should be added immediately, independent of any other work in this package - this is a near-zero-cost fix for a real, present governance gap."Tracked in:
salazarsebas/Cougrissues
A Code of Conduct will be added here imminently. It will be based on the Contributor Covenant and apply to all project spaces including GitHub Issues, Pull Requests, and any community channels.
Governance
β³ This page is being written.
Identified as a gap in
docs/strategy/12-documentation-architecture.md. No documented decision-making process exists yet.
Will cover:
- Who can merge pull requests
- How disputes over public API changes are resolved
- How maintainer status is granted and revoked
- The relationship between the existing
CONTRIBUTING.mdPublic API Checklist and final authority on API decisions
Tracked in: salazarsebas/Cougr issues
Security Policy
Status
Cougr now defines a 1.0.0 stable contract for a scoped subset of the crate. Not every public subsystem is part of that stable guarantee.
Security-sensitive areas include:
- account authorization
- session lifecycle and replay protection
- persistent storage integrity
- proof verification and privacy primitives
- ECS mutation ordering where authorization depends on state transitions
Maturity and Guarantees
Current guidance:
| Area | Status | Guidance |
|---|---|---|
| ECS runtime and storage | Stable | Part of the 1.0 contract when used through the documented onboarding and runtime surfaces |
| Accounts and smart-account flows | Beta | Do not assume full production guarantees without project-specific review |
Standards layer (standards) | Stable | Reusable contract primitives are part of the 1.0 stable contract |
Privacy primitives (zk::stable) | Stable | Commit-reveal, hidden-state codecs, and Merkle utilities are the stable privacy contract |
| Advanced ZK verification | Experimental | Treat as non-stable until verification contracts and assumptions are fully hardened |
The latest maturity definitions live in docs/MATURITY_MODEL.md. The current threat-model baseline lives in docs/THREAT_MODEL.md. The explicit compatibility story lives in docs/COMPATIBILITY_PROMISES.md.
Threat Model Expectations
Cougr does not currently claim:
- external audit coverage
- formal verification
- full production guarantees across all auth and privacy paths
- stable compatibility guarantees for experimental modules
Before adopting Cougr in security-critical deployments, review at minimum:
- auth and signer flows
- replay handling
- session scope and revocation rules
- storage schema assumptions
- proof verification assumptions
Reporting a Vulnerability
If you find a security issue:
- Do not open a public issue with exploit details.
- Report the issue privately to the project maintainers.
- Include:
- affected module
- reproduction steps
- impact assessment
- version or commit information
- suggested mitigation if available
The monitored disclosure channel is GitHub Private Vulnerability Reporting:
https://github.com/salazarsebas/Cougr/security/advisories/new
security@cougr.dev is reserved as a future dedicated inbox and is not yet independently staffed. Do not rely on it for acknowledgment SLAs.
Supported Versions
The latest stable release line and current mainline development state should be assumed relevant for fixes unless a maintenance policy says otherwise.
Secure Contribution Expectations
Changes affecting auth, privacy, storage, or unsafe internals should include:
- updated invariants or trust assumptions
- negative-path tests
- compatibility notes when public behavior changes
- documentation changes when guarantees or maturity shift
Roadmap
β³ This page is being written.
No
ROADMAP.mdexists in the main repository yet. Perdocs/strategy/12-documentation-architecture.md: "Should be a public, living version of 13-roadmap.md, updated quarterly, not a one-time publish."
The public roadmap will be maintained here once established. It will track:
- Near-term:
cougr-cli(cougr new,cougr add,cougr check) - Medium-term: TypeScript client SDK, resource-cost reporting in test harness
- Long-term: Visual editor, showcase gallery, hosted-service option
Tracked in: salazarsebas/Cougr issues
RFC Process
β³ This page is being written.
The ADR practice (
docs/adr/) covers internal architecture decisions well. An RFC process is the public-facing counterpart for changes the community should weigh in on before they happen.Per
docs/strategy/12-documentation-architecture.md: "Recommend adopting a lightweight RFC template modeled directly on the existing ADR format, since the team already has the discipline to use it well."
When the RFC process is established it will cover:
- What kinds of changes require an RFC (public API changes, new primitives, breaking changes)
- The RFC template (based on the existing ADR format)
- How to submit, how long the comment period is, and who has final say
Tracked in: salazarsebas/Cougr issues
Changelog
Unreleased
Added
cougr_core::cors- CORS configuration validation and dynamic origin allowlist for HTTP gateways in front of a game contract:CorsConfigvalidates origins, methods, header names, and credential/wildcard combinations;OriginAllowlistsupports runtime add/remove of exact, single-label wildcard subdomain (https://*.example.com), and global*entries with case/default-port-normalized matching; preflight evaluation returns ready-to-emit response headerscougr-cli- new workspace member publishing thecougrbinarycougr new <name> [--template <name>]- scaffolds a Soroban game contract crate following the canonicallib.rs/components.rs/systems.rslayout, with a passingtest::GameHarnesssuite and a dependency on the publishedcougr-corerelease rather than a path dependency- Four embedded templates, each derived from a canonical example and compiled into
the binary so
cougr newworks offline:starter(spawn_and_move),turn-based(tic_tac_toe),hidden-info(hidden_hand),session-auth(session_arena) - CLI CI workflow - lints and tests
cougr-cli, then generates each template and runscargo fmt,clippy,cargo test, and awasm32v1-nonerelease build against it
1.1.0
Added
game::SorobanGametrait - standardload_world/save_worldcontract pattern; implement once withimpl_soroban_game!(Contract, "key"), use in every entrypointimpl_soroban_game!macro - wiresSorobanGameto any#[contract]structSimpleWorld::load_from_instance- load world from Soroban instance storage, returning a fresh empty world on first callSimpleWorld::save_to_instance- persist world to Soroban instance storageSimpleWorld::set_rich_observed- store a rich component and emit aRichComponentChangedEventfor off-chain indexersSimpleWorld::remove_rich_observed- remove a rich component and emit adeleventRichComponentChangedEvent- new Soroban event type with topics("COUGR", "rich", component_type)for rich component change notificationsspawn_and_moveexample - canonical Cougr starter game demonstrating the complete idiomatic pattern:impl_component_observed!+SorobanGame+ typed ECS accessSorobanGamere-exported fromprelude- import fromcougr_core::prelude::*cougr_core::circuits- four pre-built ZK game builders (hidden cards, fog of war, fair dice, sealed bid) with pipeline-embedded verification keyscougr_core::session-SessionManager,SessionStatus, andActiveSession(Beta)cougr_core::test-GameHarness,Scenario, andReplayLogsandbox behind thetestutilsfeature- Circom pipeline -
internal/cougr-core-circuitswith CI workflow and on-chain Groth16 proof verification using real VKs - ZK examples -
hidden_hand,fog_explorer,dice_duel, andblind_auction - Workspace subcrates -
internal/cougr-core-{circuits,session,test}per ADR 0007
Changed
tic_tac_toeexample modernised: replaced ~200 lines of manual serialization withimpl_rich_component!forBoardandPlayers, andimpl_soroban_game!for load/save. Public API is unchanged; all existing tests pass- README rewritten with clean 30-line quick start and full feature documentation
- canonical example set expanded from three (
snake,battleship,guild_arena) to ten:spawn_and_move(Starter),tic_tac_toe(Rich components),session_arena(Session UX),hidden_hand,fog_explorer,dice_duel,blind_auction(ZK circuits),snake(Arcade/GameApp),battleship(Hidden information),guild_arena(Auth & recovery) session_arenaexample added as canonical reference forsession::SessionManager
Stability Notes
game::SorobanGameis StableSimpleWorld::load_from_instance/save_to_instanceare Stableset_rich_observed/remove_rich_observedare StableRichComponentChangedEventis Stablecougr_core::sessionis Betacougr_core::circuitsand embedded test VKs are Experimentalcougr_core::testis Experimental (testutilsonly)
1.0.0
Added
appas the default gameplay runtime surfaceauth,privacy, andopsas product-level domain namespacesRuntimeWorldandRuntimeWorldMutas shared Soroban-first backend contracts- stronger stage scheduling with ordering, sets, and validation
SimpleQueryBuilder, query state/cache improvements, and richerArchetypeWorldquery helpers- expanded benchmark coverage for backend comparisons and cache invalidation behavior
Changed
- the recommended onboarding path is now
app::GameApp+SimpleWorld+SimpleQueryBuilder - canonical examples now emphasize the curated runtime story and explicit maturity boundaries
battleshipnow uses stable privacy primitives fromzk::stable- documentation now treats
SimpleWorldandArchetypeWorldas the defended Soroban-first backends
Stability Notes
- Stable: ECS onboarding/runtime contract,
app,ops,standards,privacy::stable,zk::stable - Beta:
auth,accounts,game_world - Experimental:
privacy::experimental,zk::experimental, hazmat cryptographic helpers
Upgrade Notes
- Prefer
appover wiring scheduler/world primitives directly for new gameplay code - If you still have pre-1.0 code built around removed runtime abstractions, port directly to
GameApp,SimpleWorld, andSimpleQuery - Prefer
ops,privacy, andauthin application code when you want domain-oriented imports - Treat root-level advanced re-exports as compatibility/advanced surfaces rather than the default learning path
- See docs/MIGRATION_GUIDE.md for concrete migration mappings