Skip to content

0.18 - #222

Merged
endel merged 144 commits into
masterfrom
0.18
Aug 25, 2026
Merged

0.18#222
endel merged 144 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 13 commits August 19, 2026 10:03
Figurative "underwater" is the kind of idiom the style guide exists to
avoid; the opener-metaphor rule stays, but this one reads better plain.
STYLE.md cited the exact phrase as its example, so the citation now
points at the lag-compensation opener instead.

Assisted-by: Claude Fable 5
The hero now carries "Multiplayer Framework" under the wordmark, as the social
card already did, which left the text h1 repeating what the artwork says.
Wrapping the image in the h1 keeps the heading text in the alt rather than
hiding it with CSS, which Google discounts, and priority stops the LCP element
from being lazy-loaded.

The markdown twins had no rule for a raw heading tag. h1-h6 now convert, and an
image-only heading resolves to its alt.

Assisted-by: Claude Opus 5
The one-liners left the differences invisible: drain() had no example at
all, and iterate vs next() only diverged in the surrounding loop the
snippets omitted.

Assisted-by: Claude Opus 5
sdk 0.18.2 merges the two per-second rate options into one time
constant in ms (smoothMs = 1000 / old value). Snippets move to the new
name in every language tab, the reconciler options gain the missing
smoothMs bullet, and the 0.18 migration page carries the conversion
rule.

Assisted-by: Claude Fable 5
The hold example's loop also gains the wasIdle guard: a frame held
through a packet gap repeats jump: true, and edge-triggered actions
must not re-fire on it.

Assisted-by: Claude Fable 5
The rename pass introduced smoothMs into every snippet without saying
what it does on lerp. A recipe covers the judder it exists for, the
speed × smoothMs sizing rule, and the draw-vs-hit boundary; the
per-attach options list gains the missing smoothMs bullet.

Assisted-by: Claude Fable 5
The options reference sat inside the Steps, pushing "Edit your room
code" below two tables. It now lives in its own Scaffolding options
section, and the steps keep a one-line pointer to it.

Assisted-by: Claude Fable 5
0.18 was never announced, so a rename between its previews is not a
migration concern for anyone coming from 0.17.

Assisted-by: Claude Fable 5
These repositories are private, so a non-sponsor who follows one gets a 404.
That is why the link was withheld; the note under the grid now explains it
instead.

Assisted-by: Claude Fable 5
The screenshot is a cropped frame from Defold's announcement video, which
showcases the Photon build of the same sample: the crop keeps gameplay only,
no browser chrome and no Photon branding.

Assisted-by: Claude Fable 5
@endel endel mentioned this pull request Aug 20, 2026
endel added 14 commits August 20, 2026 17:50
…bles and screenshots

Every snippet now runs as-is: the resources example needed
`tables: { ...db.tables, guilds }` and SQL column names, `'everyone'`
still requires a signed-in identity, and `totals` never counts custom
tables. The password-reset section described a panel page that does not
ship, so it now documents the API flow and the relative `url`.

Any password-holding player can log in and read live room state through
`/admin-api`, so the roles docs carry a warning instead of stating it as
a feature. Multi-process visibility needs shared presence, not only a
shared driver.

Screenshots are regenerated from a harness that runs the docs' own
configuration, replacing three captures from a customized build whose
labels contradicted the page.

Assisted-by: Claude Fable 5
The import now ships two scripts, the client has to be created after
colyseus_is_ready() (HTML5 instantiates the module after the game has
started), get_state/map_get hand back structs rather than handles, and
send() accepts booleans. Adds the drop/reconnect events and the
reconnection-token functions the reconnect() row depended on, and the
on_change forms missing from the callbacks table.

Assisted-by: Claude Fable 5
The Haxe SDK gained `Predict.get(room, opts)` and the fields-array attach shape,
so the callout claiming the options object is JavaScript-only was wrong, and the
Haxe tabs no longer have to diverge from the TypeScript ones.

Assisted-by: Claude Opus 5
The page never suggested the SDK could do client-side prediction, so the whole
0.18 netcode stack was reachable only by reading colyseus/predict/*.lua. It now
carries the Defold-shaped loop, the update(self, dt) half the SDK-tabbed netcode
page cannot show, and points at /netcode/client-prediction for the rest.

The quick example printed room.id, which the Lua SDK does not define; the field
is room.room_id, so that line threw on a successful join. The new FAQ entry
covers the endpoint: os.getenv() is nil on HTML5, so it has to come from
game.project via sys.get_config_string.

Assisted-by: Claude Opus 5
Connection Lifecycle stays the only child page. Heading slugs survived the
move, so /sdk/messages redirects with replace: true and the four MovedAnchors
keys aimed at the child page are gone: those anchors resolve on /sdk natively
again.

Assisted-by: Claude Fable 5
Several "JavaScript-only" caveats no longer hold: C# takes the Predict.Get
options object, ConfirmOn (including the add/remove membership variants),
Adopt for opaque sim extras, and the Read/ReadAt batch forms.

The C# reconciler also infers the mirrored fields, so the Fields array the
examples passed is dead weight: every scalar field of the bound instance
mirrors by default.

Assisted-by: Claude Fable 5
The constructor options were only shown, never explained: they seed the
room-wide defaults each attach inherits, and setDefaults() only reaches
attaches made after the call.

Assisted-by: Claude Fable 5
The right substitute depends on what is being replaced, so this is an
existence rule carrying a rewrite message, not a substitution entry in
Idioms.yml pinning one swap. Error level for the same reason the term rules
are: there is no judgement call left to the writer.

Assisted-by: Claude Opus 5
reviewdog 0.17 asked GitHub for the PR diff to resolve `filter-mode=added`,
and #222 (223 files, ~18k lines) trips the API's 20k-line cap: a 406 that
`fail_on_error` turned into a failed build. 0.21 falls back to the git CLI.

Two side effects, both wanted: push builds now lint `pages` instead of
exiting 0 with "this is not PullRequest build", and `fail_on_error` fails
on errors alone, which is what the step's comment already claimed.

Assisted-by: Claude Opus 5
@endel
endel merged commit d5ff1de into master Aug 25, 2026
7 checks passed
@endel
endel deleted the 0.18 branch August 25, 2026 19:02
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