New templateType "snake" alongside quiz/clicker/maze — additive, not a
redesign, so it's wired through every existing per-template
enumeration point (schema, types unions, scene factory, builder
dispatch on create/edit/AI-review pages, template icon maps) following
the pattern each already established.
games.templateType is a plain SQLite text column with a TypeScript-
only enum (no SQL DDL behind it) — confirmed via drizzle-kit generate
that adding "snake" produces zero migration, same as every in-place
template redesign this session.
Bounded arena (no maze-style wall authoring — keeps the builder trivial:
width/height/startLength/targetLength/wrapAround, nothing else).
Movement is a self-rescheduling delayedCall loop rather than a fixed
interval, since the step interval itself shrinks as the snake grows;
speed curve sanity-checked standalone before wiring it in. Discrete
per-cell snapping (not tweened, unlike the maze's glide) — that's the
authentic blocky Snake feel, not a shortcut. Standard collision rule:
the tail cell is excluded from the self-collision check unless this
step eats food, since the tail is vacating that cell.
Progress resumes the exact live state (body/direction/food/score) —
Snake has no natural level-boundary checkpoint the way the other three
templates do, so unlike their coarse-grained resume this restores
precisely.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
Full redesign of the Maze template (same templateType: "maze" — plain
text column, no migration needed): dots (*) to eat, power pellets (O)
that frighten ghosts, ghost spawns (G, 0-4), and continuous glide
movement with buffered turns instead of one-cell-per-keypress. Win
condition confirmed with the user explicitly: collect every dot and
pellet, then reach the exit — kept the exit tile rather than switching
to classic Pac-Man's "no exit, board-clear wins," since this app's
maze identity is built around it.
Ghosts chase via BFS pathfinding (reusing the same BFS shape the
maze's own reachability check already used, just retargeted at the
live player position each tick) and flee via simple greedy
distance-maximizing when frightened. The reachability check itself
generalized from "is the exit reachable" to "is every dot, pellet, and
the exit reachable" — same guarantee, now covering the actual win
condition instead of just the exit. Verified precisely: a maze with a
reachable exit but one walled-off dot is correctly rejected.
Continuous movement is tick-based, not real physics: a repeating timer
steps the player in the buffered direction (or continues straight) if
open, tweening the sprite's pixel position between cells for a glide
feel rather than snapping. Same 3-lives-on-capture, coarse-grained
resume (position + collected keys + lives, ghosts reset to spawn on
reload) as the other templates' resume behavior.
The one existing maze game on production validated cleanly against
the new schema unchanged (lives defaults to 3, its dots were already
reachable) — no data migration needed this time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
More levels (30->60) and heroes (6->10) in the flagship dungeon so the
curve stays climbable at the new range. Each hero row now shows
whether it improves click damage, hits passively, or both — derived
from its existing bonus fields (heroRoleLabel), never hand-authored,
so it can't drift out of sync with what the hero actually does.
New permanent account-wide upgrade shop, funded by a currency
("crystals") earned by clearing clicker dungeons — kept deliberately
separate from the real coin wallet (wallets/coinTransactions, spent on
real-money purchases and manual save), so clicker play can never be
used to farm real-money-equivalent value. Same atomic-transaction
shape as spendCoins in src/lib/wallet/service.ts throughout:
- claimCompletionReward is idempotent per (user, game) via a unique
index — replaying and re-winning the same dungeon can't farm
crystals twice. Verified through the real HTTP API, not just the
service layer: claim, claim again (no-op), confirm balance
unchanged.
- buyPrestigeUpgrade does the same read-check-debit-increment
atomicity check as spendCoins — verified an insufficient-crystal
purchase leaves no partial debit.
- The two upgrades (click damage %, gold gain %) apply automatically
in every clicker game via a new accountBonuses prop threaded through
GameCanvas/PlaySession (same optional-passthrough shape already used
for initialState/onProgress) down into the registry the scene reads.
- /api/games/[id]/complete correctly rejects non-clicker games (400) —
the reward is clicker-specific.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
Full redesign of the Clicker template (same templateType: "clicker" —
plain text column, no migration needed): a battlefield with an emoji
monster that has real HP, click/DPS damage, boss fights every 5th
level (HP x4, 20s timer — timing out just resets the boss rather than
punishing progress), and heroes that level up repeatedly with scaling
cost (Clicker Heroes' defining mechanic, replacing the old one-time
"buy an upgrade" model).
No image pipeline available, so the "2D graphics" ask is met with
large emoji as monster art backed by real animation instead: an
HP bar that shifts green -> yellow -> red, camera shake + a red flash
+ a squish tween on every hit, floating "-N" damage numbers, and a
small coin burst on every kill.
Verified the HP/boss/gold/hero-cost curve with a standalone script
before wiring it into the scene (same approach used for the quiz
checkpoint math): boss multiplier lands only on levels divisible by 5,
HP growth and hero cost compounding both check out. Builder form and
AI-generation prompt updated to match the new shape.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
Full redesign of the Quiz template (same templateType: "quiz" key —
it's a plain text column, not a SQL enum, so no migration needed):
15-level money ladder with two guaranteed checkpoints (level 5, level
10), three classic lifelines (50:50, помощь зала, звонок другу — all
simulated client-side, weighted by difficulty band), a "Забрать"
walk-away option, and questions organized into 7 categories x 5
difficulty bands.
Ships with a 105-question bank of real, fact-checked trivia (history,
geography, science, literature, film/music, sport, general knowledge)
per the explicit "don't invent anything, use facts" requirement —
deliberately sticks to foundational/uncontestable facts rather than
"current record holder" style trivia that goes stale. A game session
draws one random unused question per level from that level's band
pool, so repeated playthroughs don't see the same 15 questions.
Checkpoint math verified against the classic rule (wrong answer keeps
the highest checkpoint reached, not partial credit) via a standalone
script before wiring it into the scene. Progress save/resume, the
builder form (added category+band fields), and the AI-generation
prompt (now requires real verifiable facts, structurally constrained
to the 7-category enum) all updated to match the new shape.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
Every page here is personalized (session, coin balance, owned games) —
no intermediate proxy should ever cache one. Traced a report of a
stale UI (missing header content that had already shipped) to caching
somewhere between the app and the client — nginx isn't even on this
VM, the user's reverse proxy (Nginx Proxy Manager) lives on a separate
one I can't inspect. Rather than debug an opaque hop I don't control,
close it from this side: explicit no-store on every HTML/API response
except /_next/static, which keeps its normal long-lived immutable
caching since those filenames are content-hashed by the build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
Header nav was splitting into three lines on mobile (logo / nav /
logout floating right with a big gap) — applied the same order-last
wrap pattern already proven in top-tickets/project-claude, so nav
drops to its own scrollable row instead.
Bigger feature: player progress now survives closing the tab, for all
three templates, not just Clicker. Each Phaser scene reports its state
through the same registry-callback pattern already used for onFinish
(onProgress), and reads an initialState to resume instead of always
starting fresh. Autosave fires every 10 minutes for free; an instant
manual save costs coins.
New coin economy (src/lib/wallet/): a wallet + transaction ledger per
user, atomic grant/spend via db.transaction() so two concurrent spends
against a low balance can't both succeed (verified with a real race
test — exactly one wins, balance never goes negative). New accounts
get a 20-coin signup bonus so manual save is testable before any real
payment method exists.
Real-money purchases are scaffolded but deliberately not wired to a
provider yet — user hasn't picked one. /api/wallet/purchase creates a
pending order and returns a clean "not configured" error, same shape
as the AI/LDAP not-configured responses elsewhere in this project. The
payment abstraction (src/lib/payments/provider.ts) is the one seam a
real adapter needs to fill in later.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
Without it, COPY . . in the Dockerfile overwrote the container's npm-ci
node_modules with the host's — a native-binary mismatch that surfaced
as a missing libnode.so.115 during the build-time page-data pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
A self-hosted, Roblox-flavored game platform: browse/play published
browser games, build them by hand or by describing them to an LLM.
Fully independent from top-tickets — separate repo, DB, port (8082).
- Auth: email+password accounts (argon2, httpOnly session cookies),
no admin/agent split — every account can create and play.
- Three game templates (quiz, clicker, maze), each a zod schema paired
with a Phaser 4 Scene that renders purely from a validated instance
of that schema — manual and AI-generated games hit the same
renderer, so a saved game is always guaranteed to load and run. The
maze schema BFS-validates that the exit is reachable from the start.
- Manual constructor: per-template builder forms, draft/publish/edit/
delete, a launcher grid of published games, a full-bleed play page.
- AI generation: Anthropic API key configured under Settings → AI
(encrypted at rest, validated with a live call before saving, same
shape as top-tickets' mailbox/LDAP settings). Generation uses the
SDK's zodOutputFormat/messages.parse to force schema-shaped JSON
output, with one retry feeding back the validation error. Output
always prefills the manual builder for review — never auto-published
blind. Clean, distinct error message when no key is configured yet
(no silent fallback exists here, so it doesn't pretend one does).
- Docker Compose on port 8082 (bakes in the busy_timeout=30000 SQLite
pragma from day one — top-tickets hit this build-time race twice).
Deferred: more templates/genres, drag-and-drop level editor, likes/
leaderboards, AI-generated sprite assets, multiplayer, moderation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH