Imported from corrodedlabs/synth (
AGENTS.md). Install upstream withnpx skills add corrodedlabs/synth. Copyright stays with the author.
AGENTS.md
This repo contains a Racket game server and game logic, plus a Vite + Three.js
frontend in app/. Use this guide when making changes or running tasks.
Repo layout
server.rkt: WebSocket service, room management, and gameplay orchestration.client.rkt: Racket bot client and simulation helpers.game.rkt: Core game rules, cards, bidding, and play loop.tests.rkt: RackUnit integration test that boots the server.app/: Vite TypeScript client (Three.js immersive UI).repos/: vendored upstream repositories used as read-only reference material.
Vendored repositories
- External repositories may be vendored under
repos/for agent reference. - Treat vendored repositories as read-only unless explicitly asked to edit them.
- Do not import application code from
repos/; use normal package dependencies. - Prefer examples and patterns from vendored source over guessing from memory.
- When writing Effect code, inspect
repos/effect/for idiomatic usage, tests, module structure, and API design. - If
repos/effect/LLMS.mdexists, read it before writing Effect code.
Commands
Racket (run from repo root)
racket server.rkt: start the WebSocket server on port 8081.racket server.rkt --port 9000: start server on another port.racket server.rkt --timeout 30: set idle timeout (seconds).racket client.rkt: run client helpers (used manually for simulations).
Racket tests
raco test -t tests.rkt: run the RackUnit suite intests.rkt.racket tests.rkt: also works (callsrun-tests).- Single test: there is only one suite in
tests.rkt; isolate or add a newtest-caseor file and runraco test -t <file>for targeted runs.
Frontend (run from app/)
npm install: install dependencies.npm run dev: run Vite dev server.npm run build: typecheck withtscand build with Vite.npm run preview: preview production build.- Lint/test: no lint or unit test runner is configured for the frontend.
- Effect is available in the frontend via the
effectnpm package.
Runtime notes
- Server expects WebSocket text frames containing s-expressions.
- Dispatch happens in
server.rktviacaseon the leading symbol. - Game rooms are keyed by host email; running games are keyed by room name.
- Each connected user has a
commchannel for server-to-game messaging. - Client bots connect to
ws://localhost:8081/testby default. - Use
with-output-to-string/readfor serialization and parsing.
Message protocol conventions
- Requests are lists whose first element is a symbol (ex:
(connect-user ...)). - Responses are lists or symbols (ex:
'room-created,(room-members ...)). - Server replies are serialized with
write, so avoid non-serializable structs. - Use
serializable-structwhen sending custom data over the socket. - Include user email as the first argument for user-scoped messages.
- When adding a new message, update the dispatch comment block in
server.rkt. - Validate message arity before
cadr/caddraccess in handlers.
Frontend entry points
- Vite entry is
app/index.htmlwhich loadsapp/src/main.ts. app/src/main.tssets up the SceneManager and GameState.app/src/scene/SceneManager.tsowns the Three.js renderer, scene, and camera.app/src/game/GameState.tsorchestrates game flow and logic.app/src/interaction/DragControls.tshandles touch/mouse input.
Racket code style
- Prefer
#lang racketat top of each file. - Organize
requireblocks: Racket stdlib, third-party, then local files. - Keep
providelists near the top; update exports when adding new public APIs. - Use
structorserializable-structfor data models shared over the wire. - Keep module-level mutable state near its definition (hashes, registries).
- Naming conventions:
+name+for constants (ex:+max-players+).*name*for mutable globals or registries.name?for predicates.name->valuefor conversion helpers.
- Prefer
definefor named functions,λfor inline lambdas. - Use
case-lambdawhen functions accept multiple arities. - Use
match/match/valuesfor destructuring; keep patterns explicit. - Prefer
matchover nestedcar/cdrwhen processing messages. - Favor pure functions in
game.rkt; limit mutation to server state modules. - When mutating hashes, use
hash-set!/hash-update!and keep updates local. - Use
casefor message dispatch with explicit fallthroughelse. - Keep list operations explicit (
map,foldl,filter,findf). - Formatting: 2 spaces indentation, trailing parens aligned with
define. - Avoid overly long lines for nested
cond/let; split into helper functions. - Prefer
let-based loops over mutation-heavy recursion. - Logging uses
displayln+format; keep logs brief and actionable. - Error handling:
- Use
(error "message" context ...)with descriptive text. - Include relevant values (user id, room name, etc.) in error context.
- Avoid placeholder or profane error messages in new code.
- Use
- Concurrency:
- Use
channel-put/channel-getfor player messaging. - Guard WebSocket send paths with connection checks.
- Run long-lived game operations on
threadto avoid blocking the listener.
- Use
- Networking:
- Messages are s-expressions serialized via
write/read. - Keep message shapes documented in
server.rktdispatch section. - Validate message arity before destructuring in handlers.
- Messages are s-expressions serialized via
TypeScript/Three.js code style
- Use ES module imports.
- Use 2-space indentation, semicolons, and double quotes for strings.
- Favor
constandlet. - Prefer named exports.
- Naming conventions:
PascalCasefor classes and types.camelCasefor functions, variables, and file names.
- Types:
- Rely on inference for simple locals.
- Add explicit types when interacting with Three.js or DOM APIs.
- Modularize logic into
src/scene,src/game,src/interaction. - Avoid global state; pass dependencies (Scene, Hand, etc.) via constructors.
- Error handling:
- Validate external inputs.
- Wrap async code (like model loading) with error handlers.
- Effect code:
- Prefer named imports from
effectfor app code, for exampleimport { Effect } from "effect". - Use
Effectfor async orchestration, typed failures, resource lifecycle, and service boundaries; avoid refactoring purely synchronous Three.js math just to use Effect.
- Prefer named imports from
Three.js patterns
- Use
SceneManagerto encapsulate boilerplate (renderer, loop, resize). - Use
GLTFLoaderfor assets, handling async loading gracefully. - Use
Tween.jsfor smooth animations (cards, camera). - Prioritize mobile performance (shadow map resolution, polycount).