Build something like this?

Volleyland: a multiplayer learning game

Volleyland: a multiplayer learning game where questions are the projectiles. Build worlds, volley questions, learn by playing. Founding landscape (target state); systems graduate as they become real.

1 layer5 insightsby Supportv2Published Oct 31, 2026

About this system

What is being built, and for whom

Volleyland is a standalone multiplayer arena game for school students. Players attack with question projectiles, and a hit presents the target with a timed multiple-choice question. A correct answer releases the target with a short advantage. A wrong answer or timeout costs health. Learning content is data-driven and curated in a question-bank CMS, so the game serves diverse schools, curricula, subjects, levels and languages. It is private-by-default, built for minors, and treats accessibility as a core requirement.

Target state. This is a founding landscape: nothing is built yet, and systems graduate as they become real.

Systems and what each owns

  • Volleyland (one app: the volleyland Turborepo monorepo): ships as two containers.
    • apps/web (Next.js 16 + React Three Fiber): the browser surface. It holds the R3F game client, the student and teacher portals, and the CMS routes. It holds no domain rules.
    • apps/server (NestJS): the sole authoritative backend. It owns match simulation, the Socket.IO game gateway, hits, health, timers, question selection and answer evaluation, scoring, the versioned /v1 API, and the CMS workflow, classes, progression, analytics, moderation and audit modules.
    • Shared packages: game-core (deterministic collision shared by client prediction and server validation), contracts, database, curriculum and ui.
  • Reverse proxy (Caddy recommended): terminates TLS on one origin and routes / to the web container and /api + /ws to the server container.
  • Supabase Auth: accounts and sessions, with JWTs verified server-side.
  • Supabase PostgreSQL: all primary data, accessed only through the server via Drizzle.
  • Question media storage: object storage for approved question media. The provider is not yet decided.

Actors

  • Students play matches, answer questions and review mistakes privately.
  • Teachers run classes, assignments and private rooms, and read class reports.
  • Content staff (authors and reviewers) author, review, publish and retire questions in the CMS.
  • Administrators (organization admins, platform admins and moderators) manage organizations, policies, global taxonomies, moderation and audit.

Zones

  • VPS (Docker Compose): one self-managed host running the proxy and Volleyland's two containers. It holds no primary data and is an accepted single point of failure for the MVP.
  • Supabase (managed): Auth and PostgreSQL stay managed for the MVP.

Deliberately not on this landscape yet

  • Redis: optional, deferred until load testing calls for multi-instance hosting.
  • Observability tooling: required, but no vendor has been chosen.
  • World building: a post-MVP mode inside Volleyland, not a separate system.

Graduation

When the monorepo starts, /new-app switches volleyland to the mono-repo app archetype, creates its board, and binds it as new_app. The web/server split then arrives on that board from the first push.

The map

The map · Spine

Architecture

What's on this map
  • Administrators · actor
  • Content Staff · actor
  • Question Media Storage · data store
  • Reverse Proxy · gateway
  • Students · actor
  • Supabase (managed) · external integrations
  • Supabase Auth · external integration
  • Supabase PostgreSQL · postgresql
  • Teachers · actor
  • Volleyland · mono repo
  • VPS (Docker Compose) · infrastructure group

The map · Layers

Layers1

What's on each layer

Volleyland

Volleyland: a multiplayer learning arena game where a hit freezes the target until they answer a multiple-choice question, and the server decides every outcome. Container-grade L0 system context with 3 drill-downs (web, server, game-core).

  • Admin and Content Author · actor
  • Admin Authoring Skills · agent skill
  • Audio Generator · cli tool
  • Build and CI · cli tool
  • Caddy Reverse Proxy · gateway
  • Compose Stack · environment
  • Contracts · library package
  • Developer Tooling · developer tooling
  • ElevenLabs API · external integration
  • Game Core · library package
  • MCP Client (AI Assistant) · ai agent
  • Player · actor
  • Server · nestjs
  • Settings and Question Bank Files · data store
  • Synced Skill Library · agent skill
  • Single-VPS Deployment · infrastructure group
  • Web · nextjs

Game Core

