Halcyon-7 is a browser-based 2D social game — PixiJS in React, Colyseus for realtime, Postgres + Redis behind a stateless web tier. Small on purpose, but architected so nothing here precludes growth.
Every contested call gets decided by these before anyone opens an editor.
Every component is something we can debug at 2am. No bleeding edge without a load-bearing reason. Postgres, Redis, Node — things we already know cold.
Items, rooms, badges, outfits — all defined in versioned data files, referenced by stable IDs like item:hat_pirate_001. Code reads data; data never depends on code shape.
Client renders, predicts, animates. Server owns position, inventory, currency, chat, friend state. Anything the client claims is suspect until the server confirms it.
One realtime monolith, one web monolith, one database. Modular code, not modular deploys.
MVP target is 20–50 concurrent. The rules below are cheap now and expensive to retrofit, so we follow them from day one — without paying the complexity tax of building for thousands today.
MapRevisit trigger: when sustained concurrency crosses 200, or any room routinely saturates its soft cap. Not before.
Game code never references a filename or sprite index — only stable IDs. Swapping a placeholder for commissioned art is a content commit, not a code change and not a migration.
room:cantina, never a path// a hat is a JSON descriptor + a PNG. day one it's a colored rectangle. { "id": "hat_pirate_001", "kind": "hat", "slot": "head", "displayName": "Pirate Tricorn", "price": 250, "sprites": { "icon": "./icon.png", "directional": { "north": "./sprites/north.png", "south": "./sprites/south.png" } } } // day 90: drop in commissioned PNGs, rebuild. no code, no migration.
The network carries intent and a path — never 60Hz position updates. Server state is always "where the player ended up," not "where they are mid-step," which kills interpolation drift on the authoritative side.
Client requests a target tile. Server runs A* on the room's collision mask (loaded once at room create), validates bounds and reachability, and sets state to the final destination immediately.
Broadcast to everyone in the room. All clients animate that player walking the same waypoints. Animation is purely client-side.
Sent if the player is muted, kicked, or redirects mid-walk. Reconnect is trivial — a joining client just gets current state, with no in-flight paths to reconcile.
Server rate-limits, runs the profanity filter, checks mute, persists for moderation audit, then broadcasts a short-lived chat bubble into room state — and pushes to a Redis channel for the mod tools.
The database never knows a user is "in the cantina at tile (12,8)." That belongs to the room process. When a player leaves, inventory and currency flush; position simply evaporates — which is exactly what lets rooms move between processes later with zero migration.
hub — one instance per public room (arrivals, cantina, observation). Persistent even when empty, soft cap 25.
cargo_sort — single-player, ephemeral. Created per session, disposed on leave. Keeps the connection lifecycle uniform.
profiles — username, role, status; one per usercharacters — equipped slots as jsonb, credits; one per profileinventory — owned items, unique per (character, item)friendships — symmetrical, stored as one sorted rowevents_log — append-only audit of every mutating actionCurrency is just an int on the character — no speculative ledger. If we ever need history, it derives from events_log, which is also the answer to every "I lost a hat" support ticket.
The hardest part — the plumbing — comes first, while motivation is high. Later phases are mostly content, which is the fun kind of work.
Monorepo, auth + session bridge, profiles/characters, first Drizzle migration, one trivial Colyseus room, health endpoints and structured logs.
Pixi canvas in Next.js, asset pipeline v1, click-to-walk with A*, other players visible with names, room transitions, three rooms in placeholder art.
Room chat with bubbles, profanity filter + rate limit + mute + report, profile cards, friends with online status and "find," DMs.
Items catalog, inventory, shop terminal, avatar customization, passive + minigame currency, badges, and Cargo Sort polished single-player.
Admin panel: report queue, user lookup, mute/kick/ban propagated live over Redis, audit log viewer, seasonal event system, backup verification.
50-person stress test, fix what breaks, invite-code signup, gradually open. v2 (bunks, multiplayer minigames, leaderboards) only if the MVP earns it.
Nothing gets built until there's a PLAN.md. Small reviewable commits, explicit sign-off between phases. If you like plan-before-code discipline, you'll feel at home.