@woosh/meep-engine 3.14.6 → 3.15.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.
- package/package.json +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +48 -0
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +246 -3
- package/src/shade/RENDERER_CONTRACT.md +584 -580
- package/src/shade/playground/avboit/README.md +117 -0
- package/src/shade/playground/avboit/index.html +215 -0
- package/src/shade/playground/avboit/main.d.ts +2 -0
- package/src/shade/playground/avboit/main.d.ts.map +1 -0
- package/src/shade/playground/avboit/main.js +875 -0
- package/src/shade/renderer/Renderer.d.ts +16 -0
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +29 -28
- package/src/shade/renderer/ShadeTransparencyMode.d.ts +13 -0
- package/src/shade/renderer/ShadeTransparencyMode.d.ts.map +1 -0
- package/src/shade/renderer/ShadeTransparencyMode.js +30 -0
- package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts +9 -0
- package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts.map +1 -1
- package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.js +26 -0
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts +7 -2
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +12 -1
- package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.js +1 -1
- package/src/shade/renderer/rasterize/forward/FORWARD_VERTEX_SHADER_STRUCT.d.ts +3 -0
- package/src/shade/renderer/rasterize/forward/FORWARD_VERTEX_SHADER_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/{native/oit → forward}/FORWARD_VERTEX_SHADER_STRUCT.js +4 -4
- package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.d.ts +3 -0
- package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.js +17 -0
- package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_brick4.d.ts +1 -1
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_brick4.js +16 -16
- package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_ibl.d.ts +1 -1
- package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_ibl.js +17 -17
- package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_material_side_should_draw.d.ts +1 -1
- package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_material_side_should_draw.js +2 -2
- package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.d.ts +3 -0
- package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/{native/oit → forward}/vertex_shader_chunk.js +4 -4
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.d.ts +27 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.js +27 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts +107 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.js +120 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +463 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +509 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.d.ts +15 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.js +33 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.d.ts +20 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.js +38 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.d.ts +38 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.js +130 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.d.ts +37 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.js +73 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.d.ts +9 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.js +17 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.d.ts +11 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.js +18 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.d.ts +9 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.js +17 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.d.ts +9 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.js +17 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.d.ts +15 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.js +25 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.d.ts +17 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.js +35 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.d.ts +11 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.js +21 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.d.ts +9 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.js +26 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.d.ts +11 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.js +22 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.d.ts +10 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.js +21 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.d.ts +10 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.js +32 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.d.ts +20 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.js +27 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.d.ts +10 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.js +18 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.d.ts +9 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.js +21 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.d.ts +9 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.js +23 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.d.ts +13 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.js +27 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.d.ts +22 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.js +38 -0
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +117 -0
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +935 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.d.ts +8 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.js +135 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.d.ts +3 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.js +101 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.d.ts +3 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.js +96 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.d.ts +3 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.js +83 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.d.ts +9 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.js +119 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts +3 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.js +81 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.d.ts +15 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.js +237 -0
- package/src/shade/renderer/rasterize/native/material_draw_indirect.d.ts +22 -0
- package/src/shade/renderer/rasterize/native/material_draw_indirect.d.ts.map +1 -0
- package/src/shade/renderer/rasterize/native/material_draw_indirect.js +69 -0
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +665 -722
- package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.js +3 -3
- package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.js +4 -4
- package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +4 -4
- package/src/shade/renderer/rasterize/native/oit/FORWARD_VERTEX_SHADER_STRUCT.d.ts +0 -3
- package/src/shade/renderer/rasterize/native/oit/FORWARD_VERTEX_SHADER_STRUCT.d.ts.map +0 -1
- package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.d.ts +0 -3
- package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.d.ts.map +0 -1
- package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.js +0 -17
- package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.d.ts.map +0 -1
- package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.d.ts.map +0 -1
- package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.d.ts.map +0 -1
- package/src/shade/renderer/rasterize/native/oit/vertex_shader_chunk.d.ts +0 -3
- package/src/shade/renderer/rasterize/native/oit/vertex_shader_chunk.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -18,6 +18,19 @@
|
|
|
18
18
|
* {@link onReplay} handler re-executes the same SimActions the original
|
|
19
19
|
* predict path produced.
|
|
20
20
|
*
|
|
21
|
+
* - A replay also reapplies the records in the rewound window that ARRIVED
|
|
22
|
+
* from a peer, which {@link onReplay} cannot: it re-executes this client's
|
|
23
|
+
* own input, and the AUTH_STATE that precedes it carries one `network_id`.
|
|
24
|
+
* Without this step a rewind about one entity discards the owner's published
|
|
25
|
+
* state for every OTHER entity in the window — permanently, for anything
|
|
26
|
+
* published on change rather than every frame, because nothing re-sends it
|
|
27
|
+
* and nothing else restores it. This is the client half of the rule
|
|
28
|
+
* `SimAction` states and `ServerAuthoritativeServer.#read_historical`
|
|
29
|
+
* implements: a record's `sender_id` says whether it arrived, was authored,
|
|
30
|
+
* or was derived, and a replay reapplies the first two and recomputes the
|
|
31
|
+
* third. Here the authored half is `onReplay`'s job and the arrived half is
|
|
32
|
+
* this one's.
|
|
33
|
+
*
|
|
21
34
|
* - A no-op short-circuit is built in: before doing the rewind+replay, the
|
|
22
35
|
* orchestrator gives the user a chance to compute what the replay WOULD
|
|
23
36
|
* produce via {@link onComputeExpected}; if that matches the current
|
|
@@ -75,6 +88,20 @@ export class ServerAuthoritativeClient {
|
|
|
75
88
|
reconcile_count: number;
|
|
76
89
|
/** @type {number} */
|
|
77
90
|
replay_frame_count: number;
|
|
91
|
+
/**
|
|
92
|
+
* Records that arrived from a peer and were reapplied by a replay, over the
|
|
93
|
+
* life of this client. Zero on a client whose peers publish nothing the
|
|
94
|
+
* rewind window covers; non-zero is the fix for the discard above doing
|
|
95
|
+
* work, and is what a test can assert on rather than inferring from a value.
|
|
96
|
+
* @type {number}
|
|
97
|
+
*/
|
|
98
|
+
replayed_arrived_count: number;
|
|
99
|
+
/**
|
|
100
|
+
* Reconciliations abandoned because the action log no longer held the frames
|
|
101
|
+
* the rewind needed. See {@link onReconcileAbandoned}.
|
|
102
|
+
* @type {number}
|
|
103
|
+
*/
|
|
104
|
+
reconcile_abandoned_count: number;
|
|
78
105
|
/** @type {NetworkPeer} */
|
|
79
106
|
peer: NetworkPeer;
|
|
80
107
|
/** @type {EntityComponentDataset} */
|
|
@@ -199,6 +226,26 @@ export class ServerAuthoritativeClient {
|
|
|
199
226
|
* @type {Signal}
|
|
200
227
|
*/
|
|
201
228
|
onReconcileComplete: Signal;
|
|
229
|
+
/**
|
|
230
|
+
* Fires when a reconciliation is abandoned because the action log no
|
|
231
|
+
* longer holds the frames its rewind needed. Args:
|
|
232
|
+
* `(server_frame, network_id)`.
|
|
233
|
+
*
|
|
234
|
+
* This is a real loss of replicated state and not a hiccup: state a peer
|
|
235
|
+
* does not own reaches it on the action stream, and an owner that
|
|
236
|
+
* publishes on change never sends it again — so whatever that window
|
|
237
|
+
* carried for every entity other than `network_id` is gone. The server
|
|
238
|
+
* would normally back-fill via `RECOVERY_REQUEST`/`STATE_BURST`, which
|
|
239
|
+
* is request-driven, so something has to know to ask. That is what this
|
|
240
|
+
* signal is for; {@link reconcile_abandoned_count} is the same event as
|
|
241
|
+
* a counter.
|
|
242
|
+
*
|
|
243
|
+
* A client seeing this repeatedly is running further ahead of the server
|
|
244
|
+
* frame than its `frame_capacity` covers.
|
|
245
|
+
*
|
|
246
|
+
* @type {Signal}
|
|
247
|
+
*/
|
|
248
|
+
onReconcileAbandoned: Signal;
|
|
202
249
|
/**
|
|
203
250
|
* Connect to a server peer over a transport.
|
|
204
251
|
* @param {number} peer_id
|
|
@@ -262,4 +309,5 @@ import { InputRing } from "../state/InputRing.js";
|
|
|
262
309
|
import { RewindEngine } from "../sim/RewindEngine.js";
|
|
263
310
|
import { TimeDilation } from "../time/TimeDilation.js";
|
|
264
311
|
import Signal from "../../../core/events/signal/Signal.js";
|
|
312
|
+
import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
|
|
265
313
|
//# sourceMappingURL=ServerAuthoritativeClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerAuthoritativeClient.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/orchestrator/ServerAuthoritativeClient.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ServerAuthoritativeClient.d.ts","sourceRoot":"","sources":["../../../../../src/engine/network/orchestrator/ServerAuthoritativeClient.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH;IA8DI;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,iOAxBW;QACV,KAAS,yBAAyB;QAClC,eAAmB,8BAA8B;QACjD,qBAAyB,EAAE,UAAU,CAAC;QACtC,cAAkB,EAAE,UAAU,CAAC;QAC/B,YAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,cAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,mBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,mBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,eAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,iBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,aAAiB,CAAC,EAAE,YAAY,CAAC;QACjC,oBAAwB,CAAC,EAAE,MAAM,CAAC;KAC/B,EAqNH;IAzPD,qBAAqB;IACrB,iBADW,MAAM,CACG;IAEpB,qBAAqB;IACrB,oBADW,MAAM,CACM;IAEvB;;;;;;OAMG;IACH,wBAFU,MAAM,CAEW;IAE3B;;;;OAIG;IACH,2BAFU,MAAM,CAEc;IAyC1B,0BAA0B;IAC1B,MADW,WAAW,CAUpB;IAEF,qCAAqC;IACrC,8BAAkB;IAClB,gCAAgC;IAChC,4BAAkC;IAClC,mCAAmC;IACnC,iCAAsC;IACtC,wBAAwB;IACxB,sBAAsC;IACtC,gCAAgC;IAChC,mCAAgD;IAEhD;;;;OAIG;IACH,YAFU,SAAS,CAKjB;IAEF,2BAA2B;IAC3B,eADW,YAAY,CAKrB;IAEF;;;;;OAKG;IACH,mBAFU,MAAM,CAE0B;IAE1C;;;;;OAKG;IACH,eAFU,YAAY,CAEY;IAElC;;;;OAIG;IACH,mBAFU,MAAM,CAEW;IAE3B;;;;;;;;OAQG;IACH,iBAFU,MAAM,CAEU;IAO1B;;;;;;;;;OASG;IACH,WAFU,MAAM,CAEa;IAE7B;;;;;;;;;OASG;IACH,UAFU,MAAM,CAEY;IAE5B;;;;;;;;;;;;;;;;;;OAkBG;IACH,mBAFU,MAAM,CAEqB;IAErC;;;;;OAKG;IACH,kBAFU,MAAM,CAEoB;IAEpC;;;;;;;OAOG;IACH,mBAFU,MAAM,CAEqB;IAErC;;;;;;;;OAQG;IACH,kBAFU,MAAM,CAEoB;IAEpC;;;;OAIG;IACH,qBAFU,MAAM,CAEuB;IAEvC;;;;;;;;;;;;;;;;;;OAkBG;IACH,sBAFU,MAAM,CAEwB;IAW5C;;;;OAIG;IACH,2BAHW,MAAM,aACN,MAAM,QAIhB;IAED;;;OAGG;IACH,gCAFW,MAAM,QAIhB;IAED;iCAC6B;IAC7B,qBADW,MAAM,6CAGhB;IAED;;;;;;;;;;OAUG;IACH,oBAFW,MAAM,QAahB;IAED;;;;;;;OAOG;IACH,qBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;;OAMG;IACH,iBAHW,MAAM,GACJ,YAAY,CAIxB;IA6OD;;;;;OAKG;IACH,oBAFW,MAAM,QAIhB;IAED;;;;OAIG;IACH,oBAFW,MAAM,QAIhB;;CACJ;4BA9pB2B,kBAAkB;0BAFpB,uBAAuB;6BADpB,wBAAwB;6BAExB,yBAAyB;mBALnC,uCAAuC;6BAF7B,sCAAsC"}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { assert } from "../../../core/assert.js";
|
|
2
|
+
import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
|
|
2
3
|
import { Uint32Map } from "../../../core/collection/Uint32Map.js";
|
|
3
4
|
import Signal from "../../../core/events/signal/Signal.js";
|
|
4
5
|
import { AlwaysRelevantScope } from "../replication/ScopeFilter.js";
|
|
6
|
+
import { ActionRecordCursor } from "../sim/ActionRecordCursor.js";
|
|
5
7
|
import { RewindEngine } from "../sim/RewindEngine.js";
|
|
6
8
|
import { InputRing } from "../state/InputRing.js";
|
|
7
9
|
import { TimeDilation } from "../time/TimeDilation.js";
|
|
8
10
|
import { NetworkPeer } from "./NetworkPeer.js";
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Ints per harvested record in {@link ServerAuthoritativeClient}'s replay index:
|
|
14
|
+
* action type id, sender id, and the offset of its payload.
|
|
15
|
+
*
|
|
16
|
+
* @type {number}
|
|
17
|
+
*/
|
|
18
|
+
const HARVEST_STRIDE = 3;
|
|
19
|
+
|
|
10
20
|
/**
|
|
11
21
|
* Client-side orchestrator for the predict-reconcile model.
|
|
12
22
|
*
|
|
@@ -27,6 +37,19 @@ import { NetworkPeer } from "./NetworkPeer.js";
|
|
|
27
37
|
* {@link onReplay} handler re-executes the same SimActions the original
|
|
28
38
|
* predict path produced.
|
|
29
39
|
*
|
|
40
|
+
* - A replay also reapplies the records in the rewound window that ARRIVED
|
|
41
|
+
* from a peer, which {@link onReplay} cannot: it re-executes this client's
|
|
42
|
+
* own input, and the AUTH_STATE that precedes it carries one `network_id`.
|
|
43
|
+
* Without this step a rewind about one entity discards the owner's published
|
|
44
|
+
* state for every OTHER entity in the window — permanently, for anything
|
|
45
|
+
* published on change rather than every frame, because nothing re-sends it
|
|
46
|
+
* and nothing else restores it. This is the client half of the rule
|
|
47
|
+
* `SimAction` states and `ServerAuthoritativeServer.#read_historical`
|
|
48
|
+
* implements: a record's `sender_id` says whether it arrived, was authored,
|
|
49
|
+
* or was derived, and a replay reapplies the first two and recomputes the
|
|
50
|
+
* third. Here the authored half is `onReplay`'s job and the arrived half is
|
|
51
|
+
* this one's.
|
|
52
|
+
*
|
|
30
53
|
* - A no-op short-circuit is built in: before doing the rewind+replay, the
|
|
31
54
|
* orchestrator gives the user a chance to compute what the replay WOULD
|
|
32
55
|
* produce via {@link onComputeExpected}; if that matches the current
|
|
@@ -55,6 +78,30 @@ export class ServerAuthoritativeClient {
|
|
|
55
78
|
#expected_scratch;
|
|
56
79
|
#measured_scratch;
|
|
57
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Records of one log frame that arrived from a peer, indexed
|
|
83
|
+
* {@link HARVEST_STRIDE} ints apiece into {@link #harvest_payloads}. Filled
|
|
84
|
+
* by {@link #harvest_arrived} before the frame is reopened and drained by
|
|
85
|
+
* {@link #execute_harvested} once it is.
|
|
86
|
+
* @type {Int32Array}
|
|
87
|
+
*/
|
|
88
|
+
#harvest_index = new Int32Array(HARVEST_STRIDE * 32);
|
|
89
|
+
|
|
90
|
+
/** @type {number} records currently described by {@link #harvest_index} */
|
|
91
|
+
#harvest_count = 0;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The harvested records' action payloads, back to back. A frame's bytes have
|
|
95
|
+
* to be copied out before the frame is reopened: opening it rewinds its
|
|
96
|
+
* buffer to zero and the re-execution then writes over exactly the bytes
|
|
97
|
+
* being read.
|
|
98
|
+
* @type {BinaryBuffer}
|
|
99
|
+
*/
|
|
100
|
+
#harvest_payloads = new BinaryBuffer();
|
|
101
|
+
|
|
102
|
+
/** @type {ActionRecordCursor} */
|
|
103
|
+
#harvest_cursor = new ActionRecordCursor();
|
|
104
|
+
|
|
58
105
|
// Counters
|
|
59
106
|
/** @type {number} */
|
|
60
107
|
reconcile_count = 0;
|
|
@@ -62,6 +109,22 @@ export class ServerAuthoritativeClient {
|
|
|
62
109
|
/** @type {number} */
|
|
63
110
|
replay_frame_count = 0;
|
|
64
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Records that arrived from a peer and were reapplied by a replay, over the
|
|
114
|
+
* life of this client. Zero on a client whose peers publish nothing the
|
|
115
|
+
* rewind window covers; non-zero is the fix for the discard above doing
|
|
116
|
+
* work, and is what a test can assert on rather than inferring from a value.
|
|
117
|
+
* @type {number}
|
|
118
|
+
*/
|
|
119
|
+
replayed_arrived_count = 0;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Reconciliations abandoned because the action log no longer held the frames
|
|
123
|
+
* the rewind needed. See {@link onReconcileAbandoned}.
|
|
124
|
+
* @type {number}
|
|
125
|
+
*/
|
|
126
|
+
reconcile_abandoned_count = 0;
|
|
127
|
+
|
|
65
128
|
/**
|
|
66
129
|
* @param {{
|
|
67
130
|
* world: EntityComponentDataset,
|
|
@@ -261,6 +324,30 @@ export class ServerAuthoritativeClient {
|
|
|
261
324
|
*/
|
|
262
325
|
this.onReconcileComplete = new Signal();
|
|
263
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Fires when a reconciliation is abandoned because the action log no
|
|
329
|
+
* longer holds the frames its rewind needed. Args:
|
|
330
|
+
* `(server_frame, network_id)`.
|
|
331
|
+
*
|
|
332
|
+
* This is a real loss of replicated state and not a hiccup: state a peer
|
|
333
|
+
* does not own reaches it on the action stream, and an owner that
|
|
334
|
+
* publishes on change never sends it again — so whatever that window
|
|
335
|
+
* carried for every entity other than `network_id` is gone. The server
|
|
336
|
+
* would normally back-fill via `RECOVERY_REQUEST`/`STATE_BURST`, which
|
|
337
|
+
* is request-driven, so something has to know to ask. That is what this
|
|
338
|
+
* signal is for; {@link reconcile_abandoned_count} is the same event as
|
|
339
|
+
* a counter.
|
|
340
|
+
*
|
|
341
|
+
* A client seeing this repeatedly is running further ahead of the server
|
|
342
|
+
* frame than its `frame_capacity` covers.
|
|
343
|
+
*
|
|
344
|
+
* @type {Signal}
|
|
345
|
+
*/
|
|
346
|
+
this.onReconcileAbandoned = new Signal();
|
|
347
|
+
|
|
348
|
+
// One packet's worth of action payloads, grown on demand from there.
|
|
349
|
+
this.#harvest_payloads.setCapacity(1024);
|
|
350
|
+
|
|
264
351
|
// Wire the AUTH_STATE handler to the algorithm above.
|
|
265
352
|
this.peer.onAuthState.add((_peer_id, server_frame, network_id, buffer) => {
|
|
266
353
|
this.#handle_auth_state(server_frame, network_id, buffer);
|
|
@@ -379,17 +466,51 @@ export class ServerAuthoritativeClient {
|
|
|
379
466
|
|
|
380
467
|
// Real divergence: rewind, apply server's bytes, replay forward.
|
|
381
468
|
const rewind_target = server_frame - 1;
|
|
469
|
+
let rewound = false;
|
|
382
470
|
if (this.#current_frame > rewind_target) {
|
|
383
471
|
try {
|
|
384
472
|
this.rewind_engine.rewind_to(this.#current_frame, rewind_target);
|
|
473
|
+
rewound = true;
|
|
385
474
|
} catch (e) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
475
|
+
/*
|
|
476
|
+
The action_log rolled past server_frame, so the window this
|
|
477
|
+
reconciliation needs no longer exists and skipping it is the
|
|
478
|
+
only option left. Counted and announced rather than swallowed:
|
|
479
|
+
what the window carried for every entity other than this one is
|
|
480
|
+
gone for good, and an application that cannot see that happen
|
|
481
|
+
cannot ask for the back-fill that would repair it.
|
|
482
|
+
*/
|
|
483
|
+
this.reconcile_abandoned_count++;
|
|
484
|
+
this.onReconcileAbandoned.send2(server_frame, network_id);
|
|
389
485
|
return;
|
|
390
486
|
}
|
|
391
487
|
}
|
|
392
488
|
|
|
489
|
+
/*
|
|
490
|
+
Frame `server_frame` was just undone and is NOT replayed below: the
|
|
491
|
+
AUTH_STATE is the server's state at the END of it, so re-running this
|
|
492
|
+
client's own input for that frame would apply that input twice.
|
|
493
|
+
|
|
494
|
+
Its ARRIVED records are the other half of the frame, and the half that
|
|
495
|
+
was being dropped. The bytes below repair one `network_id`; every other
|
|
496
|
+
entity the frame touched has nothing else to restore it. They go back in
|
|
497
|
+
BEFORE those bytes, so the authoritative value still wins wherever the
|
|
498
|
+
two describe the same component.
|
|
499
|
+
|
|
500
|
+
In the ordinary owner-scoped arrangement this is the frame that matters
|
|
501
|
+
most: the owner's records for a frame land in the log at the owner's
|
|
502
|
+
frame number, which is at or below `server_frame`, so they sit at the
|
|
503
|
+
very bottom of the rewound window rather than inside the replay range.
|
|
504
|
+
*/
|
|
505
|
+
if (rewound && this.#harvest_arrived(server_frame) > 0) {
|
|
506
|
+
this.action_log.begin_frame(server_frame);
|
|
507
|
+
try {
|
|
508
|
+
this.#execute_harvested();
|
|
509
|
+
} finally {
|
|
510
|
+
this.action_log.end_frame();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
393
514
|
// Apply the server's authoritative state bytes. The handler does the
|
|
394
515
|
// adapter-specific deserialize.
|
|
395
516
|
this.onApplyAuthState.send3(server_frame, network_id, buffer);
|
|
@@ -397,6 +518,10 @@ export class ServerAuthoritativeClient {
|
|
|
397
518
|
// Replay forward from (server_frame + 1) through current_frame.
|
|
398
519
|
let replay_count = 0;
|
|
399
520
|
for (let f = server_frame + 1; f <= this.#current_frame; f++) {
|
|
521
|
+
// Harvested before `begin_frame`, which rewinds the frame's buffer
|
|
522
|
+
// over the bytes being read. Nothing to harvest if nothing was undone.
|
|
523
|
+
const arrived = rewound ? this.#harvest_arrived(f) : 0;
|
|
524
|
+
|
|
400
525
|
this.action_log.begin_frame(f);
|
|
401
526
|
try {
|
|
402
527
|
let input_reader = null;
|
|
@@ -405,6 +530,15 @@ export class ServerAuthoritativeClient {
|
|
|
405
530
|
input_reader.position = 0;
|
|
406
531
|
}
|
|
407
532
|
this.onReplay.send2(f, input_reader);
|
|
533
|
+
|
|
534
|
+
/*
|
|
535
|
+
After the local input, which is the order the frame was written
|
|
536
|
+
in the first time: this client's prediction was logged as it
|
|
537
|
+
ticked the frame, and the owner's mutations for that frame
|
|
538
|
+
arrived later. It also leaves the owner's value last, which is
|
|
539
|
+
the one that should win where both touch a component.
|
|
540
|
+
*/
|
|
541
|
+
if (arrived > 0) this.#execute_harvested();
|
|
408
542
|
} finally {
|
|
409
543
|
this.action_log.end_frame();
|
|
410
544
|
}
|
|
@@ -416,6 +550,115 @@ export class ServerAuthoritativeClient {
|
|
|
416
550
|
this.onReconcileComplete.send2(server_frame, replay_count);
|
|
417
551
|
}
|
|
418
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Copy the records of `frame` that arrived from a peer out of the action
|
|
555
|
+
* log, ready for {@link #execute_harvested} to put back.
|
|
556
|
+
*
|
|
557
|
+
* MUST be called before `action_log.begin_frame(frame)`. Opening a frame
|
|
558
|
+
* rewinds its buffer to zero without clearing it, and the re-execution
|
|
559
|
+
* writes over exactly the bytes this reads, so the payloads are copied into
|
|
560
|
+
* {@link #harvest_payloads} rather than read in place.
|
|
561
|
+
*
|
|
562
|
+
* Provenance decides what a replay does with a record, and this client keeps
|
|
563
|
+
* a narrower set than the server does:
|
|
564
|
+
*
|
|
565
|
+
* - a CONNECTED peer's id — it arrived from that peer, and nothing else
|
|
566
|
+
* here reproduces it. `onApplyAuthState` covers one `network_id` and
|
|
567
|
+
* {@link onReplay} covers this client's own input. Kept.
|
|
568
|
+
* - `SENDER_DERIVED` — output a replay recomputes. Skipped, same as
|
|
569
|
+
* `ServerAuthoritativeServer.#read_historical` skips it.
|
|
570
|
+
* - `SENDER_LOCAL`, and this client's own peer id — its own input, which
|
|
571
|
+
* {@link onReplay} reapplies from the sampled-action ledger. Skipped:
|
|
572
|
+
* reapplying it from the log as well would apply it twice.
|
|
573
|
+
*
|
|
574
|
+
* Connectedness is the test rather than a comparison against the local peer
|
|
575
|
+
* id because it is the same question stated directly — the sender byte of an
|
|
576
|
+
* arrived record is the peer it arrived from, and a client never connects to
|
|
577
|
+
* itself, so its own records can never match.
|
|
578
|
+
*
|
|
579
|
+
* @param {number} frame
|
|
580
|
+
* @returns {number} how many records were harvested
|
|
581
|
+
* @private
|
|
582
|
+
*/
|
|
583
|
+
#harvest_arrived(frame) {
|
|
584
|
+
this.#harvest_count = 0;
|
|
585
|
+
this.#harvest_payloads.position = 0;
|
|
586
|
+
|
|
587
|
+
if (!this.action_log.has_frame(frame)) return 0;
|
|
588
|
+
|
|
589
|
+
const cursor = this.#harvest_cursor;
|
|
590
|
+
cursor.open(this.action_log, frame);
|
|
591
|
+
const bytes = cursor.buffer.raw_bytes;
|
|
592
|
+
|
|
593
|
+
while (cursor.next()) {
|
|
594
|
+
if (!this.peer.is_connected(cursor.sender_id)) continue;
|
|
595
|
+
|
|
596
|
+
if ((this.#harvest_count + 1) * HARVEST_STRIDE > this.#harvest_index.length) {
|
|
597
|
+
this.#grow_harvest_index();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const i = this.#harvest_count * HARVEST_STRIDE;
|
|
601
|
+
this.#harvest_index[i] = cursor.action_type_id;
|
|
602
|
+
this.#harvest_index[i + 1] = cursor.sender_id;
|
|
603
|
+
this.#harvest_index[i + 2] = this.#harvest_payloads.position;
|
|
604
|
+
this.#harvest_count++;
|
|
605
|
+
|
|
606
|
+
this.#harvest_payloads.writeBytes(bytes, cursor.payload_start, cursor.payload_len);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return this.#harvest_count;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Re-execute what {@link #harvest_arrived} took, in log order, into the
|
|
614
|
+
* currently-open frame.
|
|
615
|
+
*
|
|
616
|
+
* Going back through the executor is the point rather than an implementation
|
|
617
|
+
* detail: it captures each record's prior bytes again as it applies, so the
|
|
618
|
+
* reopened frame describes what is now live and the next rewind can undo it.
|
|
619
|
+
* A record whose action class is no longer registered is skipped — the same
|
|
620
|
+
* thing `Replicator.#apply_groups` does on the way in.
|
|
621
|
+
*
|
|
622
|
+
* @private
|
|
623
|
+
*/
|
|
624
|
+
#execute_harvested() {
|
|
625
|
+
const payloads = this.#harvest_payloads;
|
|
626
|
+
|
|
627
|
+
for (let record = 0; record < this.#harvest_count; record++) {
|
|
628
|
+
const i = record * HARVEST_STRIDE;
|
|
629
|
+
const type_id = this.#harvest_index[i];
|
|
630
|
+
const sender_id = this.#harvest_index[i + 1];
|
|
631
|
+
const payload_start = this.#harvest_index[i + 2];
|
|
632
|
+
|
|
633
|
+
const klass = this.action_registry.klass_for(type_id);
|
|
634
|
+
if (klass === undefined) continue;
|
|
635
|
+
|
|
636
|
+
const action = this.action_registry.acquire(klass);
|
|
637
|
+
try {
|
|
638
|
+
payloads.position = payload_start;
|
|
639
|
+
action.deserialize(payloads);
|
|
640
|
+
// `execute` returns false when an authorization policy rejects
|
|
641
|
+
// the action, in which case nothing was applied or logged — so
|
|
642
|
+
// the counter follows the return value rather than the attempt.
|
|
643
|
+
if (this.executor.execute(action, sender_id)) {
|
|
644
|
+
this.replayed_arrived_count++;
|
|
645
|
+
}
|
|
646
|
+
} finally {
|
|
647
|
+
this.action_registry.release(action);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* @private
|
|
654
|
+
*/
|
|
655
|
+
#grow_harvest_index() {
|
|
656
|
+
const old = this.#harvest_index;
|
|
657
|
+
const next = new Int32Array(old.length * 2);
|
|
658
|
+
next.set(old);
|
|
659
|
+
this.#harvest_index = next;
|
|
660
|
+
}
|
|
661
|
+
|
|
419
662
|
/**
|
|
420
663
|
* Helper for the {@link onComputeExpected} handler: stash the
|
|
421
664
|
* computed scalar so the orchestrator can read it. Called inside the
|