Skip to content

0.18 - #222

Open
endel wants to merge 114 commits into
masterfrom
0.18
Open

0.18#222
endel wants to merge 114 commits into
masterfrom
0.18

Conversation

@endel

@endel endel commented Jul 24, 2026

Copy link
Copy Markdown
Member

No description provided.

endel added 30 commits May 12, 2026 16:16
Documents the RoomPlugin system (definePlugins, lifecycle ordering,
message handler rules, typed state access, isolated testing) and the
first-party IdleKickPlugin shipped at colyseus/plugins/idle-kick.

Assisted-by: Claude Opus 4.7
Documents the WebRTC signaling plugin shipped at colyseus/plugins/webrtc,
including the signaling message table, client-side WebRTCClient API from
@colyseus/webrtc/client, and the maxPayload caveat for SDP-heavy
exchanges.

Assisted-by: Claude Opus 4.7
…ions

Rewrite the Room Plugins page around the array form of
`definePlugins([...])`: each plugin owns its key via
`readonly pluginName`, so `this.plugins.<key>` autocomplete works
without the user picking a name. The keyed-record form is kept as
"Multiple instances of the same plugin" for when the same class is
mounted twice.

Document `static dependencies` (auto-pull-in of prerequisite plugins,
zero-arg constructors, transitive resolution, dedup by class) and add
a warning callout against `<this>` in the `plugins =` initializer —
it triggers a TS7022 cycle and collapses autocomplete to `any`. The
canonical pattern is a type alias declared outside the room class.