Deterministic shared library: math, collision, movement, terrain, worlds, navigation and world tooling used by both client prediction and server simulation. Grain: terminal (plan fits the band, no drill-downs), component grain.

  • Appearance · module
  • Arena · engine component
  • Built-in Worlds · module
  • Camera and Aim · module
  • Collision · engine component
  • Layout Generator · engine component
  • Math and RNG · module
  • Movement · engine component
  • Navigation · engine component
  • Simulation Physics · domain group
  • Shared Presentation · domain group
  • Terrain · engine component
  • Weapons · module
  • World Model · schema model
  • World Checks · engine component
  • World Editing · engine component
  • World Authoring · domain group

Server

NestJS game server: authoritative match simulation, Socket.IO gateway, settings, question bank CMS, operations and MCP. Grain: terminal component-grade (no child units; the plan was over-band by file count but the server is one deployable with no drill-downs to carry).

  • Admin API · controller component
  • App Bootstrap · module
  • Bots · engine component
  • Command Guard · service component
  • Content API · controller component
  • Content Service · service component
  • Content Store · repository component
  • Content Workflow · domain group
  • Game Gateway · domain group
  • Game Gateway Socket · adapter component
  • Health · controller component
  • Match · engine component
  • Match Simulation · domain group
  • MCP Access Guard · service component
  • MCP Controller · controller component
  • MCP Server · domain group
  • MCP Tool Catalog · service component
  • Operation Catalog · service component
  • Operation Error Filter · adapter component
  • Operation Runner · service component
  • Operations · domain group
  • Platform · domain group
  • Contracts · boundary port
  • Game Core · boundary port
  • Question Selection · domain group
  • Question Selection Service · service component
  • Question Selector · engine component
  • Room Registry · service component
  • Rooms Operations · handler component
  • Server Config · module
  • Settings · domain group
  • Settings Change · engine component
  • Settings Operations · handler component
  • Settings Service · service component
  • Settings Store · repository component
  • World Edits · engine component

Web

Next.js web app: R3F game client, admin panel (settings, worlds, question bank), blog, and rendering/audio support. Grain: terminal; the plan fits the band with no drill-downs beyond the game-client child unit.

  • Admin API Client · adapter component
  • Admin Settings and Content · domain group
  • App Shell and Config · module
  • Blog · page component
  • Character Renderer · engine component
  • Game Audio · engine component
  • Game Client · frontend app
  • Contracts · boundary port
  • Game Core · boundary port
  • Question Bank Editor · ui component
  • Settings Admin · page component
  • World Builder · ui component
  • World Renderer · engine component

Game Client

R3F game client mounted at /play: socket connection, session, prediction and snapshot interpolation, Koota world and runtime, input, HUD and scene. Grain: terminal (plan fits band 5-20, no drill-downs), component grain.

  • Session and State · domain group
  • Frame Loop and Input · domain group
  • Game Connection · adapter component
  • Game Loop and Camera · engine component
  • Game Session · facade component
  • HUD Overlays · ui component
  • HUD Store · store component
  • Input and Controls · hook component
  • Local Predictor · engine component
  • Network and Sync · domain group
  • Play Route · page component
  • Admin API Client · boundary port
  • Character Renderer · boundary port
  • Game Audio · boundary port
  • Settings Admin · boundary port
  • World Renderer · boundary port
  • Presentation and Input · domain group
  • Runtime and ECS · store component
  • Scene Rendering · ui component
  • Snapshot Buffer · engine component
  • Socket Contracts · schema model

What to notice

Inbound · Signals

Insights4 analyses

5Insights
1Critical
risk1opportunity1strength2observation1
Read all 5

One misnamed MCP operation can stop the whole game server at startup

risk · critical · verified

Only MCP lists tools from Operation Catalog; the admin REST routes call their operations directly through Operation Runner. Yet the catalog checks every operation when the server starts (snake_case name, unique name, destructive implies write) and throws on a failure, so one bad name aborts startup for matches and the admin panel too. 20 of the board's 33 elements depend on it, only 2 directly.

Impact: A typo in any operation name, in any module, keeps the game server from booting, although the REST path never needed the catalog.

