#Integrated backend baselines — October 28
Fresh, sequential, uncontended runs on Intel Core Ultra X7 358H / Linux x64 /
Node 22.23.4 use room-simulation.1, 60 warm-up + 300 measured ticks, 16 actual
trusted motors and 128 bodies. All 128 remain active; every traveler stays on the
walkway. The backends were measured separately, without competing test load.
| Local simulation CPU, ms | p50 | p95 | p99 |
|---|---|---|---|
| Rapier | 89.725 | 102.195 | 107.302 |
| Owned | 419.691 | 620.580 | 664.337 |
Whole-tick p95 is 118.656 ms reference / 634.813 ms owned. Traveler stepping alone has p95 97.726 / 441.866 ms; the 10 Hz body batch p95 is 13.206 / 211.359 ms. Checkpoint and wire work are measured separately in the raw reports. Both fail the <8 ms p95 / <16 ms p99 simulation target. These runs do not qualify 16-player deployment. They exclude database, scheduling, sockets, GPU, long-duration load and physical-device qualification. They are longer samples than the historical 30-tick runs below, not equivalent before/after comparisons.
Reproduce with npm run physics:room -- --rapier --ticks=300 or --owned.
Raw local reports/logs: .data/qualification-2026-10-28/room-baseline-{rapier,owned}.{json,log}.
The separate rendered arrival sample is recorded in RELEASE_CANDIDATE. Feature
implementation stays 70/70. Further optimization belongs to a separately scoped
follow-up after the frozen beta release gates, not an endless extension of this pass.
#Sixteen-player / 128-body ceiling — October 27
The persistent host now accepts an explicitly configured DW_ROOM_CAP=16 (the
unchanged default is 8). Regional route files and retained room hosts accept
playerCap:16. compose.persistent.yaml passes through DW_ROOM_CAP, so Coolify
can set it on that persistent-host stack. This does not change the separate
arrival-art relay beta, whose cap remains eight.
Authored dynamic bodies now have a 128-body world limit, shared by source validation, native registries, checkpoints, motion frames and carried model manifests. Spawned collision remains separately limited to 32, and combined spawned/authored rigid bodies cannot exceed 128. The new limits require the new retained engine; old engine builds retain their old limits. Source shape and solver math are unchanged.
The sixteen-player ceiling is also used by contacts, seats/helms, cargo riders, NPC/sensor inputs and recording readers. Room socket headroom grows with the configured cap, allowing an existing player to replace a tab at full occupancy. It does not bypass admission pressure, RSS or message bounds.
This is an admission/data-capacity increment, not a supported full-load claim. The integrated CPU workload currently misses the simulation budget. Keep normal rooms at eight until the workload and deployment-specific load checks pass.
#Reproduce the simulation workload
npm run physics:room
# Short diagnostic run, or one backend:
npm run physics:room -- --ticks=30
npm run physics:room -- --rapier --ticks=300This uses RoomFloating plus sixteen actual TrustedMotion instances in one
128-buoy court. The host's three-body-step batch at 10 Hz is combined with thirty
motor steps per second without averaging away batch spikes. It separately times
room bodies, player simulation, checkpoint materialization/serialization and
binary motion packets. Every traveler walks back and forth on the same path;
leaving it invalidates the run. Body activity is measured, and the CPU budget
cannot pass unless all 128 bodies stay active. Defaults: sixty warm-up ticks and
300 measured ticks; short runs warm up for the smaller of sixty or their tick
count. Runtime progress goes to stderr; JSON results go to stdout.
The reported packet throughput covers only motor snapshots, not all network
traffic. Database, event-loop scheduling, sockets, GPU rendering, interaction-heavy
crowds, packed stacks, longer stress runs and other hardware are excluded. The
existing bench:rooms trial below is a different test: sixteen players across
two eight-player rooms with the Lantern House content.
#Prepared component reads — October 27
Fixed-shape component readers now compile their field traversal once. Transaction forks share that immutable layout, while each read still returns fresh objects, arrays and cloned dynamic records. Worlds without public component declarations skip catalog construction; authored-state validation constructs a declared catalog once per invocation instead of once per changed body. Every invariant still runs.
The same 30-warm-up / 30-measured-tick workload on the machine below gives:
| Simulation, ms | Rapier before | Rapier after | Owned before | Owned after |
|---|---|---|---|---|
| p95 | 132.287 | 106.976 | 594.267 | 565.255 |
| p99 | 136.659 | 112.879 | 595.047 | 570.884 |
All 128 bodies remain active; all sixteen final traveler positions exactly match the preceding run. The required <8/<16 ms gate still fails. This short sample has the exclusions below and does not qualify deployment capacity.
Focused validation: 52 native checks pass; one database-dependent public-program persistence check was skipped. Existing checks cover read isolation, mixed and numeric rows, transaction rollback, transforms, public contracts and both native room backends. Ten recorded owned scenarios retain every prior 240-step hash. The production build passes. Solver policy, profile and wire schemas are unchanged. The requested rendered multiplayer/screenshot check did not run: automatic approval review failed because the reviewing account hit its usage limit. This is deferred validation, not a safety rejection or a browser pass.
Further micro-optimization is deferred at the user's direction. The full-load performance gate, longer/socket/database/device runs and the previous screenshot remain recorded here. The next delivery is a substantive missing core capability.
#State-comparison cost reduction — October 27
EntityStore.matches and scoped invariant views now compare schema-checked
expected values directly against numeric/mixed storage columns. This avoids
reconstructing and serializing each body component solely to test equality.
Reference leaves retain canonical comparison; no mutable storage is exposed.
Floating invariants still check every body's dependencies, membership, native
state and complete component values, including on unrelated player writes.
Active native states are validated once instead of reconstructing an extra ECS
copy. Body-frame validation also builds one identity lookup instead of repeatedly
scanning all world objects for each body.
The same final capacity fixture, machine and 30 warm-up / 30 measured ticks as the short baseline below produced:
| Simulation, ms | Rapier before | Rapier after | Owned before | Owned after |
|---|---|---|---|---|
| p95 | 217.601 | 132.287 | 680.131 | 594.267 |
| p99 | 231.661 | 136.659 | 682.868 | 595.047 |
That is approximately 39% less reference p95 / 13% less owned p95 in this short local sample. All 128 bodies remained active, all travelers stayed on the path, and all sixteen final player positions exactly match the prior run on each backend. Both still fail the <8/<16 ms target. The body batch, checkpoint, wire overhead and excluded hosted/render work remain separate; this is not a new supported-capacity claim.
Focused validation: forty-two native checks pass with no skips, including numeric, mixed/reference-row equality, missing/extra/invalid fields, row compaction, read isolation, scoped-view expiry, rejected transaction rollback, native/body mismatch on unrelated row writes, changed collider/mass/membership rejection, save/restore and sixteen-motor/128-body integration on both backends. All 240 prior state hashes remain exact in ten fixed-identity owned replay scenarios. Code/boundary/wire checks and the production build pass. Solver math/profile and wire contracts are unchanged; the full browser replay matrix was not repeated.
The rendered shared-push check exposed a separate reach mismatch: ordinary body prompts used center distance while authority required an unobstructed native reach. On the cove jetty, the first prompt could offer a push through the planks. Ordinary floating-body prompts now retain their existing distance bound and also require clear reach, matching the existing hinge behavior. Both backends reject a nearby prop behind a wall and offer it from the clear side in focused checks. The input action itself and server authority checks are unchanged. The unchanged physical-GPU Chromium journey now passes (28.9 seconds): two travelers share the push, the room owner leaves, and the remaining player retains the body state after reload, across Worker and main-thread graphics. The shared cove screenshot was visually reviewed.
The next work remains the per-traveler frame/body-state overhead and additional owned native contact/controller cost. Reference schema checks and repeated frame copies remain significant candidates; measure the remaining path before changing it. Sustained host/database/device testing follows once CPU timing makes it useful.
#Short local CPU result
Intel Core Ultra X7 358H, Linux x86-64, Node 22.23.4; thirty warm-up and thirty measured ticks per backend, with no concurrent build/test run. Both retained all 128 active bodies and all sixteen travelers stayed on the path. This one-second sample identifies a clear blocker; it is not a stable capacity certification or a replacement for the longer/named-device gate. p99 here is the maximum of thirty ticks, and body/checkpoint columns have only ten batch samples.
| Work, milliseconds | Rapier p95 | Owned p95 |
|---|---|---|
| Combined simulation tick | 217.601 | 680.131 |
| Sixteen traveler simulations | 208.866 | 501.084 |
| Three-step body batch | 13.774 | 180.786 |
| Checkpoint materialization/serialization | 12.844 | 4.375 |
| Sixteen binary motion snapshots | 8.971 | 9.458 |
| Complete measured local tick | 232.399 | 693.759 |
Combined simulation p99 was 231.661 ms reference / 682.868 ms owned, versus required p95 <8 / p99 <16 ms. The measured snapshot payload alone is about 18.5 MB/s across sixteen clients, because every motor snapshot includes the complete body frame. Those numbers are failures, not newly supported capacity.
A short reference CPU profile points primarily to syncPlayerComponents,
floatingUpdates, ECS invariant validation and repeated canonical comparisons /
copying. The owned backend also spends much more time on contact/controller
work. Next reduce these demonstrated costs while retaining invariant and replay
contracts, then address repeated frame transmission and run sustained real
socket/database load. Do not spend another long hosted trial on this CPU result.
#Focused acceptance
- 31 native checks pass; 25 database/browser-dependent cases in that selection were skipped. New checks cover authorable 128-body content, rejection of body 129, both backends with sixteen moving motors, complete JSON/binary frames, exact body restore/continuation and rejection of contact 17.
- Two isolated PostgreSQL checks pass: sixteen actual WebSockets, rejection of arrival 17, same-player replacement at full occupancy, and all 128 bodies retained/restored with sixteen contact participants. Admission uses a light world; it is not a live socket load qualification with 128 bodies.
- Code/boundary/wire checks, the production client/server/engine build and Compose configuration validation pass. No solver policy changed, so the already-passed exact replay matrix was not repeated.
- Larger-roster cargo/vehicle/NPC/recording end-to-end journeys, routed remote-host capacity, sustained live 16/128 rooms and Docker/Coolify execution remain for qualification. Container execution still requires access to a Docker daemon.
There is no new visual interface in this increment; no unrelated art screenshot is substituted for its connection, persistence and CPU evidence.
npm run bench:rooms runs an isolated native capacity trial against the production regional gateway and retained trusted runtime. It starts its own PostgreSQL cluster on a private Unix socket, packages the engine, gradually admits protocol travelers, measures occupancy stages and reconnects the admitted travelers together. It does not use an existing beta/editor database or stop their services.
Use Node 22.23.4, installed development dependencies, PostgreSQL's initdb and pg_ctl, and Linux /proc/getconf support:
npm ci
npm run bench:rooms -- --seconds 20 --rooms 2 --output test-results/room-capacity.json--seconds is the measured duration per stage, from 10 to 120. --rooms is one or two. Every stage also has a twelve-second warmup so the recent ten-bucket admission observations can settle. Default total occupancy targets are 1, 2, 4, 8 and 16 for two rooms, each configured for at most eight players. Rooms and travelers are admitted normally; the runner never disables pressure checks or raises the default RSS admission budget. Two consecutive admission refusals stop further growth and remain in the report.
The fixture keeps the production trusted Lantern House geometry, connected places and rewards. Only the initial spawn moves beside the keeper lantern, matching the existing durable-playtest fixture. The first traveler in each room takes that room's lantern before the load phase. All travelers then send small alternating lateral inputs at 30 Hz; normal autosaves exercise durable storage. There is no GPU renderer or client prediction in this protocol load generator.
Each WebSocket direction adds 75 ms of delivery delay. Timers, input acknowledgements, payload byte counts and parent scheduling gaps are observed rather than assumed. The generator tracks every input acknowledged in a batch, bounds outstanding input to sixty, rejects regressive acknowledgements and fences delayed packets from retired sockets. Successful reconnect requires the same traveler identity and inventory. Like the browser client, bots wait for the admission baseline before sending heartbeats.
#Prepared room content
Simulation.prepare(world, program?) validates, owns and freezes the authored world and compiles its content once. Its synchronous create(save?) method makes independent simulations with separate ECS stores, native physics, progress, behavior state, schedulers and event queues. Dispose each simulation independently. The prepared factory can be reused after another instance is disposed; it owns no native physics resources itself. Simulation.create retains its existing asynchronous API and delegates through a one-use factory.
Trusted persistent rooms retain one prepared factory for arrivals, saved returns and place changes. Content is scoped to that immutable room release, never reused across different releases. This removes repeated content compilation and cloning per traveler without sharing mutable simulation state. Native checkpoint restoration and live revision preparation retain their existing contracts.
#Reading the report
The JSON includes machine/runtime identity, source commit and dirty state, retained engine and immutable fixture release, admitted occupancy, sampled health, per-traveler input/acknowledgement rate, latency distributions, application payload bytes, process CPU, database transaction/tuple/WAL changes, admission refusals, reconnect results and shutdown status. The final phase is the post-reconnect measurement at the actual admitted occupancy.
The room health report now includes recent, the ten-bucket observations used for admission, alongside the longer diagnostic window. This prevents a longer rolling average from hiding the cost of the current occupancy stage. CPU sampling follows only the owned gateway/room and isolated database process trees. It includes CPU accumulated by reaped descendants, so short-lived database workers remain counted. A bounded retry rejects a sample if process identity or tree topology changes during traversal; a missing/replaced root or regressing counter marks incomplete coverage. Samples are not atomic kernel-wide snapshots. The generator's own CPU, memory and scheduling lag are separate from server measurements.
The initial trial budgets require each traveler to receive at least 28 acknowledged inputs per second, send at least 29 per second, and have p95 acknowledgement latency at most 500 ms with the added 150 ms delay. There must be no unexpected disconnects or input backpressure. Generator missed slots must remain within one percent and its scheduling-lag p95 within 10 ms. Process CPU coverage must be complete and sampled rooms must remain within their admission budgets. These are explicit trial criteria, not universal production SLOs.
measured-targetmeans those criteria held for the requested occupancy and reconnect measurement in this trial.measured-below-requested-targetmeans the admitted workload met the criteria but admission stopped below the requested total.inconclusive-or-overloadedmeans at least one measured criterion failed; inspect individual stages and generator validity before attributing the result to the host.failedmeans setup, protocol correctness, inspection or another required operation failed. Partial evidence is still written when the runner can complete cleanup.
A full-cap extra arrival must be refused. All report interpretation must keep the actual per-room occupancy: sixteen regional travelers do not by themselves prove eight travelers in every room. The room cap is a hard admission ceiling; it is not automatically a supported capacity claim.
#Recorded local result · 2026-10-25
The final twenty-second-per-stage trial passed all criteria with eight travelers in each of two rooms. All sixteen reconnected in about 1.03 seconds with unchanged identity/inventory, and an extra arrival was refused. Highest sampled room RSS was 487.2 MiB against the unchanged 512 MiB admission budget. Every stage sustained at least 29.99 acknowledged inputs/second per traveler; the highest measured acknowledgement p95 was 314.43 ms, including the added 150 ms delay.
The baseline before prepared sharing admitted eleven travelers (3 + 8) and encountered memory pressure. An intermediate prepared trial exposed a bot that sent heartbeat before admission; another exposed incomplete CPU accounting for an exited database worker. Focused reproduction tests and corrected full trials are retained in the acceptance record. A single local comparison does not establish a universal memory reduction or production capacity.
#Measurement limits
Application-byte counts exclude WebSocket framing, TLS, HTTP assets and operator requests. CPU results include the measured service's observation overhead; database statistics include the benchmark's read queries and normal lease maintenance. PostgreSQL cumulative statistics can lag their latest activity. These are local native measurements on the recorded machine, not container-host or cloud cost estimates.
The fixture exercises movement, autosaves, one durable pickup per room and simultaneous reconnects. It does not qualify dense rendered crowds, arbitrary content, cross-place travel load, adversarial traffic, long-soak stability, WAN loss/congestion, mobile devices or production deployment. Admission refusals and slow-generator observations remain visible and must not be omitted when selecting a public limit.
The runner drains its owned gateway/children and stops the temporary PostgreSQL cluster, then removes the temporary build/database directory. SIGINT/SIGTERM cancellation requests orderly cleanup. If PostgreSQL shutdown cannot be confirmed, the launcher preserves its temporary directory and reports its location instead of deleting a potentially live database. Abrupt OS termination cannot guarantee a final report or full temporary-directory cleanup.
Recorded findings belong in acceptance evidence. The production and device gates in milestones 0–18 remain open; a successful local trial is one input to those gates.