| .forgejo/workflows | ||
| .hermes/triage | ||
| public | ||
| server | ||
| src | ||
| test | ||
| .env.example | ||
| .gitignore | ||
| .gsd | ||
| .pre-commit-config.yaml | ||
| bun.lock | ||
| capacitor.config.ts | ||
| chainbound-e2e-thorough.cjs | ||
| chainbound-smoke-deep.cjs | ||
| chainbound-smoke-final.cjs | ||
| chainbound-smoke-full.cjs | ||
| chainbound-smoke.cjs | ||
| components.json | ||
| eslint.config.js | ||
| generate-placeholders.py | ||
| index.html | ||
| lefthook.yml | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.js | ||
| README.md | ||
| tailwind.config.js | ||
| todo.txt | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
Chainbound Duel
A dual-chain combat game where two duelists face off using symbolic magic. Build chains from seven base symbols and five composite sigils, cast devastating spells, manage two resource pools (vitality and will), and outmaneuver your opponent through tactical timing, flow control, and reactive conditions.
Tech Stack
- Framework: React 19 + TypeScript
- Build: Vite 7
- Routing: React Router 7
- Animation: GSAP, Framer Motion
- UI: Radix UI primitives + shadcn/ui, Tailwind CSS
- State/Forms: React Hook Form + Zod
- Audio: Web Audio API with spatial sound and adaptive music
Getting Started
git clone https://forgejo.tail018ac4.ts.net/Mark/chainbound.git
cd chainbound
npm install
npm run dev
Game Overview
Core Mechanics
Dual Chains — Each duelist has a left hand and right hand, each building a chain of up to 5 slots. Chains are built simultaneously each turn before both sides reveal.
Symbols — Seven base symbols form the foundation of all magic:
- Spark — Ignition energy for offensive spells
- Veil — Concealment and protection
- Knot — Binding and restriction
- Fang — Direct, relentless pressure
- Mirror — Duplication and reversal
- Gate — Amplification and transition
- Pulse — Sustained, lingering effects
Sigils — Adjacent symbol pairs compress into five composite sigils:
- Seal (veil + knot) — Binding ward
- Mask (mirror + veil) — Total misdirection
- Current (gate + pulse) — Sustained flow
- Snare (knot + fang) — Damaging restraint
- Glint (spark + mirror) — Feinting strike
Resources — Two pools tracked per player:
- Vitality (100) — Primary health; reach zero to lose
- Will (80) — Secondary resource for certain spells and conditions
Turn Phases — plan → compose → commit → reveal → resolve → persist
Flow Tags — Every chain has a flow state that affects spell casting: open, lean, exposed, anchored, dead
Spells
Spells are crafted by matching symbol/sigil sequences. Each spell has a pattern (single-hand sequence or simultaneous two-hand pair) and effects (damage, heal, curse, or modifier application). Spells interact with the reactive condition system based on flow-tag transitions and chain state.
Notable spells include:
- Turned Knife — Low damage with favorable flow tag economy
- Blind Witness — Applies reflective shroud modifier
- Ember Lance — High damage execution spell (12 vitality)
- Paper Phoenix — Simultaneous heal + phoenix-ascent modifier
- Cinder Storm — Highest damage spell (15 vitality), requires 4 slots
- Iron Grasp — Applies chain-lock control modifier
- Void Siphon — Boss-only: drains opponent will to heal self vitality
Conditions & Reactive System
Conditions are persistent effects applied to players or hands:
- Veiled, Anchored, Marked, Delayed, Phased, Witnessed, Compelled, Echoed, Sealed, Inverted
- Barbed, Grinning Seal, Funeral Courtesy, Black Ichor, Phoenix Ash
The reactive system allows spells to trigger effects on specific events: on-cast, on-interrupt, on-chain-advance, on-repeated-symbol, on-lane-collapsed, and more.
Counters & Modifiers
Phase 2 systems include counters (delay, weaken, redirect, tax, full-stop, cancel, reflect, disrupt-setup, phase-disrupt, change-ownership, change-target, re-source) and modifiers (delay, haste, out-of-phase, veil, permanence, echo, inversion, anchor) that alter spell resolution.
Game Modes
| Mode | Description |
|---|---|
| Casual Duel | Single match vs AI opponent, any personality |
| Ranked Trials | Best of 3 series, ladder ranking system |
| The Gauntlet | Campaign with 5 tiers, boss fights, unlockable titles |
| Online Match | Real-time PvP via WebSocket relay server |
| First Binding | Interactive tutorial teaching core mechanics |
| Chronicles | Replay viewer for past matches |
| Stats | Win/loss analytics and performance metrics |
| Player Profile | Titles earned, match history, preferences |
Project Structure
src/
├── pages/ # Route pages
│ ├── TitleScreen.tsx # Main menu
│ ├── CasualDuel.tsx # Casual vs AI
│ ├── RankedMatch.tsx # Ranked best-of-3
│ ├── Campaign.tsx # Gauntlet mode
│ ├── OnlineMatch.tsx # Real-time PvP
│ ├── Tutorial.tsx # Interactive tutorial
│ ├── ReplayViewer.tsx # Match replays
│ ├── Stats.tsx # Analytics dashboard
│ ├── ProfileScreen.tsx # Player profile
│ ├── Spellbook.tsx # Full spell reference
│ └── Settings.tsx # Options panel
├── components/ # UI components
│ ├── ui/ # shadcn/ui primitives
│ ├── ChainPane.tsx # Chain display
│ ├── ChainResolveVFX.tsx # Resolution effects
│ ├── ConditionMarker.tsx # Condition icons
│ ├── SpellHorizon.tsx # Spell display
│ ├── SymbolToken.tsx # Symbol rendering
│ ├── HealthBar.tsx # Resource bars
│ ├── OpeningEditor.tsx # Opening book editor
│ ├── CutscenePlayer.tsx # Campaign cinematics
│ └── ...
├── data/ # Game data
│ ├── spells.ts # Spell definitions (19 spells)
│ ├── symbols.ts # Symbols and sigils
│ ├── campaign.ts # Gauntlet tier definitions
│ ├── tutorial.ts # Tutorial progression
│ ├── dialogue.ts # Story dialogue
│ ├── openings.ts # Named opening strategies
│ ├── stats.ts # Stats tracking
│ ├── replays.ts # Replay storage
│ └── campaignStory.ts # Campaign narrative
├── engine/ # Core game logic
│ ├── state.ts # Game state pure functions
│ ├── resolver.ts # Turn resolution
│ ├── spellGraph.ts # Spell pattern matching
│ ├── conditions.ts # Condition system
│ ├── reactiveConditions.ts # Reactive triggers
│ ├── modifiers.ts # Modifier system
│ ├── counters.ts # Counter system
│ ├── timing.ts # Turn phase timing
│ ├── visibility.ts # Per-player information
│ ├── ai.ts # AI decision making
│ ├── aiPersonalities.ts # AI behavior profiles
│ ├── GameContext.tsx # React game state context
│ ├── sound.tsx # Audio system
│ └── network/ # Online multiplayer
│ ├── relayClient.ts # WebSocket client
│ └── useRelayClient.ts # React hook for relay
├── hooks/ # Custom React hooks
├── lib/ # Utilities and shared logic
└── App.tsx # Root with routing
server/
└── src/
├── main.ts # Relay server entry
├── Room.ts # Match room management
└── types.ts # Server types
Routes
| Path | Page | Description |
|---|---|---|
/ |
TitleScreen | Main menu |
/casual |
CasualDuel | Casual vs AI |
/ranked |
RankedMatch | Ranked best-of-3 |
/online |
OnlineMatch | Real-time PvP |
/campaign |
Campaign | Gauntlet mode |
/tutorial |
Tutorial | Interactive tutorial |
/settings |
Settings | Options panel |
/replays |
ReplayViewer | Browse replays |
/replay/:id |
ReplayViewer | Watch specific replay |
/stats |
Stats | Analytics dashboard |
/profile |
ProfileScreen | Player profile |
/spellbook |
Spellbook | Full spell reference |
Scripts
| Command | Description |
|---|---|
npm run dev |
Start dev server |
npm run build |
Production build |
npm run preview |
Preview production build |
npm run lint |
Run ESLint |
npm run test |
Run test suite |
CI
CI is configured via Forgejo Actions — runs on every push.
Relay Server Deployment
The WebSocket relay server for Online Match is deployed automatically to Node Hub:
# .forgejo/workflows/deploy-relay.yml
on: push: paths: ['server/**']
Requires secrets: NODEHUB_TOKEN, FORGEJO_TOKEN. See server/README.md for details.
License
© Chainbound Studios