@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.
Files changed (154) hide show
  1. package/package.json +1 -1
  2. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +48 -0
  3. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  4. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +246 -3
  5. package/src/shade/RENDERER_CONTRACT.md +584 -580
  6. package/src/shade/playground/avboit/README.md +117 -0
  7. package/src/shade/playground/avboit/index.html +215 -0
  8. package/src/shade/playground/avboit/main.d.ts +2 -0
  9. package/src/shade/playground/avboit/main.d.ts.map +1 -0
  10. package/src/shade/playground/avboit/main.js +875 -0
  11. package/src/shade/renderer/Renderer.d.ts +16 -0
  12. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  13. package/src/shade/renderer/Renderer.js +29 -28
  14. package/src/shade/renderer/ShadeTransparencyMode.d.ts +13 -0
  15. package/src/shade/renderer/ShadeTransparencyMode.d.ts.map +1 -0
  16. package/src/shade/renderer/ShadeTransparencyMode.js +30 -0
  17. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts +9 -0
  18. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.d.ts.map +1 -1
  19. package/src/shade/renderer/path_tracer/accumulating/AccumulatingPathTracer.js +26 -0
  20. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts +7 -2
  21. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  22. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +12 -1
  23. package/src/shade/renderer/rasterize/expand/shader_filter_triangle_by_material_side.js +1 -1
  24. package/src/shade/renderer/rasterize/forward/FORWARD_VERTEX_SHADER_STRUCT.d.ts +3 -0
  25. package/src/shade/renderer/rasterize/forward/FORWARD_VERTEX_SHADER_STRUCT.d.ts.map +1 -0
  26. package/src/shade/renderer/rasterize/{native/oit → forward}/FORWARD_VERTEX_SHADER_STRUCT.js +4 -4
  27. package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.d.ts +3 -0
  28. package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.d.ts.map +1 -0
  29. package/src/shade/renderer/rasterize/forward/SHADING_SCENE_RESOURCE_GROUP.js +17 -0
  30. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_brick4.d.ts +1 -1
  31. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -0
  32. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_brick4.js +16 -16
  33. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_ibl.d.ts +1 -1
  34. package/src/shade/renderer/rasterize/forward/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -0
  35. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_forward_shade_standard_fragment_ibl.js +17 -17
  36. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_material_side_should_draw.d.ts +1 -1
  37. package/src/shade/renderer/rasterize/forward/chunk_material_side_should_draw.d.ts.map +1 -0
  38. package/src/shade/renderer/rasterize/{native/oit → forward}/chunk_material_side_should_draw.js +2 -2
  39. package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.d.ts +3 -0
  40. package/src/shade/renderer/rasterize/forward/vertex_shader_chunk.d.ts.map +1 -0
  41. package/src/shade/renderer/rasterize/{native/oit → forward}/vertex_shader_chunk.js +4 -4
  42. package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.d.ts +27 -0
  43. package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.d.ts.map +1 -0
  44. package/src/shade/renderer/rasterize/native/avboit/AVBOITSettings.js +27 -0
  45. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts +107 -0
  46. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts.map +1 -0
  47. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.js +120 -0
  48. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +463 -0
  49. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +509 -0
  50. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.d.ts +15 -0
  51. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.d.ts.map +1 -0
  52. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_WARP_STRUCT.js +33 -0
  53. package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.d.ts +20 -0
  54. package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.d.ts.map +1 -0
  55. package/src/shade/renderer/rasterize/native/avboit/avboit_build_depth_curve.js +38 -0
  56. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.d.ts +38 -0
  57. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.d.ts.map +1 -0
  58. package/src/shade/renderer/rasterize/native/avboit/avboit_draw_fragment_body.js +130 -0
  59. package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.d.ts +37 -0
  60. package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.d.ts.map +1 -0
  61. package/src/shade/renderer/rasterize/native/avboit/avboit_spec_support.js +73 -0
  62. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.d.ts +9 -0
  63. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.d.ts.map +1 -0
  64. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.js +17 -0
  65. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.d.ts +11 -0
  66. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.d.ts.map +1 -0
  67. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_dilate_word.js +18 -0
  68. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.d.ts +9 -0
  69. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.d.ts.map +1 -0
  70. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_counts.js +17 -0
  71. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.d.ts +9 -0
  72. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.d.ts.map +1 -0
  73. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_extinction_to_transmittance.js +17 -0
  74. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.d.ts +15 -0
  75. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.d.ts.map +1 -0
  76. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_fold_pairs.js +25 -0
  77. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.d.ts +17 -0
  78. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.d.ts.map +1 -0
  79. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_integral_depth_coordinate.js +35 -0
  80. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.d.ts +11 -0
  81. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.d.ts.map +1 -0
  82. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_range.js +21 -0
  83. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.d.ts +9 -0
  84. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.d.ts.map +1 -0
  85. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_occupancy_word_mask.js +26 -0
  86. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.d.ts +11 -0
  87. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.d.ts.map +1 -0
  88. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_overflows.js +22 -0
  89. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.d.ts +10 -0
  90. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.d.ts.map +1 -0
  91. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_pack_counts.js +21 -0
  92. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.d.ts +10 -0
  93. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.d.ts.map +1 -0
  94. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_sample_transmittance.js +32 -0
  95. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.d.ts +20 -0
  96. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.d.ts.map +1 -0
  97. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmission_tint.js +27 -0
  98. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.d.ts +10 -0
  99. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.d.ts.map +1 -0
  100. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_transmittance_to_extinction.js +18 -0
  101. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.d.ts +9 -0
  102. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.d.ts.map +1 -0
  103. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_unpack_counts.js +21 -0
  104. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.d.ts +9 -0
  105. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.d.ts.map +1 -0
  106. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_physical.js +23 -0
  107. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.d.ts +13 -0
  108. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.d.ts.map +1 -0
  109. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_view_depth_to_virtual.js +27 -0
  110. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.d.ts +22 -0
  111. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.d.ts.map +1 -0
  112. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_warp.js +38 -0
  113. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +117 -0
  114. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -0
  115. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +935 -0
  116. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.d.ts +8 -0
  117. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.d.ts.map +1 -0
  118. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_build_warp.js +135 -0
  119. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.d.ts +3 -0
  120. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.d.ts.map +1 -0
  121. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_brick4.js +101 -0
  122. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.d.ts +3 -0
  123. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.d.ts.map +1 -0
  124. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_draw_ibl.js +96 -0
  125. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.d.ts +3 -0
  126. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.d.ts.map +1 -0
  127. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_integrate.js +83 -0
  128. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.d.ts +9 -0
  129. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.d.ts.map +1 -0
  130. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_occupancy.js +119 -0
  131. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts +3 -0
  132. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts.map +1 -0
  133. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.js +81 -0
  134. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.d.ts +15 -0
  135. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.d.ts.map +1 -0
  136. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_splat.js +237 -0
  137. package/src/shade/renderer/rasterize/native/material_draw_indirect.d.ts +22 -0
  138. package/src/shade/renderer/rasterize/native/material_draw_indirect.d.ts.map +1 -0
  139. package/src/shade/renderer/rasterize/native/material_draw_indirect.js +69 -0
  140. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
  141. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +665 -722
  142. package/src/shade/renderer/rasterize/native/oit/shader_oit_generate_moments.js +3 -3
  143. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments.js +4 -4
  144. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +4 -4
  145. package/src/shade/renderer/rasterize/native/oit/FORWARD_VERTEX_SHADER_STRUCT.d.ts +0 -3
  146. package/src/shade/renderer/rasterize/native/oit/FORWARD_VERTEX_SHADER_STRUCT.d.ts.map +0 -1
  147. package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.d.ts +0 -3
  148. package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.d.ts.map +0 -1
  149. package/src/shade/renderer/rasterize/native/oit/SHADING_SCENE_RESOURCE_GROUP.js +0 -17
  150. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.d.ts.map +0 -1
  151. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.d.ts.map +0 -1
  152. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.d.ts.map +0 -1
  153. package/src/shade/renderer/rasterize/native/oit/vertex_shader_chunk.d.ts +0 -3
  154. package/src/shade/renderer/rasterize/native/oit/vertex_shader_chunk.d.ts.map +0 -1
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Advanced 3D game engine",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "3.14.6",
9
+ "version": "3.15.0",
10
10
  "exports": {
11
11
  "./build/*": "./build/*",
12
12
  "./src/*": "./src/*",
@@ -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":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH;IAsBI;;;;;;;;;;;;;;;;;;;;;;;;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,EA6LH;IAjND,qBAAqB;IACrB,iBADW,MAAM,CACG;IAEpB,qBAAqB;IACrB,oBADW,MAAM,CACM;IAyCnB,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;IAQ3C;;;;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,gBAKhB;IAiFD;;;;;OAKG;IACH,oBAFW,MAAM,QAIhB;IAED;;;;OAIG;IACH,oBAFW,MAAM,QAIhB;;CACJ;4BA7a2B,kBAAkB;0BAFpB,uBAAuB;6BADpB,wBAAwB;6BAExB,yBAAyB;mBAJnC,uCAAuC"}
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
- // The action_log probably rolled past server_frame. The
387
- // server would normally back-fill us via STATE_BURST; for now
388
- // skip this reconciliation.
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