Recommendation (small effort): Keep the naming checks, but run them in a unit test over every Operations provider so a bad name fails in CI. At startup, log and skip an invalid operation rather than throwing, so MCP loses one tool instead of the server failing to boot.

  1. Operation Runner
  2. Operation Catalog
  3. MCP Tool Catalog
  4. MCP Controller
  5. Operation Runner

Agents get strict, self-describing tools that are checked twice

strength · medium · verified

Each MCP tool is an operation with a zod input schema that carries field titles and descriptions from contracts. The MCP SDK validates arguments, then OperationRunner validates again with refinements. Refusals return as tool results a model can act on. World writes and import_questions take dryRun, every write names expectedVersion, and destructive operations carry destructiveHint. These are the schema and correction loops an agent needs.

Schema discipline is the strongest of the seven concerns here; identity, provenance and the human escape hatch are the gaps.

  1. MCP Controller
  2. MCP Tool Catalog
  3. Operation Runner

An agent's change cannot be told apart from an admin's or reconstructed later

opportunity · medium · verified

The audit writes one op.call line per call: operation, transport, outcome, principal and user agent, but never inputs. Stored settings, worlds and questions carry version and updatedAt but no author. Once an agent has saved, nothing records what it changed or that it was an agent. Yet settingsChange already computes a list of what changed (section, key, from and to) for every settings save.

Impact: Recording who changed what turns agent edits into reviewable history, the precondition for letting agents write outside development.

Recommendation (medium effort): Store the actor (transport and principal) on each saved record, and log the settingsChange list with each settings write; add an equivalent field diff for worlds and questions.

  1. Operation Runner
  2. Settings Service
  3. Settings Change

Production deploys start closed: token required, MCP off, answer keys withheld from readers

strength · low · verified

Compose refuses to start the server without ADMIN_TOKEN, sets MCP_ACCESS to off, and the image sets NODE_ENV=production. Caddy forwards only /api, /ws and the web app, so the health probe stays inside the Compose network. If MCP is later opened read-only, list_questions and get_question strip the answer key for read-only callers.

These defaults only hold for the Compose path. Running the built server any other way skips them, which is why the development defaults above matter.

  1. Compose Stack
  2. Server
  3. MCP Access Guard

The admin API is open to everyone unless NODE_ENV is exactly production

observation · low · verified

Without ADMIN_TOKEN, the admin guard lets every caller in whenever openWithoutToken is set, and loadAdminAuth sets that flag to NODE_ENV !== 'production' (server-config.ts:29, admin.guard.ts:31). Only the server Dockerfile sets NODE_ENV=production (line 20), so the Compose deployment is safe. Any other way of running the built server without that variable serves an open admin API, whose settings, worlds and questions come through Contracts and Game Core.

Impact: A server started outside the image with no NODE_ENV and no ADMIN_TOKEN lets anonymous callers rewrite game rules, worlds and the question bank, answer keys included.

Recommendation (small effort): Invert the default: open only when NODE_ENV is explicitly 'development' or 'test', and refuse to start without ADMIN_TOKEN otherwise. Add a test for the unset-NODE_ENV case.

  1. Admin API
  2. Contracts
  3. Contracts
  4. Game Core
  5. Game Core

In the code

Endpoints 17 · Pages 4 · Events 14 · AuthZ 4

Inbound · From code

Endpoints

17endpoints

Public
6
Deprecated
0
Rate-limited
0
By method
  • GET7
  • POST4
  • PUT3
  • DELETE3

Endpoints

+13 more

Inbound · From code

Pages

4pages

2 writes without a guard

Guarded
0
With forms
0
Unguarded writes
2

Needs a look

Captures
/admin
/play
/
/blog

Inbound · From code

Events

14channels

14 without a dead-letter queue

Event types
14
Subscriptions
14
No dead-letter
14

Needs a look

+10 more

Event types per channel
  • bot.windup1
  • health.changed1
  • hit.confirmed1
  • match.setup1
  • player.input1

+9 more

Inbound · From code

AuthZ

4roles

Permissions
43
Policies
10
Resource types
8
Permissions per role
  • Admin REST caller (ADMIN_TOKEN holder, or anonymous in dev)13
  • MCP caller with MCP_ACCESS=write13
  • MCP caller with MCP_ACCESS=read10
  • Game socket player7

Roles