Two new plugin pages: UniqueSessionPlugin (one concurrent session per
user, reject/replace) and TrackUserSessionsPlugin (per-user reverse
index in Presence — explicit install when the admin Active rooms tab
should surface a user's current room).

Assisted-by: Claude Opus 4.7
- Unify section skeleton: Features → Installation → Mounting → Usage → Password protection
- Lead with Features right after the intro on both pages
- Sentence-case headings; consistent app.config.ts code samples
- playground: drop Screenshots/Rooms wrapper, promote API endpoints to top level

Assisted-by: Claude Opus 4.7
…layer

- Rewrite pages/database.mdx around @colyseus/database (GameDatabase, services,
  matchmaker driver, custom schema), with Drizzle/BYO as the secondary fallback
- Cover dialects (SQLite default, PostgreSQL; PGlite as lower-profile option),
  migration strategies, room plugins, and feature overviews
  (auth, cloud saves, leaderboards, live configs, analytics/moderation/notes)
- Move Database from the Tools section to API Reference in the nav

Assisted-by: Claude Opus 4.7
…o-end

database.mdx:
- Promote Authentication to a top-level ### section under @colyseus/database
  with what's-wired-automatically bullets, minimal setup, admin helpers,
  and customization pointer
- Update cloud saves to the current save(userId, data, slot?, expectedVersion?)
  signature; document delete()
- Reframe "Custom schema" as "Customizing built-in table schemas" with a
  full table of the 10 built-in tables and which service uses each
- Simplify the configs Zod example to .default({}) (inner field defaults
  fill the object)

auth/module.mdx:
- Top-of-page callout pointing to the database integration
- Frame the Backend API section as the manual path

auth.mdx (landing):
- Lead with the database-backed path as the fastest route
- Add a Database-backed card alongside the existing three

auth/room.mdx:
- Callout noting JWT revocation is automatic when @colyseus/database is wired
- New "With @colyseus/database" section showing db.auth.isBanned() in onAuth

auth/http.mdx:
- Add an HTTP Routes (createEndpoint) tab — auth.middleware() works in both
  Express and createMiddleware modes
- Callout on revocation-awareness when @colyseus/database is in use

server/http-routes.mdx:
- New ### JWT Authentication subsection documenting auth.middleware() as the
  canonical JWT gate for createEndpoint, with the same revocation callout

Assisted-by: Claude Opus 4.7
Replace the icon Cards with a 5-row table mapping common questions
("How do I authenticate a room join?", "How do I gate a new HTTP route?")
to the page that answers each one. Keep the recommended-path Callout
above the table as the lede.

The Cards previously duplicated the table's destinations; the table
gives readers a one-glance "where do I go" rather than a row of
icon+title-only links.

Assisted-by: Claude Opus 4.7
@colyseus/database AuthService.ban() now folds the tokenVersion bump
into the same UPDATE as the ban fields — the package-side fix removes
the foot-gun where ban() alone left existing JWTs valid until expiry.

Doc changes reflect the new behavior:

- pages/auth.mdx: scenario 3 (revoke/force-logout) no longer says
  "bans don't bump tokenVersion — pair with bumpTokenVersion()";
  bans now invalidate live tokens on their own. Refactored /auth
  landing as an intent-based FAQ with Cards-per-scenario along the
  way (Common scenarios, 7 entries).
- pages/auth/room.mdx: top callout + "With @colyseus/database"
  section drop the pair-with-bump caveat.
- pages/auth/http.mdx, pages/server/http-routes.mdx: revocation
  callouts now name ban() alongside bumpTokenVersion() as both
  invalidating live tokens.
- pages/database.mdx: JWT-session-revocation bullet rewritten to
  describe ban() as atomic ban+bump; bumpTokenVersion() positioned
  as the standalone lever for non-ban revocation.

Assisted-by: Claude Opus 4.7
New section:
- pages/admin.mdx — landing page covering install, mount (router + Express),
  bootstrap, hardening checklist, full AdminOptions reference. Recommends
  auth.settings.onResetPassword for reset emails (player-side flow already
  handles delivery) over the panel-specific onResetRequest.
- pages/admin/authentication.mdx — sessions, JWT cookies, 3-role hierarchy,
  scoped mods, admin.guard() for gating monitor/playground/custom routes,
  password reset, rate limits.
- pages/admin/resources.mdx — defineAdminResource(), list/form/show/create,
  per-row + bulk actions, policies, FK auto-linking, audit log.
- pages/admin/dashboard.mdx — 5 preset widgets, custom widgets, 4 render
  modes (kpi/table/list/json), polling, 12-column grid.
- pages/admin/rooms.mdx — built-in room inspector (kick/lock/state edit/
  dispose), multi-process visibility, RBAC defaults.
- pages/admin/_meta.tsx — sidebar order.
- pages/_meta.tsx — Admin Panel entry in API Reference (after Database).

Reusable component:
- components/scenario-card.tsx — <ScenarioGrid> + <ScenarioCard icon title
  href>{children}</ScenarioCard> for landing-page intent indexes.
- style.css — .scenario-grid / .scenario-card with hover lift, shadow,
  150ms transitions; theme-agnostic via rgba.

Refactored:
- pages/auth.mdx — collapsed 7 vertical scenario sections into a single
  ScenarioGrid with 7 ScenarioCards.

Assisted-by: Claude Opus 4.7
Apply the new CLAUDE.md writing guideline ("Plain, approachable prose.
Skip marketing copy and hedging adverbs; keep the why alongside the
what.") across the admin section. Smaller, more surgical pass than the
previous attempt: cut marketing language ("ships with", "bundles") and
hedging adverbs ("typically", "generally"), but preserve transitional
sentences, brief rationale, and use-case examples.

Net: -/+19 lines across 5 files; CLAUDE.md committed for the first time.

Assisted-by: Claude Opus 4.7
Break pages/database.mdx (395 lines, single page) into a focused landing
plus four sub-pages — matching the cadence of the admin section.

Landing (pages/database.mdx, ~130 lines):
- Intro + service overview + beta callout
- Installation
- Quick start
- Dialects & connection strings
- Migrations
- Matchmaking driver
- Next steps (links to sub-pages)

Sub-pages:
- pages/database/authentication.mdx — built-in @colyseus/auth integration,
  admin/moderation helpers, customizing callbacks.
- pages/database/services.mdx — cloud saves, leaderboards, live configs,
  analytics/moderation/notes, plus the matching room plugins folded in
  (CloudSavesPlugin, LeaderboardsPlugin, AnalyticsPlugin).
- pages/database/schemas.mdx — customizing built-in table schemas, with
  the 10-row built-in tables reference.
- pages/database/bring-your-own.mdx — ORMs, query builders, Firebase,
  and the onAuth/onLeave integration patterns for BYO setups.
- pages/database/_meta.tsx — sidebar order.

Cross-references updated across 7 pages: /database#authentication-built-in-
colyseusauth-integration → /database/authentication;
/database#customizing-callbacks → /database/authentication#customizing-callbacks.

Assisted-by: Claude Opus 4.7
Covers the three database delivery modes (bring-your-own MMDB, MaxMind
auto-download, bundled DB-IP Lite), licensing terms for each, and
worked examples for region-locked matchmaking, locale defaults,
GDPR-aware analytics routing, and manual lookup via
`this.plugins.geoip.lookup(ip)` for reconnect-time re-resolution.

Assisted-by: Claude Opus 4.7
Replace the Cards grid with an early table of all five plugins (now
including GeoIPPlugin) with one-line descriptions; drop unused Cards import.

Assisted-by: Claude Opus 4.7
- matchmaker: add Batch Lookup by Room Id section for
  matchMaker.findRoomsByIds — single wire op for K known roomIds.

- room/plugins/track-user-sessions: document the new static
  listUserSessions(userId, options?), the UserSessionInfo shape,
  and the reconcile / removeStale options. Replace the stale
  matchMaker.query reconcile caveat.

- room/plugins/unique-session: update conflictsWith for its new
  (existing, currentRoom) signature with full IRoomCache exposure.
  Drop the misleading "scope by game mode metadata" example (it
  couldn't actually work before) and add three realistic ones:
  mode-based scoping, per-process exemption, capacity-aware
  filtering. Add a Performance section noting the 2 wire-op cap.

Assisted-by: Claude Opus 4.7
Server (room.mdx): new "Responding to a message" section — a message
handler returns a value (awaited) to answer the client; throwing settles
the request as an error; the "*" fallback can't answer and unknown types
reject with no_handler.

Client (sdk.mdx): new "Request/Response" section — room.request() and
room.send(type, payload, callback), timeout/rejection behavior, flagged
as JS/TS-only for now.

roadmap.mdx: point the #331 entry at the new section. Also folds in the
previously-staged restructure into a "Shipped in v0.18" section.

Assisted-by: Claude Opus 4.7
Add a Server-section page for the Colyseus Vite plugin (colyseus/vite),
covering setup, options, dev HMR (tied to devMode), production build, and
middleware mode. Cross-link from the Development Mode page and the server
devMode option.

Assisted-by: Claude Opus 4.7
# Conflicts:
#	style.css
New pages/netcode/ section (client-prediction, lag-compensation,
determinism, recipes) synced to the 0.18 observer-model predict API, with
targeted fixes across auth, database, faq, migrating and the phaser
tutorial surfaced by the full-site audit.

Assisted-by: Claude Opus 4.8
- sim step examples flipped to (ctx, world, command)
- room.clock section rewritten reader-first: always safe to call (no
  optional chaining), custom-clock swap note moved below the timelines
- input.data: note that undeclared-field writes do nothing and warn once
  per key with the debug panel active
- concepts.mdx removed — superseded by the netcode section

Assisted-by: Claude Opus 4.8
…cessor bookkeeping as a list

Assisted-by: Claude Opus 4.8
Migration guide gains: preview/next-tag install callout, schema 4→5 and
SDK version bumps, request/response messaging section, client.id /
fossil-delta-serializer removals, playground production lockdown.
Banner + header badge flip from 0.17 to 0.18.

Assisted-by: Claude Opus 4.8
…cipe

The three matchmaking flags were listed without semantics, and the
password recipe recommended setPrivate() — which excludes the room from
joinOrCreate() matching, defeating its own filterBy(['password']) step
(verified against the 0.18 test harness).

- room#matchmaking-properties: flag × join-path table, per-flag guidance,
  no-built-in-password callout; restore the truncated lock()/unlock()
  sections and the individual-setters list; metadata is replaced, not
  merged
- password recipe: filterBy + unlisted (not private) + onAuth() check to
  cover the joinById bypass and the missing-password-field hole
- consistency: driver/matchmaker/lobby pages now agree on what each flag
  filters

Thanks @jeffreyhugh for the heads-up!

Assisted-by: Claude Opus 4.8
- Document `onAuth` as a plugin hook: ordering row, the returns-nothing
  contract, and how GeoIPPlugin uses it to populate `client.geoip`.
- Switch examples to the array form of `definePlugins()`, the recommended
  shape; explain when the record form is worth it.
- Note that `@colyseus/geoip` is the one plugin outside the core package.
- Replace the misleading `onKick` note: arrows bind `this` to the room.
- Repoint broken links (`/auth/room`, `/sdk#send-and-receive-messages`)
  and swap `this.log.warn` for `console.warn`.

Assisted-by: Claude Opus 4.8
Tier 1 of TODO/sidebar-reorg.md — sidebar labels and ordering only, no URL
or content changes.

Root sidebar:
- "API Reference" becomes "Building Your Game", reordered to the dev loop:
  Server, Rooms, State Sync, Client SDK, Matchmaking, Auth, Database.
- Netcode moves out to its own "Advanced" separator — as the flagship 0.18
  feature it was buried at 5th-of-9, and the split signals "after the basics".
- "Matchmaker API" retitled "Matchmaking"; it was the lone "...API" entry.
- Admin Panel moves under "Tools & Integrations" (ex-"Tools") — it's a
  product, not API reference.
- "Infrastructure" renamed "Deploy & Scale".
- The consecutive "More" and "Extra" junk drawers merge into "Resources".

Server: group children under Setup / Development / Production instead of
accretion order.

Rooms: title the three untitled children and order by learning curve —
messages, timing events, reconnection, exceptions, plugins, built-in.

Assisted-by: Claude Opus 4.8
These anchors pointed at headings that no longer exist (or never did):

- /room#patchrate            -> #configuration-properties (it's a table row)
- /room#set-simulation-interval -> #game-loop, and the link text had a typo
  ("setSimiulationInterval"); renamed to setTimestep(), the current name
- /room#client               -> #client-instance
- /sdk#consume-seat-reservation -> #other-join-methods
- /sdk#join-or-create-a-room -> #join-or-create-recommended
- /sdk#join-existing-room    -> #other-join-methods

Found by slugging every heading with github-slugger (what Nextra uses) and
resolving every `](/page#anchor)` link against it. The existing check-links.js
and find-broken-links.js both split on "#" and only validate the page path,
which is why BROKEN_LINKS.md came out empty.

Assisted-by: Claude Opus 4.8
The two existing checkers (check-links.js, find-broken-links.js) were dead:
both used `require` under `"type": "module"`, so they crashed on startup.
That's why BROKEN_LINKS.md was empty — it wasn't "no broken links found",
the script never ran. Both are replaced by scripts/check-links.js.

The new checker validates anchors, not just page paths. A link to a renamed
heading still loads the page, so it never 404s and rots silently — that class
of rot accounted for every one of the 33 fixes here. Headings are slugged with
github-slugger (added as a devDependency), the same slugger Nextra uses, so
results match what ships. It also:

- checks same-page `#anchor` links and trailing-slash routes (`/sdk/#x`)
- skips fenced code, JSX/HTML comments, and inline code, so commented-out
  <Cards.Card href> entries don't register as links
- unwraps `[label](url)` in headings, so `## Self-hosting on [Vultr](…)`
  slugs as self-hosting-on-vultr
- exits non-zero, so it can gate CI

Run with `npm run check-links`.

Notable fixes: 4 tutorials pointed at a Unity heading renamed to "Running the
test server locally"; 9 links used an SDK page structure that no longer exists;
migrating/0.15 had a case-mismatched anchor (#onLeave-… vs the lowercase id).

Assisted-by: Claude Opus 4.8
Tier 2 (part 1) of TODO/sidebar-reorg.md. room.mdx drops 1,241 -> 801 lines:

- pages/room/visibility.mdx — the locked/private/unlisted flags, setMatchmaking,
  lock/unlock, and the password callout. Titled "Room Visibility & Access:
  locked, private, unlisted" so the terms people actually search for are in the
  page title. This is the answer that was invisible at an anchor inside a
  1,241-line page.
- pages/room/lifecycle.mdx — the full onCreate/onAuth/onJoin/onDrop/onReconnect/
  onLeave/onDispose reference plus the devMode and shutdown hooks.

room.mdx keeps overview, state, messages, config, communication, reconnection,
and Client Instance, with a short pointer where each extracted section was.

Old anchors: the plan called for mapping these in pages/404.mdx, but that can't
work — /room still exists, so Next serves it 200 and the 404 page never runs.
Server-side redirects can't help either, since browsers don't send the fragment.
Added <MovedAnchors> (components/moved-anchors.tsx), a small inline script on
the source page that maps the 16 moved hashes to their new homes, so external
links and bookmarks keep working.

Headings were promoted a level (### -> ##), which leaves their slugs unchanged,
so every moved anchor resolves on the new page. check-links.js now also
validates bare "/path#anchor" string literals, so the redirect map itself is
covered — verified with a negative test.

Verified: 165/165 pages build, check-links clean, and a line-by-line diff of the
original against the three resulting files shows no content lost (the only
deltas are the 16 heading promotions and 8 deliberately rewritten links).

Assisted-by: Claude Opus 4.8
endel added 26 commits August 13, 2026 12:13
Six recipe blocks gain the six-SDK tab group; the dead-reckoning fence
that mixed client and server code in one block is split so only the
client half is tabbed. The confirmOn pickup recipe and the Rapier sim
recipe stay TypeScript with availability notes, and the Godot tab for
unreliable input states the reliable-only limitation.

Assisted-by: Claude Fable 5
The client-side stamp example and the warnOnDivergence example gain the
six-SDK tab group. The GDScript and Dart panes read the drift telemetry
directly since those bindings expose no warnOnDivergence option, and
the allowRewind bullet names their spellings of the input gate.

Assisted-by: Claude Fable 5
Tab titles drop the engine name — TypeScript, C#, Lua, Haxe, GDScript,
Dart — since the engine adds width without telling the reader anything
the language does not. The sdk-tab-language groups lose "Unity (…)" and
"Defold (…)" for the same reason.

state/callbacks was still on five panes per group, so selecting Dart
rendered nothing; all eight groups now carry a Flutter example, with
bindTo marked unimplemented the way the other ports already are.

Assisted-by: Claude Opus 5
The Defold examples called socket.gettime() directly, putting a
LuaSocket dependency in user code for a value the SDK already tracks.
room.clock:now() is the same monotonic ms axis Predict.get(room) wires
into tick(), and it is present on every Defold target, so the snippets
now build unchanged for HTML5 and desktop.

Assisted-by: Claude Opus 5
tick() now defaults `now` to the room clock in every SDK, so the C#,
Lua, Haxe, GDScript and Dart examples just call it. The GDScript one was
passing Time.get_ticks_msec(), an engine clock with no guaranteed
relationship to the SDK's epoch — exactly the footgun the default
removes. JavaScript keeps the rAF timestamp, which is the exact frame
time on the same axis.

Assisted-by: Claude Opus 5
The examples dropped the timestamp when it became optional, but the
prose on the landing page, the determinism rules and two asides still
spelled the call `predict.tick(now)`, which reads as a required
argument. The TypeScript examples and the reference sentence keep it:
there the rAF timestamp is genuinely the thing to pass.

Assisted-by: Claude Opus 5
…for delivery modifiers

Work in progress across several threads, committed together as a checkpoint.

Demo listings become components instead of per-page markup. `premium-demos.tsx`
holds the showcase prototypes as one source of truth rendered on both `/netcode`
and `/learn`, so the two pages can't drift; `client-list.tsx` replaces the wide
per-demo engine table with a compact row per client implementation — every
client of a demo renders the same game, so a screenshot per row carried no
signal and the engine logo is what identifies it. Supporting styles, the
prediction demo imagery, and the Flutter icon come with them.

`/learn` is reorganized around those components and Cards, and its description
now names the engines it actually covers. `/netcode` drops its inline demo grid
and the commented-out placeholder block in favour of the shared component.

Flutter joins the getting-started client line-up.

`state/schema.mdx` and `schema/decorators.mdx` gain a decorator column on the
field- and delivery-modifier tables, and the decorator examples fold into the
existing style tabs rather than living in a separate block — one table to read
instead of two spellings to cross-reference.

`cloud/postgresql.mdx` is removed, with its `_meta` entry dropped and a `404.mdx`
mapping added. `roadmap.mdx` picks up `<MovedAnchors>` for a renamed heading.
Smaller fixes: the PM2 example points at `build/index.js` (the compiled
entrypoint, not `lib/`) and gains `wait_ready`/`time`; deployment and
getting-started descriptions rewritten for search.

`tsconfig.tsbuildinfo` is deliberately left out — it's a build artifact and
should probably be gitignored.

Assisted-by: Claude Opus 5
The unreliable paths read as generally available; none of that held. Each
opt-in point now says what is true — both the features and WebTransport are
experimental, only WebTransport has a datagram channel, and only the
JavaScript/TypeScript SDK supports it. Other SDKs now reject
mode:"unreliable" at construction, and the recipe tabs that showed C#/Lua/
Haxe/Dart code for it carry "not available yet" notes instead of code that
throws.

Two removed claims were wrong outright: the delivery-modifiers callout said
no client-side setup is required for all three modifiers, and a stale
warning said .unreliable() "has no effect" over WebSocket and is "safe to
leave either way" — the field actually freezes after the full state sync,
since tick patches skip clients without a datagram channel. The
allowRewind bullet also caught up with the SDK: reliable mode only, an
unreliable packet stamps its whole ring or none of it.

The WebTransport page gains an "Unreliable delivery" section so the
capability is discoverable from the transport side, not only stumbled into
from the feature pages.

Assisted-by: Claude Fable 5
Streaming collections now order a client's backlog with a callback passed
to `view.subscribe()`, so the closure captures that client's entity.

Drops the section that existed only to explain the old workaround --
copying coordinates onto the StateView with `(view as any).x` and
refreshing them every tick. The anchor is the entity now, so it cannot go
stale.

Rewrites "At a glance" into a runnable example: the previous one declared
a priority callback with no view for it to apply to, and referenced a
players map the schema never defined. Folds the four scattered priority
blocks into one section, per-client first.

Also corrects view.mdx: `subscribe()` is idempotent except for a supplied
priority, which always replaces the previous one, and the page mentioned
"priority order" without ever saying how to set it.

Assisted-by: Claude Opus 5
The `subscribe()` callout overstated the rule. Additions made before any
client holds a StateView land in the broadcast backlog and still reach
everyone; only additions made after that point need the subscription.
That is the failure mode worth naming: the room looks like it works, then
silently stops delivering once the first client joins. Verified against
the encoder in schema's DocsStreamingExample suite.

Also rewrites both pages under the new no-em-dash rule.

Assisted-by: Claude Opus 5
The 64th field is now rejected where the class is defined. Its operation
byte collided with the marker that starts a new structure, and the
previous advice for the slot (make the last field a primitive) did not
hold: a primitive reaches the same operation by being set to undefined
and back in one tick.

Assisted-by: Claude Opus 5
0.17 accepted 64, but an operation on the last field could encode as the
byte that starts a new structure, so that slot already desynchronized
clients. The 64th field is now rejected where the class is defined,
which moves the failure from production to startup.

Assisted-by: Claude Opus 5
llms.txt had not been touched since it was added: two of its 22 links pointed
at pages that had moved, it covered 22 of 140 pages, and check-links never
looked at it. Deriving it from the sidebar and each page's frontmatter removes
the class of bug rather than the instances. sitemap.xml and robots.txt come
from the same pass.

Every page also ships as raw markdown at <route>.md, because an agent that
fetches a page gets HTML and the code fences carrying the API survive that
badly. A conversion that mangles a fence is worse than no file at all, so an
unknown component or JSX leaking into the output fails the build.

Assisted-by: Claude Opus 5
The page-move ritual lived as two bullets in CLAUDE.md and decayed anyway:
918b377 had to repair the redirect map, and only 13 of 142 pages carry the
<MovedAnchors> half that no check can infer. It is now a skill with
completion criteria, alongside one for auditing claims against the
framework source and a two-axis docs review. CLAUDE.md keeps only what
bears on every turn.

The hook lints staged pages at error level rather than the whole corpus.
A full run replays 331 pre-existing warnings, which is how a hook gets
ignored.

Assisted-by: Claude Opus 5
The page shipped in 5c4b5d0 without its `_meta.ts` entry, so it was
reachable by URL but missing from the sidebar.

Splitting the old "Limitations and Best Practices" heading also stranded
its anchor: `<MovedAnchors>` now forwards it to `#limitations`, and the
three inbound links that promised efficiency tips point at the new page
instead of a section that only ever listed limitations.

Assisted-by: Claude Opus 5
The refId and collection-index sizes stopped at 3 bytes, where the real
tiers are 1/2/3/5. `.patchOnly()` was listed among the bitmask-classified
modifiers, but no `patchOnlyBitmask` exists. `.noSync()` was cited from
the delivery-modifiers section, which defines exactly three and excludes
it. And `.deprecated()` was described as having no setter: it installs a
no-op one, while `.deprecated(false)` installs no descriptor at all, so
that field keeps its real accessors and its value still ships in every
full state sync.

The 63-field cap now names its two quiet contributors, inherited and
`.deprecated()` fields, in the schema reference rather than only on
/state, so a schema following the versioning advice does not reach the
cap by surprise.

Assisted-by: Claude Opus 5
Each route is stamped with the date of the commit that last touched its
page, read from `git log`. A pre-commit hook runs before that commit
exists, so the sitemap it approves is stale the moment the commit lands
and the *next* commit is rejected for it. Every one of the last 20
commits touched `pages/`, so the gate fired on essentially all of them,
always blaming the wrong change.

The file is now gitignored and left to `build`, which runs the generator
first and sees settled history. `--check` keeps gating llms.txt and
robots.txt: those derive only from the page files, so they mean the same
thing in the hook and in CI.

Assisted-by: Claude Opus 5
An em-dash appositive is almost always a second idea, which the sentence
rules already want split out, and it reads worse for non-native readers
than the comma, colon or parentheses that replace it. `Colyseus.NoEmDash`
makes that a CI error rather than a habit, and flags spaced en-dashes and
` -- ` so the ban cannot be dodged with a lookalike.

The one permitted use is the empty-table-cell placeholder `| — |`, which
the rule misses by construction: its token needs a non-space neighbour,
and a lone dash in a cell has none.

Assisted-by: Claude Opus 5
They were committed before `.DS_Store` reached .gitignore, and git keeps
tracking a file the rule was added after. Finder rewrites them whenever
someone opens those folders, so they surfaced as phantom modifications.

Assisted-by: Claude Opus 5
Both are pulled from the fps demos, where the deciding property is that
they run without a DOM: one collision implementation serves the backend
and the browser, which is what lets prediction agree with the server.

node-three-gltf gets the build-time caveat too. Parsing a GLB at server
boot costs startup time for geometry that never changes.

Assisted-by: Claude Opus 5
All five premium prototypes and the Prediction Playground are live on Vercel
against the shared server at demos.colyseus.cloud, so the placeholder warning
on the play URLs can go and the playground gets a link readers can click.

check-links does not validate external URLs, so nothing here fails CI if a demo
host goes down. That is now noted where the URLs are defined.

Assisted-by: Claude Opus 5
The callback receives the same auth context as onAuth, so the ip resolution
order and the onAuth-only context.req are recorded alongside it.

Assisted-by: Claude Fable 5
The demo repositories moved into the colyseus org, so the MOBA and karts
links point at their canonical names instead of redirecting.

Assisted-by: Claude Fable 5
The intro promised "three different games" directly above an entry that
opens "a tour rather than a game", so it now says projects.

Assisted-by: Claude Fable 5
They are working notes, not documentation. TODO/ was also stripped from every
commit that carried it, so this branch's history was rewritten.

Assisted-by: Claude Fable 5
endel added 3 commits August 18, 2026 16:10
Readability pass over the Vale warnings: 330 down to 38, all of which
are the segmenter false positives documented in STYLE.md. AmbiguousThis
now also flags bare "This" with any common verb; "It" keeps the
narrower verb list because "It returns…" right after naming the
subject reads fine.

Assisted-by: Claude Fable 5
The v0.18 section only ever existed on this branch, so no published URL
carries either spelling of its old slug. The v0.17 entry stays: master
still serves that heading with the typo.

Assisted-by: Claude Opus 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant