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:

ToolInstall command
Rust`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs
Stellar CLIcargo install stellar-cli --features opt
wasm32 targetrustup 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

  1. 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.
  2. 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.
  3. 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 murdoku or battleship) in the examples/ 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.

GuideStatus
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:

BackendFileStrategyBest for
SimpleWorldsrc/simple_world/Map<(EntityId, Symbol), Bytes> with dual Table/Sparse indexesGeneral use, small entity counts
ArchetypeWorldsrc/archetype_world/Groups entities by component signatureLarge 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:

MacroWhen 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, behind hazmat-crypto feature
  • 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) - GameCircuit trait + pre-built circuits (Movement, Combat, Inventory, TurnSequence) + CustomCircuitBuilder
  • ECS integration (components.rs, systems.rs) - CommitReveal, HiddenState, ProofSubmission components 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:

  • Ownable and Ownable2Step for owner-managed authority
  • AccessControl for role-based authorization with delegated admins
  • Pausable for emergency stops
  • ExecutionGuard for serialized critical sections
  • RecoveryGuard for blocking sensitive paths during recovery windows
  • BatchExecutor for bounded multi-operation flows
  • DelayedExecutionPolicy for 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 moduleSourceMaturityFeature
cougr_core::circuitssrc/circuits/Experimentalalways
cougr_core::sessionsrc/session/Betaalways
cougr_core::testsrc/test/Betatestutils

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

FlagEnables
hazmat-cryptoPoseidon2 hash, BN254 curve ops (via soroban-sdk/hazmat-crypto)
testutilscougr_core::test sandbox, MockAccount, Soroban test helpers
debugRuntime 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...UseExampleRead more
Fairness (a roll, a draw, an outcome no one can predict or bias)circuits::FairDiceBuilder - on-chain Groth16-verified randomness (Experimental)dice_duelHidden Information Guidance below, PRIVACY_MODEL.md
Hidden information (cards, ship positions, sealed bids - state some players shouldn't see)privacy::stable commit-reveal + Merkle primitivesbattleship (canonical), rock_paper_scissors, hidden_hand, blind_auctionHidden 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 changespawn_and_move (start here), snakeSystem 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 AccountKernelguild_arenaACCOUNT_KERNEL.md Β§ Signers
A session players approve once, not per-transactionSession signer + SessionPolicy (scope, expiry, operation budget)session_arenaACCOUNT_KERNEL.md Β§ Session Model
Account recovery if a device is lostGuardianPolicy + ActiveDevicePolicyguild_arenaACCOUNT_KERNEL.md Β§ Policies
An emergency stop / pause switchPausable-STANDARDS_LAYER.md Β§ Pausable
To serialize mutations / guard against reentrancy-like issuesExecutionGuard-STANDARDS_LAYER.md Β§ ExecutionGuard
Delayed or timelocked executionDelayedExecutionPolicy-STANDARDS_LAYER.md Β§ DelayedExecutionPolicy
To batch several operations safelyBatchExecutor-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 statebattleship, snake, blind_auctionONCHAIN_OFFCHAIN_BOUNDARY.md
To know whether I even need ECSDirect contract model for small/config-driven contracts-When Not To Use ECS below
To pick table vs. sparse storageTable for hot-loop state, sparse for infrequent markers-Storage Guidance below
A thin, explicit contract entrypoint / gameplay loopGameApp + explicit stage placementspawn_and_move, snakeDefault 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:

  1. build the app
  2. register plugins and startup systems
  3. register tick systems into explicit stages, preferably with named_system(...) / named_context_system(...)
  4. 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 setup
  • PreUpdate: input decoding, action validation, turn preparation
  • Update: core gameplay state transitions
  • PostUpdate: scoring, derived-state maintenance, indexing side effects
  • Cleanup: 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::stable Merkle 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 Update systems 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.

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.md and docs/strategy/12-documentation-architecture.md.

Tracked in: salazarsebas/Cougr issues


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.md and STANDARDS_LAYER.md into one place, distinguishing "Cougr patterns" from "general Soroban patterns."

Tracked in: salazarsebas/Cougr issues


What this guide will cover

  • Access control with AccessControl and Ownable
  • 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, and SnapshotAssert exist in the codebase and are used across thousands of lines of tests, but have no standalone guide yet. Named explicitly in docs/strategy/08-ux-strategy.md Stage 5.

Tracked in: salazarsebas/Cougr issues


What this guide will cover

  • Setting up GameHarness for unit tests
  • Writing Scenario-based integration tests
  • Using SnapshotAssert to lock in expected world state
  • How the Soroban test sandbox works (and how it differs from running cargo test for 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/Cougr issues


What this guide will cover

  • Configuring the Stellar CLI for Testnet and Mainnet
  • Building a release .wasm binary 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.

DocumentDescription
ECS CoreThe core ECS primitives: Entity, Component, Query, System, Scheduler
Account KernelAccount abstraction layer - session keys, recovery, passkeys
Standards LayerReusable contract standards: AccessControl, Pausable, Ownable, etc.
Privacy ModelZK proofs, Pedersen commitments, hidden state
Feature Flagshazmat-crypto, testutils, debug - what each enables
Performance GuideResource cost intuition, benchmarks, optimization patterns
API ContractPublic API guarantees and stability promises
Compatibility PromisesWhat Cougr will and won't break between releases
Migration GuideHow to update your game for new cougr-core versions
CLI ReferenceπŸ”œ Ships with the CLI
Client SDK ReferenceπŸ”œ Ships with the TypeScript SDK
ADRsArchitecture 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 identity
  • Component: typed or raw data attached to entities
  • Query: a declarative selection over entities by component presence
  • System: logic that reads or mutates the world
  • CommandQueue: deferred structural mutations
  • GameApp: app-level orchestration over world + scheduler + plugins
  • RuntimeWorld / RuntimeWorldMut: the shared backend contract for Soroban-first worlds

For Soroban gameplay contracts, the recommended path is:

  • app
  • SimpleWorld
  • SimpleQuery
  • SimpleScheduler
  • GameApp

ArchetypeWorld is the alternate backend for heavier query workloads.

The shared stable overlap between those backends lives in:

  • ecs::RuntimeWorld
  • ecs::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:

  1. README.md
  2. GameApp
  3. SimpleWorld
  4. SimpleQueryBuilder
  5. 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
  • 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
  • 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:

  • IntentExpiryPolicy
  • SessionPolicy
  • ActiveDevicePolicy
  • GuardianPolicy

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:

  • Ownable and Ownable2Step require an explicit caller address
  • AccessControl checks the caller against the relevant admin role
  • Pausable, 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:
    • CommitmentScheme
    • MerkleProofVerifier
    • HiddenStateCodec
    • ProofVerifier as an interface contract only

These are exposed through:

  • cougr_core::privacy::stable
  • cougr_core::zk::stable as 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::experimental
  • cougr_core::zk::experimental as 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

SurfaceStatusNotes
CommitmentsStableExplicit interface and verification contract
Commit-revealStableExplicit component semantics and deadline behavior
Hidden-state encodingStableStable codec interface; fixed-width codecs can be defended
Merkle inclusion and sparse Merkle utilitiesStableMalformed proof behavior and inclusion semantics are explicit
Proof submission systemsBetaUseful orchestration, but still coupled to experimental verification flows
Groth16 verification and prebuilt circuitsExperimentalAssumptions 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() != depth return ZKError::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

FlagMaturityIntended useNotes
debugSupport-onlyLocal diagnostics and introspectionExposes runtime snapshots and metrics that are not part of the stable product contract
hazmat-cryptoExperimentalAdvanced ZK and cryptographic integrationsEnables low-level host crypto helpers; do not treat as part of the stable privacy promise
testutilsNon-contract support surfaceTests and explicit test-utility consumersEnables 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:

  • auth mirrors the Beta accounts surface
  • privacy::stable and privacy::experimental mirror the split inside zk
  • ops mirrors the stable standards surface

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_index for table-backed components
  • all_index for table + sparse lookups

That changes the expected behavior of the common query paths:

  • get_table_entities_with_component() uses the direct table index
  • get_all_entities_with_component() uses the all-storage index
  • SimpleQuery selects 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 ArchetypeWorld outperform SimpleWorld
  • 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
  • SimpleWorld vs ArchetypeWorld multi-component query comparison
  • SimpleWorld vs ArchetypeWorld structural mutation comparison

Reading The Current Benchmarks

Interpret the benchmark output in this order:

  1. Query Paths If plain indexed queries and cached queries are already cheap enough, stay on SimpleWorld.
  2. Backend Query Comparison If ArchetypeWorld is materially better on your real multi-component query shape, it may be worth adopting.
  3. Backend Structural Mutation Comparison If archetype migration is significantly more expensive for your workload, do not switch just because query numbers look better in isolation.
  4. Query Cache Invalidation If 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.

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-core is primarily an ECS framework for Soroban-compatible applications
  • app is the default gameplay runtime surface for new projects
  • auth, privacy, and ops are 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 standards are part of the 1.0 stable contract
  • helper APIs that exist only for compatibility or transition should remain clearly demoted

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:

  • SimpleWorld
  • ArchetypeWorld
  • ecs::{RuntimeWorld, RuntimeWorldMut, WorldBackend}
  • typed and raw component operations
  • command queues
  • scheduling primitives
  • events, hooks, and observers
  • incremental persistence utilities

Concrete frozen root-level contract:

  • SimpleWorld
  • ArchetypeWorld
  • CommandQueue
  • Component, ComponentTrait, ComponentStorage, ComponentId
  • SimpleQuery, SimpleQueryBuilder
  • RuntimeWorld, RuntimeWorldMut, WorldBackend
  • Resource
  • runtime::ChangeTracker, runtime::TrackedWorld
  • Plugin, PluginGroup, GameApp
  • ScheduleStage, SystemConfig, SimpleScheduler, SystemGroup
  • prelude
  • runtime
  • app
  • ops as the clearest Stable standards namespace
  • standards as a Stable namespace
  • privacy::stable as the clearest stable privacy namespace
  • zk::stable as the stable privacy namespace
  • auth as the clearest Beta account namespace
  • accounts as a Beta namespace
  • privacy::experimental as an explicitly non-contract namespace
  • zk::experimental as 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:

  • app
  • auth
  • accounts
  • archetype_world
  • commands
  • component
  • debug behind feature flag
  • error
  • event
  • ops
  • privacy
  • plugin
  • query
  • resource
  • scheduler
  • simple_world
  • zk

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:

  • app
  • auth
  • privacy
  • ops
  • SimpleWorld
  • ArchetypeWorld
  • CommandQueue
  • GameApp
  • app::{named_system, named_context_system} and add_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:

  • app is the clearest default runtime namespace for new gameplay code
  • auth is the clearest Beta auth namespace for application code
  • privacy is the clearest domain namespace for privacy adoption, with stability determined by submodule
  • ops is the clearest stable namespace for operational standards in application code
  • root re-exports and prelude are the default onboarding path
  • runtime is the supported namespace for advanced ECS integrations that are not part of the smallest onboarding contract
  • query and archetype_world retain their cache/state helpers outside the smallest root onboarding surface
  • standards is a supported stable namespace
  • accounts remains a public Beta namespace
  • zk::stable is the only privacy namespace treated as Stable
  • zk::experimental remains 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
  • prelude
  • runtime
  • ops
  • standards
  • privacy::stable
  • zk::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
  • auth
  • accounts
  • 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::experimental
  • zk::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:

1.0 Freeze Decisions

The 1.0 release gate decisions are:

  • ECS onboarding and runtime surfaces are in the stable contract
  • ops is the stable domain alias for standards
  • standards is in the stable contract
  • auth is a Beta domain alias and is not part of the stable guarantee
  • accounts remains Beta and is not part of the stable guarantee
  • privacy::stable maps to the frozen privacy contract
  • zk::stable is the frozen privacy contract
  • privacy::experimental remains outside compatibility guarantees
  • zk::experimental remains 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:

  • app for gameplay runtime
  • auth for account and session flows
  • privacy::stable for stable privacy primitives
  • ops for 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:

  • SimpleQueryBuilder
  • SimpleQueryState
  • SimpleQueryCache

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:

  • RuntimeWorld
  • RuntimeWorldMut

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:

  • snake for app::GameApp and stage-based gameplay loops
  • battleship for privacy::stable and hidden-information patterns
  • guild_arena for 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 app where 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-cli crate 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, and cougr check subcommands.

Tracked in: salazarsebas/Cougr issues

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/Cougr issues

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.

ADRTitle
0001Public API Surface
0002Accounts Beta
0003Privacy Model Split
0004Standards Layer Stable
0006Game Circuit Suite
0007Workspace 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
  • standards as a Stable namespace
  • zk::stable as the stable privacy namespace
  • accounts as a Beta namespace
  • zk::experimental as 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::stable for commitments, commit-reveal, hidden-state codecs, and Merkle verification
  • zk::experimental for 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 testutils feature 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 standards as 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

  1. Ship four pre-built circuit builders under cougr_core::circuits (always available, Experimental maturity):

    BuilderPublic 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
  2. Each builder returns GameCircuitSpec with a frozen PublicInputLayout, placeholder VK (correct IC length), and typed verify methods that delegate to zk::experimental::verify_groth16. Production deploys replace the VK via with_verification_key.

  3. fog_of_war reuses FogOfWarCircuit in zk::advanced - no duplicate verification logic.

  4. Circom scaffolds and off-chain scripts live in internal/cougr-core-circuits/ (publish = false). Rust implementation lives in src/circuits/.

  5. 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 CircuitId and 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::stable remains 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

  1. Add a Cargo workspace with three internal members under internal/:

    • cougr-core-circuits
    • cougr-core-session
    • cougr-core-test
  2. Each internal member sets publish = false.

  3. Each layer's implementation lives in src/{circuits,session,test}/inner.rs. The public modules include! that file; internal workspace members point their [lib] path at the same inner.rs for isolated cargo check -p runs. This avoids:

    • circular dependencies (session needs auth from the same crate)
    • cargo publish failures on unpublished path deps
    • missing files in the published tarball
  4. Public API:

    • cougr_core::circuits - always available
    • cougr_core::session - always available
    • cougr_core::test - testutils feature only
  5. The test sandbox uses no_std + alloc, not std. It runs in Soroban testutils environments the same way contract tests do today.

Consequences

  • One cargo add cougr-core for all capabilities
  • Internal folders can still be checked with cargo check -p cougr-core-session
  • cargo publish ships internal/** sources inside the cougr-core tarball
  • Feature testutils keeps 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 standards as 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.

  • 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:

  1. examples/catalog.toml β€” structured metadata (category, maturity, Cougr features, optional screenshot/testnet contract).
  2. Each example's README.md β€” the description and full documentation.
  3. 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

Hidden Information Canonical
privacycommit-revealgame-appecs
battleship preview

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:

  1. Proof is valid against stored merkle_root
  2. Records hit (value=1) or miss (value=0)
  3. Updates ship count
  4. 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

FunctionParametersDescription
new_gameplayer_a: Address
player_b: Address
Initialize game
commit_boardplayer: Address
commitment: BytesN<32>
merkle_root: BytesN<32>
Commit board layout
attackattacker: Address
x: u32, y: u32
Attack coordinates (0-9)
reveal_celldefender: Address
x: u32, y: u32
value: u32
proof: 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

PatternBest ForCostComplexity
Commit-Reveal + MerkleHidden boards, card hands, fog-of-warO(log n) proof verificationLow - uses standard SHA256
ZK Circuits (Groth16/Poseidon)Private game logic evaluation, hidden card dealsSingle on-chain verificationHigh - 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 salt
  • merkle_root_a/b - root of the Merkle tree built from cell hashes
  • has_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 to CellResult (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:

  1. Constructs the expected leaf hash from (index, value) using the same SHA256 scheme as the commit phase
  2. Validates the OnChainMerkleProof against the stored merkle_root using Sha256MerkleProofVerifier
  3. 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

ComponentFieldsPurpose
BoardCommitmentcommitment: BytesN<32>
merkle_root: BytesN<32>
Cryptographic board commitment
AttackGridcells: Map<u32, CellResult>Public record of attacks
ShipStatusremaining_a: u32
remaining_b: u32
Ship cell counts
TurnStatecurrent_player: Address
phase: Phase
has_pending: bool
Game state management

Systems

SystemResponsibility
CommitSystemValidates and stores commitments
AttackSystemRecords attack coordinates
RevealSystemVerifies stable OnChainMerkleProof, updates grid
WinConditionSystemDetects when all ships sunk

Why Merkle Proofs?

Merkle trees enable selective disclosure:

ApproachReveal CostPrivacy
Full board on-chainO(1)❌ None
Reveal entire board per attackO(n)❌ None
Merkle proofO(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

License

MIT OR Apache-2.0

blind_auction

Other Canonical
soroban-gamezk-circuitsprivacy

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

FunctionParametersReturnsDescription
init_auctionmax_bid: u32, auction_id: BytesN<32>AuctionConfigStarts a new auction config with maximum bid limits and an auction identifier.
reveal_bidbidder: Address, bid_commitment: BytesN<32>, revealed_bid: u32, proof: Groth16ProofboolVerifies a bidder's reveal proof, storing the bid record if valid.
bid_revealbidder: AddressBidRevealRetrieves 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

  1. Setup: Call init_auction to setup maximum bid constraints and the auction ID.
  2. Commit Phase: Bidders record hash commitments of their bids (handled off-chain or via standard storage).
  3. Reveal Phase: Bidders call reveal_bid with 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.

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

Other Canonical
soroban-gamezk-circuitsprivacy

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

FunctionParametersReturnsDescription
init_duelsides: u32, seed_commitment: BytesN<32>DuelConfigRegisters the dice parameters and the starting cryptographic seed commitment.
submit_rollplayer: Address, roll_result: u32, nonce: u32, proof: Groth16ProofboolVerifies a dice roll ZK proof and updates the roll record if valid.
roll_recordplayer: AddressRollRecordRetrieves 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

  1. Setup: Call init_duel to bind the dice properties and seed commitment.
  2. Roll: Players roll dice off-chain and calculate proof.
  3. Submit: Players call submit_roll to 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.

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

Other Canonical
soroban-gamezk-circuitsprivacy

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

FunctionParametersReturnsDescription
init_mapwidth: u32, height: u32, visibility_radius: u32MapConfigInitializes the map configuration and builds the ZK circuit specs.
register_explorerplayer: Address, explored_root: BytesN<32>ExplorerStateRegisters a player with their starting empty explored map commitment.
exploreplayer: 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: Groth16ProofboolVerifies a transition proof, updating the player's explored map root if valid.
explorer_stateplayer: AddressExplorerStateRetrieves 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

  1. Map Config: Call init_map to configure map dimensions and visibility boundaries.
  2. Registration: Explorer calls register_explorer to record their initial map state.
  3. Exploration: Explorer calls explore with 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.

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

Other Canonical
authstandardsgame-app

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:

  1. Social Recovery - guild members act as guardians who can collectively restore account access after a timelock period
  2. 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

FunctionDescription
register_playerRegister with guardians and recovery config
add_deviceAdd a device key with policy (Full or PlayOnly)
remove_deviceRevoke a device key
start_matchQueue for or start a PvP match
submit_actionSubmit combat action (Attack/Defend/Special)
initiate_recoveryGuardian starts recovery process
approve_recoveryGuardian approves recovery
finalize_recoveryComplete recovery after timelock
get_playerQuery player profile
get_matchQuery current arena state

Device Policies

LevelPlayTrade/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

Card Canonical
soroban-gamezk-circuitsprivacy

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

FunctionParametersReturnsDescription
init_tabledeck_size: u32, hand_size: u32TableConfigConfigures the game table with the specified deck and hand size, initializing the ZK verifier.
verify_dealplayer: Address, deck_root: BytesN<32>, hand_commitment: BytesN<32>, proof: Groth16ProofboolVerifies 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

  1. Setup: The contract calls init_table(deck_size, hand_size) to prepare the ZK circuit parameters.
  2. Dealing: The dealer generates a hand commitment and a proof off-chain.
  3. 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.

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

Other Canonical
soroban-gamesession-authgame-app

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:

  1. Approve - owner signs once to create a scoped session key
  2. Play - many tap calls without wallet prompts
  3. Renew - extend session before expiry (owner re-approves)
  4. 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_in and expires_at use ledger timestamps (seconds). SessionBuilder::expires_in(n) sets expiry to ledger.timestamp() + n.

Public contract API

FunctionParametersReturnsDescription
approve_sessionowner, max_taps, expires_inActiveSessionOne-time owner approval creating a scoped session
tapowner, key_idu32Gameplay action via active session (no wallet prompt)
renew_sessionowner, key_id, expires_inActiveSessionExtend session lifetime (owner must re-approve)
fallback_tapowner, key_idu32Tap via session first, fall back to direct owner auth
scoreowneru32Current tap count for the owner

Cougr APIs used

  • SessionBuilder - declare allowed actions, max operations, and expiry
  • SessionManager::approve - create scoped session after owner auth
  • SessionManager::execute_action - gasless gameplay via session key
  • SessionManager::status - poll remaining ops and renewal hints
  • SessionManager::renew - extend absolute expires_at timestamp
  • SessionManager::fallback_execute - session-first with direct-auth fallback
  • SessionStorage - load session keys by owner and key ID
  • MockSession (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

FunctionParametersReturnsDescription
approve_sessionowner: Address, max_taps: u32, expires_in: u64ActiveSessionApproves a new session key with specific action scopes and expiration constraints.
tapowner: Address, key_id: BytesN<32>u32Increments the player's score, verified using the active session key (no wallet prompt).
renew_sessionowner: Address, key_id: BytesN<32>, expires_in: u64ActiveSessionExtends the active session key expiration window (requires owner wallet authorization).
fallback_tapowner: Address, key_id: BytesN<32>u32Performs a tap action, falling back to direct owner wallet authorization if the session key is expired.
scoreowner: Addressu32Retrieves 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

  1. Authorization: Owner calls approve_session from their wallet to authorize a temporary key for the tap action.
  2. Gameplay: Client signs and executes calls to tap using the session key, bypassing any ledger signature popups.
  3. Renewal / Fallback: If the session expires, the client calls renew_session (wallet-prompted) or calls fallback_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's Score component.

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

TestDescription
approve_and_tap_without_reauthMultiple taps after single approval
renew_session_extends_play_windowRenew increases absolute expires_at
fallback_tap_uses_direct_auth_after_session_expiresFallback after timestamp expiry
mock_session_helper_matches_manager_flowMockSession matches manager flow

When to use which example

Use session_arena when…Use tap_battle when…
Learning SessionManager basicsBuilding a real game with passkeys
Prototyping session UX in a new gameNeed combo mechanics and rounds
Writing integration tests for sessionsDemonstrating 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

Arcade Canonical
game-appecs

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

FunctionParametersReturn typeDescription
init_gamenone()Initializes a new game on the default 10Γ—10 grid.
init_game_with_sizegrid_size: i32()Initializes a new game on a custom square grid.
change_directiondirection: u32boolChanges the snake direction (0 up, 1 down, 2 left, 3 right); returns false for invalid values, reversals, or game-over state.
update_ticknone()Advances the game by one GameApp tick.
get_scorenoneu32Returns the current score.
check_game_overnoneboolReturns whether the game has reached a terminal state.
get_head_posnone(i32, i32)Returns the current snake-head position.
get_snake_lengthnoneu32Returns the number of snake entities.
get_food_posnone(i32, i32)Returns the current food position.
get_snake_positionsnoneVec<(i32, i32)>Returns all snake segment positions.
get_grid_sizenonei32Returns 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.rs contains the Soroban contract entrypoints, storage access, and GameApp wiring.
  • components.rs contains serializable ECS components such as Position, DirectionComponent, SnakeHead, SnakeSegment, and Food.
  • systems.rs contains reusable game systems for movement, direction validation, collision checks, growth, and food spawning.

Storage model

Storage classDataWhy
Instance storagenoneThe example does not need contract-wide configuration shared across games.
Persistent storagestate: GameState, world: SimpleWorldGame progress must survive across transactions. GameState stores compact scalar data; SimpleWorld stores entities and component bytes.
Temporary storagenoneNo 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

  1. A player calls init_game or init_game_with_size.
  2. Startup systems spawn the snake head at the grid center and create one food entity.
  3. The player calls change_direction to submit a valid non-reversing input.
  4. The player or a relayer calls update_tick.
  5. GameApp runs movement first, then collision and food checks.
  6. A wall/self collision sets game_over; eating food grows the snake, increments score, and spawns new food.
  7. Query functions expose score, positions, grid size, and terminal state.

Cougr APIs used

APIWhy it is used
GameAppProvides the maintained arcade-loop pattern and owns scheduled system execution per tick.
ScheduleStage / SystemConfigEnsures movement runs before post-update collision and food systems.
SimpleWorldStores snake, food, and component data in a Soroban-serializable ECS container.
SimpleQueryBuilderScans entities by component type for food, head, and segment queries.
ComponentTraitGives 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

FunctionParametersReturnsDescription
init_game--Start with 10Γ—10 grid
init_game_with_sizegrid_size: i32-Start with custom grid

Control Functions

FunctionParametersReturnsDescription
change_directiondirection: u32boolChange movement direction
update_tick--Advance game one step

Direction Values:

ValueDirectionDelta (x, y)
0Up(0, -1)
1Down(0, +1)
2Left(-1, 0)
3Right(+1, 0)

Query Functions

FunctionReturnsDescription
get_scoreu32Current score
check_game_overboolGame ended status
get_head_pos(i32, i32)Head coordinates
get_snake_lengthu32Total length
get_food_pos(i32, i32)Food coordinates
get_snake_positionsVec<(i32, i32)>All positions
get_grid_sizei32Grid 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

NetworkContract IDExplorer

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
FilePurpose
lib.rsSoroban contract with public functions and tests
components.rsComponent definitions implementing ComponentTrait
systems.rsGame mechanics (movement, collision, spawning)
simple_world.rsEntity 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

IssueSolution
Rust version errorsrustup update && rustup default stable
WASM target missingrustup target add wasm32v1-none
Stellar CLI not foundbrew install stellar-cli (macOS)
Dependency conflictscargo update && cargo clean && cargo build
Test snapshots issuesDelete test_snapshots/ directory

Full Verification

cargo fmt --check && cargo clippy -- -D warnings && cargo test && stellar contract build

References

ResourceLink
Soroban Docsdevelopers.stellar.org
Stellar CLICLI Documentation
Cougr Repositorygithub.com/salazarsebas/Cougr
Rust TestingRust 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

Other Canonical
soroban-gameecs

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

FunctionParametersReturnsDescription
spawn-u32Spawns a new entity at origin (0,0) and returns its generated entity ID.
move_entityentity_id: u32, direction: u32-Moves the entity in the specified direction if moves remain.
positionentity_id: u32Option<Position>Retrieves the current Position of the given entity.
movesentity_id: u32Option<Moves>Retrieves the current Moves component of the given entity.
entity_count-u32Retrieves 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

  1. Initialization / Spawn: The user calls spawn. An entity is spawned at (0,0) with 10 moves remaining. A ("COUGR", "set", "position") event is emitted.
  2. Action / Movement: The user calls move_entity with direction (0=North, 1=East, 2=South, 3=West). The remaining moves decrement, the position updates, and a position set event is emitted.
  3. Query: The user reads the entity's position or moves remaining.

Cougr APIs used

  • SorobanGame and impl_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.

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

Board Canonical
soroban-gamerich-componentsgame-app
tic_tac_toe preview

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

FunctionParametersReturnsDescription
init_gameplayer_x: Address, player_o: AddressGameStateSpawns the single game entity, seeds an empty 9-cell board, and sets X to move first. Overwrites any previous game.
make_moveplayer: Address, position: u32MoveResultValidates and applies a move at position (0–8). Returns success: false with a status message symbol instead of panicking on illegal input.
get_state-GameStateCurrent board, both player addresses, whose turn it is, move count, and status.
is_valid_moveposition: u32boolWhether 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-GameStateRe-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

  1. Deployer calls init_game(player_x, player_o); a new entity is spawned, the board is seeded with 9 empty cells, and TurnState is set to X's turn, move count 0, status in-progress.
  2. 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.
  3. On a legal move, the cell is marked, systems::detect_winner re-checks all 8 winning lines plus the draw condition, and turn state is updated (status, move count, whose turn is next).
  4. Players alternate make_move calls until detect_winner reports a win (status 1 or 2) or a draw (status 3), after which further moves return gameover.
  5. Either player can call get_state or get_winner to read the outcome, or reset_game to start over with the same two players.

Cougr APIs used

  • cougr_core::{impl_rich_component!} - used for Board (holds a Vec<u32>) and Players (holds two Address values). Both fields require Soroban's XDR codec rather than fixed-size byte packing, so impl_rich_component! was chosen to get RichComponentTrait for free from the #[contracttype] derive, avoiding a hand-written serialize/deserialize pair for Vec<u32> and Address like the one reversi's components.rs still carries.
  • cougr_core::{impl_component!} - used for TurnState, which is three fixed-size plain fields (bool, u32, u32). impl_component! generates a compact, fully typed ComponentTrait implementation (byte-packed, not XDR) since there are no Address/Vec fields needing the heavier rich-component codec.
  • cougr_core::game::SorobanGame / impl_soroban_game! - generates load_world/ save_world for the #[contract] struct so every entrypoint can read and persist the SimpleWorld without 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 GameApp or ScheduleStage - validation and win detection run synchronously inside make_move since 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.md and 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.

GuideStatus
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.

DocumentDescription
ContributingHow to open issues, write code, and get PRs merged
Code of ConductExpected behaviour in all project spaces
GovernanceHow decisions are made, who can merge, how disputes are resolved
SecurityHow to report vulnerabilities
RoadmapWhere the project is headed
RFC ProcessHow to propose significant changes before implementing them
ChangelogWhat 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:

AreaExpected outcome
Core frameworkImproved ECS, scheduling, storage, authorization, or zero-knowledge capabilities
ExamplesNew game patterns, better reference implementations, or tighter example documentation
DocumentationClearer architecture, setup, or usage guidance aligned with the current codebase
QualityBetter 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:

  1. what changed
  2. why the change was needed
  3. how it was validated
  4. 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, or zk::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/Cougr issues


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.md Public 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:

AreaStatusGuidance
ECS runtime and storageStablePart of the 1.0 contract when used through the documented onboarding and runtime surfaces
Accounts and smart-account flowsBetaDo not assume full production guarantees without project-specific review
Standards layer (standards)StableReusable contract primitives are part of the 1.0 stable contract
Privacy primitives (zk::stable)StableCommit-reveal, hidden-state codecs, and Merkle utilities are the stable privacy contract
Advanced ZK verificationExperimentalTreat 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:

  1. Do not open a public issue with exploit details.
  2. Report the issue privately to the project maintainers.
  3. 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.md exists in the main repository yet. Per docs/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: CorsConfig validates origins, methods, header names, and credential/wildcard combinations; OriginAllowlist supports 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 headers
  • cougr-cli - new workspace member publishing the cougr binary
  • cougr new <name> [--template <name>] - scaffolds a Soroban game contract crate following the canonical lib.rs / components.rs / systems.rs layout, with a passing test::GameHarness suite and a dependency on the published cougr-core release rather than a path dependency
  • Four embedded templates, each derived from a canonical example and compiled into the binary so cougr new works 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 runs cargo fmt, clippy, cargo test, and a wasm32v1-none release build against it

1.1.0

Added

  • game::SorobanGame trait - standard load_world / save_world contract pattern; implement once with impl_soroban_game!(Contract, "key"), use in every entrypoint
  • impl_soroban_game! macro - wires SorobanGame to any #[contract] struct
  • SimpleWorld::load_from_instance - load world from Soroban instance storage, returning a fresh empty world on first call
  • SimpleWorld::save_to_instance - persist world to Soroban instance storage
  • SimpleWorld::set_rich_observed - store a rich component and emit a RichComponentChangedEvent for off-chain indexers
  • SimpleWorld::remove_rich_observed - remove a rich component and emit a del event
  • RichComponentChangedEvent - new Soroban event type with topics ("COUGR", "rich", component_type) for rich component change notifications
  • spawn_and_move example - canonical Cougr starter game demonstrating the complete idiomatic pattern: impl_component_observed! + SorobanGame + typed ECS access
  • SorobanGame re-exported from prelude - import from cougr_core::prelude::*
  • cougr_core::circuits - four pre-built ZK game builders (hidden cards, fog of war, fair dice, sealed bid) with pipeline-embedded verification keys
  • cougr_core::session - SessionManager, SessionStatus, and ActiveSession (Beta)
  • cougr_core::test - GameHarness, Scenario, and ReplayLog sandbox behind the testutils feature
  • Circom pipeline - internal/cougr-core-circuits with CI workflow and on-chain Groth16 proof verification using real VKs
  • ZK examples - hidden_hand, fog_explorer, dice_duel, and blind_auction
  • Workspace subcrates - internal/cougr-core-{circuits,session,test} per ADR 0007

Changed

  • tic_tac_toe example modernised: replaced ~200 lines of manual serialization with impl_rich_component! for Board and Players, and impl_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_arena example added as canonical reference for session::SessionManager

Stability Notes

  • game::SorobanGame is Stable
  • SimpleWorld::load_from_instance / save_to_instance are Stable
  • set_rich_observed / remove_rich_observed are Stable
  • RichComponentChangedEvent is Stable
  • cougr_core::session is Beta
  • cougr_core::circuits and embedded test VKs are Experimental
  • cougr_core::test is Experimental (testutils only)

1.0.0

Added

  • app as the default gameplay runtime surface
  • auth, privacy, and ops as product-level domain namespaces
  • RuntimeWorld and RuntimeWorldMut as shared Soroban-first backend contracts
  • stronger stage scheduling with ordering, sets, and validation
  • SimpleQueryBuilder, query state/cache improvements, and richer ArchetypeWorld query 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
  • battleship now uses stable privacy primitives from zk::stable
  • documentation now treats SimpleWorld and ArchetypeWorld as 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 app over 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, and SimpleQuery
  • Prefer ops, privacy, and auth in 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