@series-inc/rundot-syncplay 6.0.0-rc.39 → 6.0.0-rc.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/MIGRATING-RC39-TO-RC40.md +41 -0
  2. package/README.md +7 -0
  3. package/core/src/physics/physics-profile-wasm-module.generated.mjs +1 -1
  4. package/core/src/physics/physics-wasm-module.generated.mjs +1 -1
  5. package/core/src/physics2d/physics2d-wasm-module.generated.mjs +1 -1
  6. package/dist/authority-room.d.ts +209 -3
  7. package/dist/authority-room.js +941 -49
  8. package/dist/creator.d.ts +2 -0
  9. package/dist/creator.js +1 -0
  10. package/dist/extensions/hub/airlock.d.ts +153 -0
  11. package/dist/extensions/hub/airlock.js +500 -0
  12. package/dist/extensions/hub/contracts.d.ts +369 -0
  13. package/dist/extensions/hub/contracts.js +106 -0
  14. package/dist/extensions/hub/exfil.d.ts +54 -0
  15. package/dist/extensions/hub/exfil.js +110 -0
  16. package/dist/extensions/hub/handoff.d.ts +84 -0
  17. package/dist/extensions/hub/handoff.js +161 -0
  18. package/dist/extensions/hub/index.d.ts +12 -0
  19. package/dist/extensions/hub/index.js +12 -0
  20. package/dist/extensions/hub/inventory.d.ts +86 -0
  21. package/dist/extensions/hub/inventory.js +288 -0
  22. package/dist/extensions/hub/party.d.ts +43 -0
  23. package/dist/extensions/hub/party.js +58 -0
  24. package/dist/extensions/hub/plugin.d.ts +23 -0
  25. package/dist/extensions/hub/plugin.js +37 -0
  26. package/dist/extensions/hub/settlement.d.ts +42 -0
  27. package/dist/extensions/hub/settlement.js +121 -0
  28. package/dist/index.d.ts +2 -2
  29. package/dist/input-authority.d.ts +13 -0
  30. package/dist/input-authority.js +129 -28
  31. package/dist/modules/character-3d.js +1 -1
  32. package/dist/modules/extraction.d.ts +148 -0
  33. package/dist/modules/extraction.js +344 -0
  34. package/dist/movement3d-common.d.ts +57 -0
  35. package/dist/movement3d-common.js +218 -0
  36. package/dist/movement3d-revision3.d.ts +2 -0
  37. package/dist/movement3d-revision3.js +844 -0
  38. package/dist/movement3d.d.ts +2 -2
  39. package/dist/movement3d.js +7 -227
  40. package/dist/networked-client.d.ts +20 -0
  41. package/dist/networked-client.js +274 -12
  42. package/dist/plugins/index.d.ts +27 -0
  43. package/dist/plugins/index.js +114 -0
  44. package/dist/plugins/types.d.ts +34 -0
  45. package/dist/plugins/types.js +5 -0
  46. package/dist/secret-client.d.ts +1 -1
  47. package/dist/secret-client.js +1 -1
  48. package/dist/session-snapshot.d.ts +1 -0
  49. package/dist/session-snapshot.js +1 -0
  50. package/dist/session-wire.d.ts +24 -1
  51. package/dist/session-wire.js +59 -2
  52. package/extensions/hub/package.json +1 -0
  53. package/hub/package.json +1 -0
  54. package/package.json +46 -5
  55. package/plugins/package.json +1 -0
@@ -0,0 +1,41 @@
1
+ # Character landing repair
2
+
3
+ Use `behaviorRevision: 3` to recover from prop edge intersections and to walk on tilted dynamic props.
4
+ Revision 1 remains the default.
5
+ Revisions 1 and 2 keep their previous results.
6
+
7
+ ```ts
8
+ const result = stepDeterministicKcc3D(body, input, bounds, {
9
+ frame,
10
+ physics: world,
11
+ behaviorRevision: 3,
12
+ groundOnStaticBodies: true,
13
+ standOnDynamicBodies: true,
14
+ });
15
+ ```
16
+
17
+ Revision 3 extends revision 1 movement.
18
+ It recovers the stored capsule before it applies input.
19
+ It adds capsule clearance when it selects dynamic ground.
20
+ Each recovery correction stops before another solid.
21
+ Recovery obeys ignored bodies, collision layers, sensors, and collision filters.
22
+ Ground and recovery filters receive the native contact normal.
23
+
24
+ Recovery has a limit of four passes.
25
+ It does not guarantee escape from deep containment or a space that is too small.
26
+ It does not add revision 2 support state or portal movement.
27
+ Keep revision 2 for games that use those features.
28
+
29
+ Revision 3 returns `hasChecksum: true` and `checksumMode: 'required'` when the mode is omitted.
30
+ Use `checksumMode: 'disabled'` to omit the checksum.
31
+ The character module also accepts revision 3 in its movement config.
32
+ Its revision 1 default and revision 2 physics preset remain unchanged.
33
+
34
+ The new revision changes queries, positions, and checksums.
35
+ Pin the SDK version and controller revision in the game identity.
36
+ All clients and the authoritative server must use the same identity.
37
+ Do not reuse a revision 1 replay as a revision 3 replay.
38
+
39
+ The release gates use native WASM.
40
+ They cover flat and tilted boxes, cooked voxels, hollow roofs, edge drops, corner drops, walls, ceilings, filters, and checkpoint replay.
41
+ The frozen revision 1 source and result corpus remain unchanged.
package/README.md CHANGED
@@ -558,6 +558,13 @@ the bits of a finite input. It rejects NaN and infinity.
558
558
  This change can produce TypeScript errors in existing games. Update those call
