@driftengine/script 3.61.0

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 (61) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +81 -0
  4. package/dist/bindings/ai.d.ts +66 -0
  5. package/dist/bindings/ai.js +173 -0
  6. package/dist/bindings/animation.d.ts +13 -0
  7. package/dist/bindings/animation.js +177 -0
  8. package/dist/bindings/audio.d.ts +56 -0
  9. package/dist/bindings/audio.js +126 -0
  10. package/dist/bindings/behavior.d.ts +36 -0
  11. package/dist/bindings/behavior.js +101 -0
  12. package/dist/bindings/chemistry.d.ts +72 -0
  13. package/dist/bindings/chemistry.js +253 -0
  14. package/dist/bindings/core.d.ts +172 -0
  15. package/dist/bindings/core.js +814 -0
  16. package/dist/bindings/editor.d.ts +53 -0
  17. package/dist/bindings/editor.js +248 -0
  18. package/dist/bindings/entities.d.ts +73 -0
  19. package/dist/bindings/entities.js +339 -0
  20. package/dist/bindings/interface.d.ts +55 -0
  21. package/dist/bindings/interface.js +200 -0
  22. package/dist/bindings/navigation.d.ts +36 -0
  23. package/dist/bindings/navigation.js +255 -0
  24. package/dist/bindings/network.d.ts +51 -0
  25. package/dist/bindings/network.js +96 -0
  26. package/dist/bindings/render.d.ts +71 -0
  27. package/dist/bindings/render.js +143 -0
  28. package/dist/bindings/rollback.d.ts +41 -0
  29. package/dist/bindings/rollback.js +74 -0
  30. package/dist/bindings/sprites.d.ts +53 -0
  31. package/dist/bindings/sprites.js +186 -0
  32. package/dist/bindings/terrain.d.ts +54 -0
  33. package/dist/bindings/terrain.js +138 -0
  34. package/dist/bindings/xr.d.ts +56 -0
  35. package/dist/bindings/xr.js +155 -0
  36. package/dist/entityHost.d.ts +80 -0
  37. package/dist/entityHost.js +159 -0
  38. package/dist/host.d.ts +153 -0
  39. package/dist/host.js +233 -0
  40. package/dist/index.d.ts +21 -0
  41. package/dist/index.js +18 -0
  42. package/package.json +68 -0
  43. package/src/bindings/ai.ts +306 -0
  44. package/src/bindings/animation.ts +346 -0
  45. package/src/bindings/audio.ts +185 -0
  46. package/src/bindings/behavior.ts +181 -0
  47. package/src/bindings/chemistry.ts +548 -0
  48. package/src/bindings/core.ts +1604 -0
  49. package/src/bindings/editor.ts +393 -0
  50. package/src/bindings/entities.ts +550 -0
  51. package/src/bindings/interface.ts +300 -0
  52. package/src/bindings/navigation.ts +367 -0
  53. package/src/bindings/network.ts +178 -0
  54. package/src/bindings/render.ts +191 -0
  55. package/src/bindings/rollback.ts +110 -0
  56. package/src/bindings/sprites.ts +310 -0
  57. package/src/bindings/terrain.ts +180 -0
  58. package/src/bindings/xr.ts +272 -0
  59. package/src/entityHost.ts +219 -0
  60. package/src/host.ts +465 -0
  61. package/src/index.ts +41 -0
