Socket-event rate limits keyed on socket.id (new on every reconnect) and
passed NO ip argument, so the "IP-limited" prefixes never actually applied —
reconnecting reset every limit, and chat had no IP limit at all.
- New rateLimitContext helper resolves a STABLE identity (anon_token -> id,
falling back to socket id) and the real client IP (X-Forwarded-For honoured
only when TRUST_PROXY=true, else handshake address — no spoofing on direct
exposure).
- game:start, address:set and chat:message now pass (stableId, action, ip);
connection:new uses the real IP. 'chat:' added to IP-limited prefixes.
- Express 'trust proxy' enabled when TRUST_PROXY=true; documented in
.env.example. Aligned the contradictory default limit values with the
production override.
Adds rateLimit.test.js (reconnect can't bypass, IP backstop for fresh
identities, XFF gating). Full suite: 18 suites / 143 tests.