559
559
  sites when you upgrade the SDK. Revision 1 helper behavior remains selected by
560
560
  default. The revision 1 tests compare complete results with the previous RC.
561
+
562
+ Set `behaviorRevision: 3` to recover from contact penetration after a landing.
563
+ This revision includes slope clearance for dynamic supports. It keeps the
564
+ revision 1 result shape and uses required checksums by default. Revision 1
565
+ remains the default. Revision 2 keeps its support state and portal movement.
566
+ See [the rc.40 migration notes](MIGRATING-RC39-TO-RC40.md).
567
+
561
568
  New helper behavior requires an explicit revision selection.
562
569
 
563
570
  ### Shape-preserving body changes
@@ -23,7 +23,7 @@ export const physicsProfileWasmMetadata = Object.freeze({
23
23
  "sourceHash": "809bedd50c7a554eacbded424f8c8779850747183b0d5d29a754ad613b8bf8d0",
24
24
  "wasmSha256": "d877cf4ace481e14ea2730b3a839008ecf8326b80784c40a5f447f0294dc2e41",
25
25
  "artifact": "profile",
26
- "packageVersion": "6.0.0-rc.39",
26
+ "packageVersion": "6.0.0-rc.40",
27
27
  "compilerFlags": [
28
28
  "-std=c++20",
29
29
  "-O3",
@@ -23,7 +23,7 @@ export const physicsWasmMetadata = Object.freeze({
23
23
  "sourceHash": "dffa6d2a7e3e96815bc8e6708fd1a1544833aefc7d96d36291406a94177cc591",
24
24
  "wasmSha256": "aaa6b82830fbc7613e5b8110b3e09ab5b3a2b9339a544a935f88aa20b02125aa",
25
25
  "artifact": "production",
26
- "packageVersion": "6.0.0-rc.39",
26
+ "packageVersion": "6.0.0-rc.40",
27
27
  "compilerFlags": [
28
28
  "-std=c++20",
29
29
  "-O3",
@@ -50,7 +50,7 @@ export const physics2dWasmMetadata = Object.freeze({
50
50
  "sourceHash": "faa090a03a6fddaf79c704fb1d76f810955ff35597449c8cec9541a75905a37e",
51
51
  "wasmSha256": "49cb90541deed40f5fff1015bb578e13ec9a793d51931c1227e3e5a11bbfa3f7",
52
52
  "artifact": "production",
53
- "packageVersion": "6.0.0-rc.39",
53
+ "packageVersion": "6.0.0-rc.40",
54
54
  "compilerFlags": [
55
55
  "-std=c++20",
56
56
  "-O3",
@@ -1,5 +1,6 @@
1
1
  import { type KinetixRuntimeIdentity } from '@series-inc/rundot-syncplay/core/runtime';
2
- import { type ConfirmedInputFrame, type EncodedInput } from './input-authority.js';
2
+ import { type ConfirmedInputFrame, type EncodedInput, type AuthoritySlotPolicy, type InputSubmissionResult } from './input-authority.js';
3
+ export type { AuthoritySlotPolicy };
3
4
  import type { SyncplayMatchLog } from './match-log.js';
4
5
  import { type SyncplaySecretAuthoritySnapshot } from './secret-authority.js';
5
6
  import type { SyncplaySecretAuthorityCrypto } from './secret-crypto.js';
@@ -87,6 +88,9 @@ export interface DeterministicAuthorityRoomConfig {
87
88
  * `historyRetentionTicks` — a match longer than that loses tick 0 and stops
88
89
  * being log-verifiable (matchLog() returns undefined; fail loud, never lie).
89
90
  */
91
+ readonly raidMode?: boolean;
92
+ readonly requiredCapabilities?: readonly string[];
93
+ readonly authorityEpoch?: number;
90
94
  readonly preserveFullHistory?: boolean;
91
95
  /**
92
96
  * Catch-up telemetry (M8): fired whenever the room serves a player state
@@ -116,6 +120,8 @@ export interface DeterministicAuthorityRoomConfig {
116
120
  readonly reconfigurePolicy?: 'creator' | 'any' | 'none';
117
121
  /** The creator's seat, required to enforce `reconfigurePolicy: 'creator'`. */
118
122
  readonly creatorSlot?: number;
123
+ readonly eventKeyId?: string;
124
+ readonly slotPolicies?: readonly AuthoritySlotPolicy[];
119
125
  }
120
126
  export interface DeterministicAuthorityRoomReconfiguration {
121
127
  readonly sessionConfigBytes: Uint8Array;
@@ -124,6 +130,137 @@ export interface DeterministicAuthorityRoomReconfiguration {
124
130
  /** When supplied, must equal the live identity; a mismatch throws. */
125
131
  readonly runtimeIdentity?: KinetixRuntimeIdentity;
126
132
  }
133
+ export interface StagedSquadSeat {
134
+ readonly playerId: string;
135
+ readonly slot: number;
136
+ readonly manifestHash: string;
137
+ }
138
+ export interface StagedSquadAdmission {
139
+ readonly admissionId: string;
140
+ readonly handoffId: string;
141
+ readonly raidId: string;
142
+ readonly authorityEpoch: number;
143
+ readonly membershipEpoch: number;
144
+ readonly seats: readonly StagedSquadSeat[];
145
+ readonly hydrateThroughTick: number;
146
+ readonly runtimeDigest: string;
147
+ readonly preparedPlayerIds?: readonly string[];
148
+ }
149
+ export interface HydrationAck {
150
+ readonly profileId?: string;
151
+ readonly playerId?: string;
152
+ readonly handoffId: string;
153
+ readonly connectionId?: string;
154
+ readonly generation: number;
155
+ readonly membershipEpoch?: number;
156
+ readonly snapshotTick: number;
157
+ readonly appliedThroughTick: number;
158
+ readonly stateChecksum: string;
159
+ readonly manifestHash: string;
160
+ readonly runtimeDigest?: string;
161
+ }
162
+ export type StageResult = {
163
+ readonly ok: true;
164
+ readonly admission: StagedSquadAdmission;
165
+ } | {
166
+ readonly ok: false;
167
+ readonly reason: 'slot-collision' | 'player-collision' | 'already-staged' | 'stale-epoch' | 'epoch-mismatch' | 'capacity-exceeded' | 'invalid-admission';
168
+ };
169
+ export type PrepareResult = {
170
+ readonly ok: true;
171
+ readonly allPrepared: boolean;
172
+ readonly preparedPlayerIds: readonly string[];
173
+ } | {
174
+ readonly ok: false;
175
+ readonly reason: 'unknown-player' | 'not-staged' | 'not-connected' | 'generation-mismatch' | 'handoff-mismatch' | 'checksum-mismatch' | 'digest-mismatch' | 'manifest-mismatch' | 'tick-mismatch' | 'invalid-ack';
176
+ };
177
+ export type ScheduleResult = {
178
+ readonly ok: true;
179
+ readonly effectiveTick: number;
180
+ } | {
181
+ readonly ok: false;
182
+ readonly reason: 'unknown-admission' | 'not-all-prepared' | 'past-tick' | 'collision' | 'invalid-tick' | 'already-scheduled';
183
+ };
184
+ export type AbortResult = {
185
+ readonly ok: true;
186
+ } | {
187
+ readonly ok: false;
188
+ readonly reason: 'unknown-admission' | 'already-applied';
189
+ };
190
+ export interface CommittedAdmissionDecision {
191
+ readonly admissionId: string;
192
+ readonly handoffId: string;
193
+ readonly raidId: string;
194
+ readonly authorityEpoch: number;
195
+ readonly escrowState: 'committed';
196
+ readonly seats: readonly {
197
+ readonly playerId: string;
198
+ readonly slot: number;
199
+ readonly manifestHash: string;
200
+ }[];
201
+ }
202
+ export interface AdmissionDecisionSource {
203
+ read(admissionId: string): Promise<CommittedAdmissionDecision>;
204
+ }
205
+ export type AuthorityEvent = {
206
+ readonly kind: 'airdrop';
207
+ readonly grantId: string;
208
+ readonly spawnPointId: string;
209
+ readonly tableDigest: string;
210
+ } | {
211
+ readonly kind: 'beacon';
212
+ readonly beaconId: string;
213
+ readonly locationId: string;
214
+ readonly expiresAtTick: number;
215
+ } | {
216
+ readonly kind: 'raidDeadline';
217
+ readonly cutoffTick: number;
218
+ };
219
+ export interface AuthorityEventRequest {
220
+ readonly v: 1;
221
+ readonly eventId: string;
222
+ readonly raidId: string;
223
+ readonly authorityEpoch: number;
224
+ readonly notBeforeTick: number;
225
+ readonly expiresAtTick: number;
226
+ readonly event: AuthorityEvent;
227
+ }
228
+ export interface OrderedAuthorityEvent {
229
+ readonly v: 1;
230
+ readonly origin: 'server';
231
+ readonly kind: 'platform.event';
232
+ readonly eventId: string;
233
+ readonly authorityEpoch: number;
234
+ readonly authoritySequence: number;
235
+ readonly tick: number;
236
+ readonly payload: AuthorityEvent;
237
+ readonly signature: string;
238
+ readonly keyId: string;
239
+ }
240
+ export interface BotInputSubmission {
241
+ readonly v: 1;
242
+ readonly raidId: string;
243
+ readonly authorityEpoch: number;
244
+ readonly botLeaseId: string;
245
+ readonly slot: number;
246
+ readonly tick: number;
247
+ readonly sequence: number;
248
+ readonly input: EncodedInput;
249
+ }
250
+ export type EventQueueResult = {
251
+ readonly status: 'accepted';
252
+ readonly eventId: string;
253
+ readonly assignedTick: number;
254
+ readonly authoritySequence: number;
255
+ } | {
256
+ readonly status: 'duplicate';
257
+ readonly eventId: string;
258
+ readonly assignedTick: number;
259
+ readonly authoritySequence: number;
260
+ } | {
261
+ readonly status: 'rejected';
262
+ readonly reason: string;
263
+ };
127
264
  export interface DeterministicAuthorityRoomRestoreState {
128
265
  readonly now: number;
129
266
  readonly confirmedThrough: number;
@@ -138,6 +275,29 @@ export interface DeterministicAuthorityRoomRestoreState {
138
275
  readonly secretState?: SyncplaySecretAuthoritySnapshot;
139
276
  /** Session generation at persist time; a restored room re-greets with it. Default 0. */
140
277
  readonly generation?: number;
278
+ readonly membershipEpoch?: number;
279
+ readonly memberships?: readonly TickMembership[];
280
+ readonly bots?: readonly string[];
281
+ readonly pendingPlayers?: readonly string[];
282
+ readonly disconnectedPlayers?: readonly string[];
283
+ readonly stagedAdmissions?: readonly StagedSquadAdmission[];
284
+ readonly scheduledAdmissions?: readonly {
285
+ readonly admissionId: string;
286
+ readonly effectiveTick: number;
287
+ }[];
288
+ readonly appliedAdmissionIds?: readonly string[];
289
+ readonly enqueuedEvents?: readonly {
290
+ readonly request: AuthorityEventRequest;
291
+ readonly assignedTick: number;
292
+ readonly authoritySequence: number;
293
+ }[];
294
+ readonly nextAuthorityEventSequence?: number;
295
+ readonly evacuatedSlots?: readonly number[];
296
+ readonly evacuatedPlayerIds?: readonly string[];
297
+ readonly scheduledEvacuations?: readonly {
298
+ readonly targetTick: number;
299
+ readonly slot: number;
300
+ }[];
141
301
  }
142
302
  export interface AuthorityRoomCatchUpEvent {
143
303
  readonly playerId: string;
@@ -192,7 +352,7 @@ export interface AuthorityRoomNetcodeStats {
192
352
  export interface AuthorityRoomJoinResult {
193
353
  readonly accepted: boolean;
194
354
  readonly slot?: number;
195
- readonly reason?: 'room-full' | 'already-joined' | 'history-unavailable';
355
+ readonly reason?: 'room-full' | 'already-joined' | 'history-unavailable' | 'incompatible-protocol';
196
356
  /** True when this join re-attached a previously-seen playerId to its original slot. */
197
357
  readonly rejoined?: boolean;
198
358
  }
@@ -220,12 +380,37 @@ export interface AuthorityRoomSnapshot {
220
380
  /** Most recent snapshot rejection reason, if any snapshot attempt was rejected. */
221
381
  readonly lastSnapshotRejection?: string;
222
382
  /** Details of the pending snapshot transfer currently in-flight, if any. */
383
+ readonly membershipEpoch?: number;
384
+ readonly memberships?: readonly TickMembership[];
385
+ readonly bots?: readonly string[];
386
+ readonly pendingPlayers?: readonly string[];
387
+ readonly disconnectedPlayers?: readonly string[];
223
388
  readonly pendingSnapshotTransfer?: {
224
389
  readonly donorId: string;
225
390
  readonly minFrame: number;
226
391
  readonly startedAt: number;
227
392
  readonly elapsedTicks: number;
393
+ readonly targetTick?: number;
394
+ readonly frozenReporterSlots?: readonly number[];
228
395
  };
396
+ readonly stagedAdmissions?: readonly StagedSquadAdmission[];
397
+ readonly scheduledAdmissions?: readonly {
398
+ readonly admissionId: string;
399
+ readonly effectiveTick: number;
400
+ }[];
401
+ readonly appliedAdmissionIds?: readonly string[];
402
+ readonly evacuatedSlots?: readonly number[];
403
+ readonly evacuatedPlayerIds?: readonly string[];
404
+ readonly scheduledEvacuations?: readonly {
405
+ readonly targetTick: number;
406
+ readonly slot: number;
407
+ }[];
408
+ }
409
+ export interface TickMembership {
410
+ readonly tick: number;
411
+ readonly membershipEpoch: number;
412
+ readonly occupiedSlots: readonly number[];
413
+ readonly humanReporterSlots: readonly number[];
229
414
  }
230
415
  export interface AuthorityRoomStoredSnapshotMeta {
231
416
  readonly tick: number;
@@ -237,7 +422,16 @@ export interface AuthorityRoomStoredSnapshotMeta {
237
422
  export interface DeterministicAuthorityRoom {
238
423
  readonly confirmedThrough: number;
239
424
  readonly playerCount: number;
240
- onPlayerJoin(playerId: string): AuthorityRoomJoinResult;
425
+ onPlayerJoin(playerId: string, options?: {
426
+ readonly isPending?: boolean;
427
+ readonly capabilities?: readonly string[];
428
+ }): AuthorityRoomJoinResult;
429
+ setPlayerPending(playerId: string, pending: boolean): void;
430
+ stageSquad(admission: StagedSquadAdmission): StageResult;
431
+ markPrepared(playerId: string, acknowledgement: HydrationAck): PrepareResult;
432
+ scheduleAdmission(admissionId: string, effectiveTick: number): ScheduleResult;
433
+ abortStagedAdmission(admissionId: string): AbortResult;
434
+ isAdmissionActivated?(admissionId: string): boolean;
241
435
  /** Seat a read-only spectator (G11): role 'spectator', no slot, full catch-up + live follow. */
242
436
  onSpectatorJoin(playerId: string): boolean;
243
437
  onPlayerMessage(playerId: string, envelope: string): void;
@@ -258,6 +452,8 @@ export interface DeterministicAuthorityRoom {
258
452
  * with no slot (spectator, unknown id).
259
453
  */
260
454
  onPlayerDisconnected(playerId: string): void;
455
+ membershipAt(tick: number): TickMembership;
456
+ registerBot(playerId: string): void;
261
457
  /** Advance the authority by one server tick and broadcast newly-confirmed frames. */
262
458
  onTick(): void;
263
459
  /**
@@ -297,5 +493,15 @@ export interface DeterministicAuthorityRoom {
297
493
  matchLog(): SyncplayMatchLog | undefined;
298
494
  /** Server-only secret state. Never include this value in public snapshots or match logs. */
299
495
  secretSnapshot(): SyncplaySecretAuthoritySnapshot | undefined;
496
+ enqueueAuthorityEvent(validated: AuthorityEventRequest, options?: {
497
+ readonly signature?: string;
498
+ readonly keyId?: string;
499
+ }): EventQueueResult;
500
+ receiveVirtualInput(validated: BotInputSubmission): InputSubmissionResult;
501
+ activateBotSlot(slot: number, botId?: string): void;
502
+ deactivateBotSlot(slot: number): void;
503
+ evacuateParticipant(slot: number, scheduledTick?: number): {
504
+ scheduledTick: number;
505
+ };
300
506
  }
301
507
  export declare function createDeterministicAuthorityRoom(transport: AuthorityRoomTransport, config: DeterministicAuthorityRoomConfig): DeterministicAuthorityRoom;