Online multiplayer 3D naval combat game. Two players command submarine fleets hidden in a 7x7x7 volumetric grid (343 cells), firing torpedoes and deploying earned abilities to locate and destroy enemy vessels. Features server-authoritative gameplay, persistent player profiles, invite-based matchmaking, and AI opponent modes (BYOK).
contact-v2/
├── packages/
│ ├── shared/ @contact/shared — types, config, pure engine functions, AI tools
│ ├── client/ @contact/client — Vite SPA: Three.js renderer, Tone.js audio, UI
│ └── server/ @contact/server — Fargate container: REST API, WebSocket, session management, persistence
│ ├── auth/ JWT token verifier for WebSocket upgrade
│ ├── db/ DynamoDB repositories (players, sessions, invites)
│ ├── middleware/ JWT auth, session access validation, rate limiter (sessions, profiles, invites)
│ ├── notifications/ SES email service for invites
│ ├── routes/ REST endpoints (profile, sessions, session-actions, invites)
│ ├── session/ GameSession wrapper, SessionStore, state filter, DynamoDBLogger, LogArchiver, LogUrlGenerator
│ └── ws/ WebSocket handler, message router, turn timer, reconnection
├── infra/ AWS CDK — Cognito, DynamoDB, ECS Fargate, ALB, S3, CloudFront
├── scripts/ CLI tools: simulate, analyze-log
├── tests/ Mirrors packages/ structure
├── artifacts/ Design docs and V2 specs
└── docs/ JSONL log format reference
@contact/shared — Pure TypeScript with no DOM, Three.js, or Tone.js imports. Contains the game engine (grid, fleet, combat, credits, perks, abilities), type definitions, GameConfig factory, LoggerAdapter interface, AI briefing/tools, and observability.
@contact/client — Single-page application built with Vite. Handles 3D rendering (Three.js), synthesized audio (Tone.js), all UI screens and components, WebSocket client (GameClient with auto-reconnection), and client-side AI opponent integration (BYOK). Screens run in dual mode: local (GameController) or online (GameClient over WebSocket).
@contact/server — Express REST API + WebSocket server on Fargate. GameSession wraps GameController with player identity mapping, turn ownership validation, parallel setup, and anti-cheat state filtering. WebSocket layer provides real-time message routing, server-enforced 90-second turn timer with consecutive-timeout forfeit, and 90-second reconnection window with timer pause. DynamoDB persistence for session meta, game snapshots, and event logs.
infra/ — AWS CDK V2 app. Defines Cognito User Pool (Google OAuth), DynamoDB single-table (GSI for join codes, TTL for auto-expiry), SES email identity for invite notifications, ECS Fargate service behind an ALB with sticky sessions (24h lb_cookie) and 120s idle timeout for WebSocket support, PostConfirmation Lambda for auto-provisioned player profiles, S3 bucket for game logs (lifecycle: IA→Glacier→delete), S3 bucket for client assets with OAC, CloudFront distribution with security response headers (CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy), and Route53/ACM for custom domain.
| Layer | Choice |
|---|---|
| Language | TypeScript 5.x |
| Build | Vite 6.x |
| 3D Rendering | Three.js r128 |
| Audio | Tone.js 14.x (synthesized, no sample files) |
| UI | Vanilla TypeScript + DOM (no frameworks) |
| Testing | Vitest |
| Fonts | Press Start 2P + Silkscreen (Google Fonts CDN) |
| Layer | Choice |
|---|---|
| Server Runtime | Node.js/TS on AWS Fargate |
| HTTP | Express |
| WebSocket | ws |
| Auth | AWS Cognito (Google OAuth) |
| Database | DynamoDB (single-table design) |
| CDN | CloudFront (client assets, /api/* + /ws/* proxy to ALB) |
| Infra-as-Code | AWS CDK V2 |
npm install
npm run devOpens the client at localhost:5173 with hot module replacement.
To run the server locally:
npm run dev:serverStarts the Express API on localhost:3001. Requires Cognito environment variables for auth (see packages/server/CLAUDE.md).
| Command | Description |
|---|---|
npm run dev |
Dev server with HMR on localhost:5173 |
npm run build |
Typecheck + production build to dist/ |
npm run build:single |
Single portable HTML file (dist/contact.html) |
npm run typecheck |
Run tsc --noEmit across all packages |
npm run test |
Run tests in watch mode |
npm run test:run |
Run tests once |
npm run dev:server |
Express API server with hot reload on localhost:3001 |
npm run cdk:synth |
Synthesize CloudFormation template |
npm run cdk:deploy |
Full deploy: CDK + post-deploy env + client build + S3 sync |
npm run cdk:diff |
Preview infrastructure changes |
npm run cdk:destroy |
Tear down infrastructure |
npm run simulate |
Run bot-vs-bot game simulations |
Human vs AI (BYOK) — Play as ALPHA against Claude as BRAVO. Select VS AI on the title screen, choose a model (Haiku/Sonnet/Opus), and enter your Anthropic API key. Key is validated client-side before starting. Your key stays client-side and never touches CONTACT servers. AI actions are routed through the server via WebSocket for state consistency.
Online Multiplayer — Real-time two-player matches via WebSocket. Create a session and invite an opponent via email (30-minute expiry, deep link join), or share the join code directly. Server-enforced 90-second turn timer, automatic timeout forfeit (2 consecutive), and 90-second reconnection window. Select ONLINE on the title screen.
- Setup: Each player places 7 submarines + 1 decoy in the 7x7x7 grid (AI places automatically in VS AI mode)
- Combat: Alternate turns — fire a torpedo, use a perk, or do both (one per slot)
- Victory: Sink all 7 enemy subs to win
Each turn you have three action slots: one ping, one attack, and one defend. Firing a torpedo always uses the attack slot. Perks consume the slot matching their type. Only the attack slot is required to end your turn.
Credits fund the perk store. You earn them by landing shots:
| Action | Credits |
|---|---|
| Hit | +1 |
| Consecutive hit (chain) | +3 bonus |
| Sink | +15 |
Starting credits: 5. Credits accumulate across turns within a game.
Select a rank on the title screen to control the stalemate bonus — a dry-spell mechanic that awards bonus credits when neither player makes contact for too long.
| Rank | Dry Turn Threshold | Bonus Credits | Description |
|---|---|---|---|
| Recruit | 8 | +8 | Generous bonuses keep the perk economy flowing |
| Enlisted | 10 | +5 | Moderate safety net for intermediate players |
| Officer | -- | -- | No bonuses (default, original experience) |
When the threshold is reached, both players receive the bonus. The counter resets on any contact (torpedo hit, sonar positive, drone contact, depth charge hit, or decoy hit).
| Vessel | Size |
|---|---|
| Typhoon | 5 |
| Akula | 4 |
| Seawolf | 3 |
| Virginia | 3 |
| Narwhal | 3 |
| Midget Sub | 2 |
| Piranha | 2 |
Ships may be placed along 8 axes (any direction except purely vertical):
- Within a depth slice:
col,row,diag+,diag- - Crossing depth layers:
col-depth,col-depth-,row-depth,row-depth-
Press R during placement to cycle axes. Press F to flip direction.
| Perk | Slot | Cost | Description |
|---|---|---|---|
| Sonar Ping | Ping | 2 | Scans a 2x2x2 volume (up to 8 cells) for ship presence |
| Radar Jammer | Defend | 12 | Inverts the next enemy Sonar Ping; suppresses Recon Drone |
| Recon Drone | Attack | 10 | Reveals contents of a 3x3x3 volume (up to 27 cells) |
| Silent Running | Defend | 8 | Masks one ship from recon scans for 2 opponent turns |
| Acoustic Cloak | Defend | 14 | Masks your entire fleet from recon for 2 opponent turns |
| G-SONAR | Attack | 14 | Scans a full depth layer (49 cells) |
| Depth Charge | Attack | 20 | Strikes all occupied cells in a 3x3x3 volume |
| Key | Action |
|---|---|
R |
Cycle placement axis (setup) |
F |
Flip placement direction (setup) |
S |
Toggle own grid / targeting grid (combat) |
Switch between three 3D views during combat:
- Cube: Full volumetric 7x7x7 cube, orbit freely
- Slice: Single depth layer shown as a flat grid
- X-Ray: Semi-transparent cube revealing interior cells
Run automated bot-vs-bot games to test game balance:
npm run simulate # 100 games, officer rank
npm run simulate -- 1000 # custom game count
npm run simulate -- 1 -v # single game, verbose
npm run simulate -- --rank recruit # test stalemate bonus
npm run simulate -- 1 --export # export JSONL logParse any exported JSONL session log into an After Action Report:
npx tsx scripts/analyze-log.ts <log.jsonl> # formatted report
npx tsx scripts/analyze-log.ts <log.jsonl> --json # machine-readable JSONEvery game session produces a structured JSONL event log covering placements, shots, perk uses, and phase transitions. Export from the victory screen for analysis.
See docs/JSONL_FORMAT.md for the full event schema.
- Decomposition Plan v1.6 — master spec
- Auth Requirements — Cognito + OAuth
- BYOK One-Pager — client-side AI keys
- GameConfig Spec — injectable game rules
- Phase 1 Plan — monorepo restructuring
- Phase 2 Plan — auth foundation
- Phase 3 Plan — server game engine
- Phase 4 Plan — WebSocket gameplay
- Phase 5 Plan — AI Enhancements (BYOK)
- Phase 6 Plan — Invite System + Lobby
- Phase 7 Plan — Observability
- Phase 8 Plan — Polish + Release