@@ -0,0 +1,178 @@
1
+ import type { ScriptSession } from '@driftengine/network';
2
+ import type { CapabilityDefinition, Effect, OpaqueType } from 'driftscript';
3
+ import { defineCapability } from 'driftscript';
4
+
5
+ export const NETWORK_MODULE = 'drift/network';
6
+
7
+ /**
8
+ * `drift/network` — what a script can see of a session, and the scalars it can publish.
9
+ *
10
+ * **The linker has refused this module by name since the language shipped**, saying it waits on
11
+ * Track J. The corpus carries `NetworkReplicatedActor.drs`, written *before* any provider existed
12
+ * so that "the networking semantics are written down before networking couples itself to the state
13
+ * and task design". Its names are honoured here — `replicate` and `authority` are both capabilities
14
+ * — and its one illustrative call is not: it reads `network.replicate(character.x, character.y)`, with no
15
+ * session, because when it was written there was no handle to take. Every capability takes the
16
+ * thing it is about, the way every other binding in this directory does.
17
+ *
18
+ * ---
19
+ *
20
+ * ## The effect split, which the language asked this track to make
21
+ *
22
+ * `capability.ts` puts `network.read` **inside** `DETERMINISTIC_EFFECTS` and leaves `network.write`
23
+ * outside, with a note that the exclusion is *"a deferral rather than a judgement: each belongs to a
24
+ * track that has not shipped, and the track that builds one is the one that can say whether its
25
+ * writes are the simulation or a consequence of it."* This is that track, so here is the answer.
26
+ *
27
+ * **`network.write` stays outside, and the rollback loop is the reason.** The tempting argument for
28
+ * admitting it is that publishing a value changes no simulation state, so a replay would publish
29
+ * the same thing and nothing would drift. That argument is exactly backwards once a rewind exists:
30
+ * **a `@deterministic` function is precisely the kind that gets re-run**, several times, over ticks
31
+ * that have already happened. A send is not idempotent — the replay would publish the same value
32
+ * again for a tick a peer already has, and a correction that replayed twelve ticks would put twelve
33
+ * duplicate messages on the wire. Deferring the effect was right and the deferral now has a reason
34
+ * behind it rather than an absence.
35
+ *
36
+ * **`network.read` is kept for the two things that genuinely do not vary**: which participant this
37
+ * is, and whether this peer is the authority. Both are fixed when the session is created, so a
38
+ * `@deterministic` system may branch on them and replay identically.
39
+ *
40
+ * **Everything else a session knows is `nondeterministic`, and that is not pedantry.** The confirmed
41
+ * watermark, the participant count and whether the session has halted all move with packet timing:
42
+ * a system that branched on "is tick 400 confirmed" would take one path live and the other on
43
+ * replay, which is the class of bug rollback netcode is famous for. `bindings/ui.ts` made the same
44
+ * call for `hovered` and `pressed`, and this file's version of that sentence is: an effect that
45
+ * type-checks can still be a lie.
46
+ *
47
+ * ## Slots rather than field names
48
+ *
49
+ * `replicate(session, slot, value)` writes into a numbered table. A name would need a string map on
50
+ * a per-tick path and a miss would need an optional to answer with, which the language does not
51
+ * have. A slot out of range does nothing on write and answers zero on read, so both are total.
52
+ */
53
+ export const NETWORK_TYPES: readonly OpaqueType[] = [
54
+ {
55
+ module: NETWORK_MODULE,
56
+ name: 'Session',
57
+ doc: 'A networking session: who this peer is, what it can see of the others, and the scalars it publishes.',
58
+ },
59
+ ];
60
+
61
+ const define = (
62
+ name: string,
63
+ params: readonly { name: string; type: string }[],
64
+ returns: string,
65
+ effects: readonly Effect[],
66
+ deterministic: boolean,
67
+ doc: string,
68
+ ): CapabilityDefinition =>
69
+ defineCapability({
70
+ module: NETWORK_MODULE,
71
+ name,
72
+ signature: `fn(${params.map((p) => `${p.name}: ${p.type}`).join(', ')}) -> ${returns}`,
73
+ params: [...params],
74
+ returns,
75
+ effects: [...effects],
76
+ deterministic,
77
+ doc,
78
+ implementation: `${NETWORK_MODULE}.${name}`,
79
+ });
80
+
81
+ const ON = [{ name: 'session', type: 'Session' }] as const;
82
+
83
+ export const NETWORK_CAPABILITIES: readonly CapabilityDefinition[] = [
84
+ /* The two that do not move. See the header for why only these two. */
85
+ define(
86
+ 'self',
87
+ ON,
88
+ 'i32',
89
+ ['network.read'],
90
+ true,
91
+ 'Which participant this peer is. Fixed for the life of the session, which is why a deterministic system may read it.',
92
+ ),
93
+ define(
94
+ 'authority',
95
+ ON,
96
+ 'bool',
97
+ ['network.read'],
98
+ true,
99
+ 'Whether this peer’s world is the authoritative one. Fixed when the session is created.',
100
+ ),
101
+
102
+ /* Everything that moves with packet timing. */
103
+ define(
104
+ 'participants',
105
+ ON,
106
+ 'i32',
107
+ ['nondeterministic'],
108
+ false,
109
+ 'How many participants the session holds. Nondeterministic because somebody may join or leave between two runs of the same recording.',
110
+ ),
111
+ define(
112
+ 'confirmed',
113
+ ON,
114
+ 'i32',
115
+ ['nondeterministic'],
116
+ false,
117
+ 'The highest tick every participant’s input has arrived for, or -1. Moves with packet timing, so a deterministic system may not branch on it.',
118
+ ),
119
+ define(
120
+ 'halted',
121
+ ON,
122
+ 'bool',
123
+ ['nondeterministic'],
124
+ false,
125
+ 'Whether the session has stopped, because an input arrived too late to apply or two peers computed different worlds.',
126
+ ),
127
+ define(
128
+ 'haltReason',
129
+ ON,
130
+ 'String',
131
+ ['nondeterministic'],
132
+ false,
133
+ 'Why it halted, as a sentence, or an empty string. Names the tick a divergence began at.',
134
+ ),
135
+ define(
136
+ 'slots',
137
+ ON,
138
+ 'i32',
139
+ ['nondeterministic'],
140
+ false,
141
+ 'How many replicated scalars each participant has. A consumer sizes this when the session is made.',
142
+ ),
143
+ define(
144
+ 'replicated',
145
+ [...ON, { name: 'participant', type: 'i32' }, { name: 'slot', type: 'i32' }],
146
+ 'f32',
147
+ ['nondeterministic'],
148
+ false,
149
+ 'Read a participant’s published scalar. An index out of range answers zero rather than failing, because a frame loop has nothing to do with a refusal.',
150
+ ),
151
+
152
+ /* And the one write. */
153
+ define(
154
+ 'replicate',
155
+ [...ON, { name: 'slot', type: 'i32' }, { name: 'value', type: 'f32' }],
156
+ 'void',
157
+ ['network.write'],
158
+ false,
159
+ 'Publish a value in one of this peer’s slots. Not callable from a deterministic system: a replay re-runs one and a send is not idempotent.',
160
+ ),
161
+ ];
162
+
163
+ export function networkImplementation(): Record<string, unknown> {
164
+ return {
165
+ self: (session: ScriptSession) => session.self,
166
+ authority: (session: ScriptSession) => session.authority,
167
+ participants: (session: ScriptSession) => session.participants,
168
+ confirmed: (session: ScriptSession) => session.confirmed,
169
+ halted: (session: ScriptSession) => session.halted,
170
+ haltReason: (session: ScriptSession) => session.haltReason,
171
+ slots: (session: ScriptSession) => session.slots,
172
+ replicated: (session: ScriptSession, participant: number, slot: number) =>
173
+ session.replicated(participant, slot),
174
+ replicate: (session: ScriptSession, slot: number, value: number) => {
175
+ session.replicate(slot, value);
176
+ },
177
+ };
178
+ }
@@ -0,0 +1,191 @@
1
+ import type { RendererApi } from '@driftengine/core';
2
+ import { type CapabilityDefinition, type OpaqueType, defineCapability } from 'driftscript';
3
+
4
+ export const RENDER_MODULE = 'drift/render';
5
+
6
+ /**
7
+ * `drift/render` — how the finished image is presented, for the frame about to be drawn.
8
+ *
9
+ * **The linker refused this module by name from the day the language shipped until now**, and the
10
+ * roadmap slot it was waiting on was Track D. Track D completed on 2026-09-03 with refraction, so
11
+ * the wait ended five releases before anything noticed — `docs/CAPABILITIES.md` still mapped this
12
+ * module to a track that had closed, while `host.test.ts` had already been corrected to say it
13
+ * waits on a row of its own. Two documents, one right.
14
+ *
15
+ * ## What this module is, which is a smaller thing than "the renderer"
16
+ *
17
+ * `RenderQuality` has fifty fields and **none of them are here.** The engine already draws
18
+ * the line this module needs, in `renderQuality.ts`'s own words about depth of field:
19
+ *
20
+ * > A ceiling rather than the effect [...] this says how far a defocused point may spread and
21
+ * > therefore what the pass may cost, and `setDepthOfField` says where this frame's lens is focused
22
+ * > and how much of the ceiling it takes. A camera focuses on a subject; a quality profile decides
23
+ * > how expensive that is allowed to be.
24
+ *
25
+ * **A quality profile is the application's and a dial is the game's.** A profile is chosen once,
26
+ * from what the device can afford, and is clamped by `capabilityClamp` against what the adapter
27
+ * actually reports; a script that wrote to one would be overruling a decision made about hardware
28
+ * it cannot see, and two machines running the same script would draw different pictures for
29
+ * reasons the script did not choose. So this module binds the seven dials and none of the ceilings.
30
+ *
31
+ * ## Writes only, and the reason is the same one
32
+ *
33
+ * There is no `render.bloomOf` here, and nothing reads a dial back. Every one of these is **clamped
34
+ * against the ceiling the host set** — `setBloom` does nothing at all when `bloom` is 0, because
35
+ * the chain is never built — so what a read would answer is a fact about the machine wearing the
36
+ * costume of a fact about the frame. The language wrote this argument once already, for
37
+ * `network.read` and a confirmed-input watermark: a host registering a machine-dependent number
38
+ * under a deterministic effect hands a `@deterministic` system a value that differs between a run
39
+ * and its replay. A script sets a dial; what the device did with it is the device's business.
40
+ *
41
+ * ## The effect, which needed no language release
42
+ *
43
+ * **`scene.write`, and it is a description rather than a borrowing.** `capability.ts` puts
44
+ * `scene.write` outside `DETERMINISTIC_EFFECTS` and says exactly why: *"a `SceneNode` is what
45
+ * draws, and `ARCHITECTURE.md` forbids render code mutating simulation state, so moving a node is
46
+ * a change to the view."* Every capability here is a change to the view and nothing else — the
47
+ * renderers' own comments call these *"a renderer parameter rather than a scene one: it describes
48
+ * how the finished image is presented, not anything in the world, which is why nothing about the
49
+ * world has to know it exists."* Two files arriving at the same sentence from opposite ends is the
50
+ * argument.
51
+ *
52
+ * So no capability here is deterministic, and that is the property rather than a limitation: a
53
+ * `@deterministic` system may not dim the screen. Presentation belongs outside the fixed step,
54
+ * where the frame is.
55
+ */
56
+ export const RENDER_TYPES: readonly OpaqueType[] = [
57
+ {
58
+ module: RENDER_MODULE,
59
+ name: 'Renderer',
60
+ doc: 'The renderer drawing this frame. Reaches a script through `uses`.',
61
+ },
62
+ ];
63
+
64
+ const define = (
65
+ name: string,
66
+ params: readonly { name: string; type: string }[],
67
+ doc: string,
68
+ ): CapabilityDefinition =>
69
+ defineCapability({
70
+ module: RENDER_MODULE,
71
+ name,
72
+ signature: `fn(${params.map((p) => `${p.name}: ${p.type}`).join(', ')}) -> void`,
73
+ params: [...params],
74
+ returns: 'void',
75
+ effects: ['scene.write'],
76
+ deterministic: false,
77
+ doc,
78
+ implementation: `${RENDER_MODULE}.${name}`,
79
+ });
80
+
81
+ const R = { name: 'renderer', type: 'Renderer' } as const;
82
+
83
+ export const RENDER_CAPABILITIES: readonly CapabilityDefinition[] = [
84
+ define(
85
+ 'bloom',
86
+ [R, { name: 'scale', type: 'f32' }],
87
+ "How much of the frame's bloom ceiling to take, 0 to 1. Held until changed. Does nothing " +
88
+ "when the quality profile's `bloom` is 0, because the chain is never built.",
89
+ ),
90
+ define(
91
+ 'exposure',
92
+ [R, { name: 'stops', type: 'f32' }],
93
+ "How far this frame is scaled into the tone curve, above 0. Replaces the profile's " +
94
+ '`outputExposure` for this frame onward. Adaptation is yours: a game knows it walked into a ' +
95
+ 'cave, and the renderer could only find out a frame late. Ignored unless the output ' +
96
+ 'transform is `aces`, since otherwise there is no curve to be exposed into.',
97
+ ),
98
+ define(
99
+ 'motionBlur',
100
+ [R, { name: 'scale', type: 'f32' }],
101
+ 'How much of the camera motion blur ceiling this frame takes, 0 to 1. Held until changed. ' +
102
+ 'Ramp it with whatever "fast" means here: blur that is always on stops being a speed cue ' +
103
+ 'within seconds and costs eight taps a moving pixel while doing so.',
104
+ ),
105
+ define(
106
+ 'speedBlur',
107
+ [R, { name: 'strength', type: 'f32' }],
108
+ 'How much speed blur the frame resolves with, 0 to 1. Held until changed, and ignored when ' +
109
+ 'screen effects are off.',
110
+ ),
111
+ define(
112
+ 'focus',
113
+ [
114
+ R,
115
+ { name: 'distance', type: 'f32' },
116
+ { name: 'range', type: 'f32' },
117
+ { name: 'scale', type: 'f32' },
118
+ ],
119
+ "Where this frame's lens is focused and how deep the sharp zone is, in metres, and how much " +
120
+ 'of the depth-of-field ceiling to take, 0 to 1. Held until changed. `scale` is required ' +
121
+ 'here though the engine defaults it to 1, because taking the whole ceiling is the expensive ' +
122
+ 'answer and a script author is further from that cost than a TypeScript caller.',
123
+ ),
124
+ define(
125
+ 'medium',
126
+ [
127
+ R,
128
+ { name: 'density', type: 'f32' },
129
+ { name: 'albedo', type: 'f32' },
130
+ { name: 'anisotropy', type: 'f32' },
131
+ { name: 'maxDistance', type: 'f32' },
132
+ ],
133
+ 'How thick the air is: a medium filling the whole frustum, rather than a beam inside a hull. ' +
134
+ '`density` is extinction per metre and 0 is off — nothing is allocated and nothing is drawn, ' +
135
+ 'so a script that never raises it costs nothing. `albedo` is how much of what the air takes ' +
136
+ 'out comes back as light rather than heat, 0 to 1; `anisotropy` is -1 back to 1 forward, and ' +
137
+ 'is what makes haze glow toward a low sun; `maxDistance` is where the search for light to ' +
138
+ 'scatter stops, in metres, which is not where the fog stops. Held until changed. Does ' +
139
+ "nothing when the quality profile's `globalMediumSteps` is 0, because no march is built.",
140
+ ),
141
+ define(
142
+ 'veil',
143
+ [
144
+ R,
145
+ { name: 'red', type: 'f32' },
146
+ { name: 'green', type: 'f32' },
147
+ { name: 'blue', type: 'f32' },
148
+ { name: 'alpha', type: 'f32' },
149
+ ],
150
+ 'Composite a flat colour over the finished frame, for a cut dipping to white or to black. ' +
151
+ '**Once per frame: a frame that does not call this draws with no veil at all**, unlike the ' +
152
+ 'dials above, because a transition that forgets to turn itself off is worse than one that ' +
153
+ 'forgets to turn on.',
154
+ ),
155
+ ];
156
+
157
+ /**
158
+ * Unconditional, like `drift/terrain` and `drift/editor`: every capability takes the renderer it
159
+ * acts on, which reaches a script through `uses`, so there is nothing for an absent service to
160
+ * make fail.
161
+ *
162
+ * **`medium` takes every argument the engine defaults**, unlike `setGlobalMedium`, and for the
163
+ * reason `focus` gives about its own `scale`: a script author is further from the cost than a
164
+ * TypeScript caller, and a defaulted albedo is a number somebody has to go and look up before they
165
+ * can tell what their fog will look like. Four numbers is one line either way.
166
+ *
167
+ * * **`speedBlur` is the one name here that is not the engine's own.** The method is `setSpeedRush`,
168
+ * and a rush is a thing that happens in somebody's game rather than a description of an image
169
+ * operation — binding that spelling would have written one consumer's noun into the language's
170
+ * surface, where it could not later be taken back. A binding is a lookup and this is what a lookup
171
+ * is for.
172
+ */
173
+ export function renderImplementation(): Record<string, unknown> {
174
+ return {
175
+ bloom: (renderer: RendererApi, scale: number) => renderer.setBloom(scale),
176
+ exposure: (renderer: RendererApi, stops: number) => renderer.setOutputExposure(stops),
177
+ motionBlur: (renderer: RendererApi, scale: number) => renderer.setCameraMotionBlur(scale),
178
+ speedBlur: (renderer: RendererApi, strength: number) => renderer.setSpeedRush(strength),
179
+ focus: (renderer: RendererApi, distance: number, range: number, scale: number) =>
180
+ renderer.setDepthOfField(distance, range, scale),
181
+ medium: (
182
+ renderer: RendererApi,
183
+ density: number,
184
+ albedo: number,
185
+ anisotropy: number,
186
+ maxDistance: number,
187
+ ) => renderer.setGlobalMedium(density, albedo, anisotropy, maxDistance),
188
+ veil: (renderer: RendererApi, red: number, green: number, blue: number, alpha: number) =>
189
+ renderer.setFrameVeil(red, green, blue, alpha),
190
+ };
191
+ }
@@ -0,0 +1,110 @@
1
+ import type { CapabilityDefinition, OpaqueType } from 'driftscript';
2
+ import { defineCapability } from 'driftscript';
3
+
4
+ export const ROLLBACK_MODULE = 'drift/rollback';
5
+
6
+ /**
7
+ * `drift/rollback` — what a script can ask about the rewind it is running inside.
8
+ *
9
+ * **Every capability here is `nondeterministic`, and that is the whole design.** The obvious reading
10
+ * is that these are reads and reads are cheap, so they should be as permissive as possible. The
11
+ * opposite is true, and the reason is the thing being read: *whether this tick is a replay*, *how
12
+ * far back the loop can still reach*, *which ticks are settled*. All of them move with packet
13
+ * timing, and a `@deterministic` system that branched on one would take a different path live than
14
+ * on replay — producing a world that differs from the world it is replaying, for a reason that
15
+ * looks nothing like a networking bug.
16
+ *
17
+ * That is the same call `bindings/ui.ts` made for `hovered` and `pressed`, and `bindings/network.ts`
18
+ * makes for the confirmed watermark. Stated once more here because this is the module where getting
19
+ * it wrong would be most tempting and least visible.
20
+ *
21
+ * ## `isReplaying` is the one worth being explicit about
22
+ *
23
+ * A script asking "am I being replayed" is almost always about to do something that should not
24
+ * happen twice — play a sound, spawn a particle, send a message. **That is a legitimate use and it
25
+ * is precisely why the effect is what it is**: the function doing it is not a simulation function,
26
+ * it is a presentation function that happens to run beside one, and the annotation should say so.
27
+ *
28
+ * A script using it to change *simulation* state is writing a bug, and nothing here can stop that.
29
+ * What it can do is make the deterministic annotation refuse to compile over it, which is the
30
+ * difference between a bug that is found by the checker and one that is found by two players
31
+ * disagreeing about who won.
32
+ *
33
+ * ## There is no `rollback.read` effect and this does not add one
34
+ *
35
+ * The language named `behavior.read` and `behavior.write` ahead of their provider precisely so a
36
+ * track would not need a release first, and it named `network.read` and `network.write` the same
37
+ * way — but nothing for rollback. `nondeterministic` is honest for every capability here, so no
38
+ * release is needed. *Reversed by* a consumer who wants a `@deterministic` function to read
39
+ * something in this module, at which point the question is which capability and why, and the answer
40
+ * is a one-line change to the `Effect` union.
41
+ */
42
+ export const ROLLBACK_TYPES: readonly OpaqueType[] = [
43
+ {
44
+ module: ROLLBACK_MODULE,
45
+ name: 'Rewind',
46
+ doc: 'The rewind loop: which tick is running, whether this is a replay, and how far back it can reach.',
47
+ },
48
+ ];
49
+
50
+ /** What the binding needs of a `RewindLoop`, declared rather than imported. */
51
+ interface RewindLike {
52
+ readonly tick: number;
53
+ readonly depth: number;
54
+ readonly earliest: number;
55
+ readonly isReplaying: boolean;
56
+ readonly stats: { readonly replays: number; readonly replayedTicks: number };
57
+ }
58
+
59
+ const define = (name: string, returns: string, doc: string): CapabilityDefinition =>
60
+ defineCapability({
61
+ module: ROLLBACK_MODULE,
62
+ name,
63
+ signature: `fn(rewind: Rewind) -> ${returns}`,
64
+ params: [{ name: 'rewind', type: 'Rewind' }],
65
+ returns,
66
+ effects: ['nondeterministic'],
67
+ deterministic: false,
68
+ doc,
69
+ implementation: `${ROLLBACK_MODULE}.${name}`,
70
+ });
71
+
72
+ export const ROLLBACK_CAPABILITIES: readonly CapabilityDefinition[] = [
73
+ define('tick', 'i32', 'The last tick that was stepped, or -1 before the first.'),
74
+ define(
75
+ 'isReplaying',
76
+ 'bool',
77
+ 'Whether this tick is being re-run to correct a mispredicted one. Anything that should not happen twice — a sound, a particle, a message — asks this first.',
78
+ ),
79
+ define(
80
+ 'depth',
81
+ 'i32',
82
+ 'How many ticks back a rewind can reach. Eight at 60 Hz is 133 milliseconds of correction.',
83
+ ),
84
+ define(
85
+ 'earliest',
86
+ 'i32',
87
+ 'The oldest tick still retained, or -1. An input older than this cannot be applied and the session cannot be made correct.',
88
+ ),
89
+ define(
90
+ 'replays',
91
+ 'i32',
92
+ 'How many rewinds have happened. A session correcting constantly has a link problem.',
93
+ ),
94
+ define(
95
+ 'replayedTicks',
96
+ 'i32',
97
+ 'How many ticks those rewinds re-ran in total. Divided by `replays`, the average depth of a correction.',
98
+ ),
99
+ ];
100
+
101
+ export function rollbackImplementation(): Record<string, unknown> {
102
+ return {
103
+ tick: (rewind: RewindLike) => rewind.tick,
104
+ isReplaying: (rewind: RewindLike) => rewind.isReplaying,
105
+ depth: (rewind: RewindLike) => rewind.depth,
106
+ earliest: (rewind: RewindLike) => rewind.earliest,
107
+ replays: (rewind: RewindLike) => rewind.stats.replays,
108
+ replayedTicks: (rewind: RewindLike) => rewind.stats.replayedTicks,
109
+ };
110
+ }