## Socket starter

This is a WebSocket service, not an HTML page. Connect to `ws://localhost:8181/match?redwebVersion=1`.
The URL selects the match route; the message `type` selects its `Join`, `Move`, or `Resume` handler.
There are no socket decorators or secondary `message.action` dispatchers.

Read `src/schema.ts`, `src/app.tsx`, and `src/handlers.ts` together: they define
the inbound message schemas, route/server configuration, and handler behavior.
The files work as a complete recipe; the displayed handlers depend on their
schema and route configuration. Session ownership is separate from room fan-out.

Each handler receives its named definition from the shared `SCHEMAS` registry in
its `BaseHandler` constructor. The object key becomes the message type and the route derives its dispatch map from the
registered handlers. The schema validates client-to-server payloads before the
handler runs. `protocol: { versions: ['1'] }` independently enables versioned
envelopes; it does not declare an event schema.

Server handlers create and send their own responses with `socket.sendEvent`.
Redweb doesn't validate server output against a second schema map. Use
TypeScript types and unit/integration tests to verify those payloads. A client
can use `ProtocolClient` from `redweb/client` for envelope framing, but that
helper doesn't validate application-specific payloads or open/reconnect the
WebSocket. Add client-side validation when the application needs it.

The initial `state` response contains `{ session, name, x, y }`. Send `move`
with `{ x: 7, y: -3 }` to change the server-owned position; send `resume` with
`{ session }` on a new connection to recover it. Messages are processed in
order on each connection. Use WSS outside local development.

`npm test` builds the starter and opens real sockets. It checks independent
players, server-side moves, disconnect/resume, and server rejection of malformed
raw input. No mocks or external service are needed. The starter also passes with
the original source directory unavailable after building.

### Boundaries

- This demonstrates session-aware dispatch, not a complete authoritative game simulation. Coordinates are bounded integers; applications must enforce their own movement/rate/game rules.
- The random session ID is a bearer credential. Anyone holding it can resume that player and replace its previous connection. Keep it private; do not broadcast the `state` response to other players. Add account authentication and bind sessions to authenticated identity for production.
- Sessions are in memory, local to this server, capped at 100, and expire 30 seconds after disconnect. Server restart loses them. This is not persistent storage or a multi-instance session system.
- Calling `join` or `resume` while already joined is rejected. Movement before joining and unknown/expired sessions are rejected. Application failures currently use the protocol's sanitized `HANDLER_FAILED` error.
- Invalid inbound payloads produce `INVALID_PAYLOAD` and close that connection with code 1008. Outbound payload correctness is application-owned and should be asserted in tests.
- Transport and heartbeat bounds are illustrative; tune and load-test them for your deployment. Zod belongs to this starter, not Redweb's runtime dependencies.
