quake2ts 0.0.511 → 0.0.513

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 (32) hide show
  1. package/package.json +1 -1
  2. package/packages/client/dist/browser/index.global.js +17 -17
  3. package/packages/client/dist/browser/index.global.js.map +1 -1
  4. package/packages/client/dist/cjs/index.cjs +168 -38
  5. package/packages/client/dist/cjs/index.cjs.map +1 -1
  6. package/packages/client/dist/esm/index.js +168 -38
  7. package/packages/client/dist/esm/index.js.map +1 -1
  8. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  9. package/packages/client/dist/types/demo/handler.d.ts +0 -1
  10. package/packages/client/dist/types/demo/handler.d.ts.map +1 -1
  11. package/packages/engine/dist/browser/index.global.js +17 -17
  12. package/packages/engine/dist/browser/index.global.js.map +1 -1
  13. package/packages/engine/dist/cjs/index.cjs +461 -9
  14. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  15. package/packages/engine/dist/esm/index.js +456 -9
  16. package/packages/engine/dist/esm/index.js.map +1 -1
  17. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  18. package/packages/engine/dist/types/assets/manager.d.ts +3 -0
  19. package/packages/engine/dist/types/assets/manager.d.ts.map +1 -1
  20. package/packages/engine/dist/types/assets/pakWriter.d.ts +8 -0
  21. package/packages/engine/dist/types/assets/pakWriter.d.ts.map +1 -0
  22. package/packages/engine/dist/types/assets/resourceTracker.d.ts +33 -0
  23. package/packages/engine/dist/types/assets/resourceTracker.d.ts.map +1 -0
  24. package/packages/engine/dist/types/demo/clipper.d.ts +22 -0
  25. package/packages/engine/dist/types/demo/clipper.d.ts.map +1 -0
  26. package/packages/engine/dist/types/demo/delta.d.ts +3 -0
  27. package/packages/engine/dist/types/demo/delta.d.ts.map +1 -0
  28. package/packages/engine/dist/types/demo/playback.d.ts +40 -0
  29. package/packages/engine/dist/types/demo/playback.d.ts.map +1 -1
  30. package/packages/engine/dist/types/index.d.ts +4 -0
  31. package/packages/engine/dist/types/index.d.ts.map +1 -1
  32. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
@@ -49,6 +49,7 @@ __export(index_exports, {
49
49
  CvarRegistry: () => CvarRegistry,
50
50
  DemoAnalyzer: () => DemoAnalyzer,
51
51
  DemoCameraMode: () => DemoCameraMode,
52
+ DemoClipper: () => DemoClipper,
52
53
  DemoEventType: () => DemoEventType,
53
54
  DemoPlaybackController: () => DemoPlaybackController,
54
55
  DemoReader: () => DemoReader,
@@ -88,10 +89,13 @@ __export(index_exports, {
88
89
  PakParseError: () => PakParseError,
89
90
  PakValidationError: () => PakValidationError,
90
91
  PakValidator: () => PakValidator,
92
+ PakWriter: () => PakWriter,
91
93
  ParticleRenderer: () => ParticleRenderer,
92
94
  ParticleSystem: () => ParticleSystem,
93
95
  PlaybackState: () => PlaybackState,
94
96
  RERELEASE_KNOWN_PAKS: () => RERELEASE_KNOWN_PAKS,
97
+ ResourceLoadTracker: () => ResourceLoadTracker,
98
+ ResourceType: () => ResourceType,
95
99
  SKYBOX_FRAGMENT_SHADER: () => SKYBOX_FRAGMENT_SHADER,
96
100
  SKYBOX_VERTEX_SHADER: () => SKYBOX_VERTEX_SHADER,
97
101
  SOUND_FULLVOLUME: () => SOUND_FULLVOLUME,
@@ -141,6 +145,7 @@ __export(index_exports, {
141
145
  VertexBuffer: () => VertexBuffer,
142
146
  VirtualFileSystem: () => VirtualFileSystem,
143
147
  advanceAnimation: () => advanceAnimation,
148
+ applyEntityDelta: () => applyEntityDelta,
144
149
  applySurfaceState: () => applySurfaceState,
145
150
  attenuationToDistanceMultiplier: () => attenuationToDistanceMultiplier,
146
151
  boxIntersectsFrustum: () => boxIntersectsFrustum,
@@ -2012,7 +2017,7 @@ var PakArchive = class _PakArchive {
2012
2017
  if (dirOffset < HEADER_SIZE) {
2013
2018
  throw new PakParseError(`Invalid directory offset: ${dirOffset}`);
2014
2019
  }
2015
- if (dirLength <= 0 || dirLength % DIRECTORY_ENTRY_SIZE !== 0) {
2020
+ if (dirLength < 0 || dirLength % DIRECTORY_ENTRY_SIZE !== 0) {
2016
2021
  throw new PakParseError(`Invalid directory length: ${dirLength}`);
2017
2022
  }
2018
2023
  const dirEnd = dirOffset + dirLength;
@@ -2066,6 +2071,136 @@ function calculatePakChecksum(buffer) {
2066
2071
  return crc32(new Uint8Array(buffer));
2067
2072
  }
2068
2073
 
2074
+ // src/assets/pakWriter.ts
2075
+ var HEADER_SIZE2 = 12;
2076
+ var DIRECTORY_ENTRY_SIZE2 = 64;
2077
+ var PakWriter = class _PakWriter {
2078
+ constructor() {
2079
+ this.files = /* @__PURE__ */ new Map();
2080
+ }
2081
+ addFile(path, data) {
2082
+ const normalized = normalizePath(path);
2083
+ if (!normalized) {
2084
+ throw new Error(`Invalid path: ${path}`);
2085
+ }
2086
+ if (normalized.length > 56) {
2087
+ throw new Error(`Path too long: ${path} (max 56 chars)`);
2088
+ }
2089
+ this.files.set(normalized, data);
2090
+ }
2091
+ removeFile(path) {
2092
+ const normalized = normalizePath(path);
2093
+ return this.files.delete(normalized);
2094
+ }
2095
+ build() {
2096
+ let currentOffset = HEADER_SIZE2;
2097
+ const sortedPaths = Array.from(this.files.keys()).sort();
2098
+ const directorySize = sortedPaths.length * DIRECTORY_ENTRY_SIZE2;
2099
+ let fileDataSize = 0;
2100
+ for (const path of sortedPaths) {
2101
+ fileDataSize += this.files.get(path).byteLength;
2102
+ }
2103
+ const totalSize = HEADER_SIZE2 + fileDataSize + directorySize;
2104
+ const buffer = new Uint8Array(totalSize);
2105
+ const view = new DataView(buffer.buffer);
2106
+ view.setUint8(0, "P".charCodeAt(0));
2107
+ view.setUint8(1, "A".charCodeAt(0));
2108
+ view.setUint8(2, "C".charCodeAt(0));
2109
+ view.setUint8(3, "K".charCodeAt(0));
2110
+ const directoryOffset = HEADER_SIZE2 + fileDataSize;
2111
+ view.setInt32(4, directoryOffset, true);
2112
+ view.setInt32(8, directorySize, true);
2113
+ let fileOffset = HEADER_SIZE2;
2114
+ let dirEntryOffset = directoryOffset;
2115
+ for (const path of sortedPaths) {
2116
+ const data = this.files.get(path);
2117
+ buffer.set(data, fileOffset);
2118
+ for (let i = 0; i < 56; i++) {
2119
+ if (i < path.length) {
2120
+ view.setUint8(dirEntryOffset + i, path.charCodeAt(i));
2121
+ } else {
2122
+ view.setUint8(dirEntryOffset + i, 0);
2123
+ }
2124
+ }
2125
+ view.setInt32(dirEntryOffset + 56, fileOffset, true);
2126
+ view.setInt32(dirEntryOffset + 60, data.byteLength, true);
2127
+ fileOffset += data.byteLength;
2128
+ dirEntryOffset += DIRECTORY_ENTRY_SIZE2;
2129
+ }
2130
+ return buffer;
2131
+ }
2132
+ static buildFromEntries(entries) {
2133
+ const writer = new _PakWriter();
2134
+ for (const [path, data] of entries) {
2135
+ writer.addFile(path, data);
2136
+ }
2137
+ return writer.build();
2138
+ }
2139
+ };
2140
+
2141
+ // src/assets/resourceTracker.ts
2142
+ var ResourceType = /* @__PURE__ */ ((ResourceType2) => {
2143
+ ResourceType2["Texture"] = "texture";
2144
+ ResourceType2["Sound"] = "sound";
2145
+ ResourceType2["Model"] = "model";
2146
+ ResourceType2["Map"] = "map";
2147
+ ResourceType2["Sprite"] = "sprite";
2148
+ ResourceType2["ConfigString"] = "configString";
2149
+ return ResourceType2;
2150
+ })(ResourceType || {});
2151
+ var ResourceLoadTracker = class {
2152
+ constructor() {
2153
+ this.tracking = false;
2154
+ this.entries = [];
2155
+ this.currentFrame = 0;
2156
+ this.currentTime = 0;
2157
+ }
2158
+ startTracking() {
2159
+ this.tracking = true;
2160
+ this.entries = [];
2161
+ }
2162
+ stopTracking() {
2163
+ this.tracking = false;
2164
+ const log = {
2165
+ byFrame: /* @__PURE__ */ new Map(),
2166
+ byTime: /* @__PURE__ */ new Map(),
2167
+ uniqueResources: /* @__PURE__ */ new Map()
2168
+ };
2169
+ for (const entry of this.entries) {
2170
+ if (!log.byFrame.has(entry.frame)) {
2171
+ log.byFrame.set(entry.frame, []);
2172
+ }
2173
+ log.byFrame.get(entry.frame).push(entry);
2174
+ if (!log.byTime.has(entry.timestamp)) {
2175
+ log.byTime.set(entry.timestamp, []);
2176
+ }
2177
+ log.byTime.get(entry.timestamp).push(entry);
2178
+ const key = `${entry.type}:${entry.path}`;
2179
+ if (!log.uniqueResources.has(key)) {
2180
+ log.uniqueResources.set(key, entry);
2181
+ }
2182
+ }
2183
+ return log;
2184
+ }
2185
+ recordLoad(type, path, size, pakSource) {
2186
+ if (!this.tracking) return;
2187
+ this.entries.push({
2188
+ type,
2189
+ path,
2190
+ timestamp: this.currentTime,
2191
+ frame: this.currentFrame,
2192
+ size,
2193
+ pakSource
2194
+ });
2195
+ }
2196
+ setCurrentFrame(frame) {
2197
+ this.currentFrame = frame;
2198
+ }
2199
+ setCurrentTime(time) {
2200
+ this.currentTime = time;
2201
+ }
2202
+ };
2203
+
2069
2204
  // src/assets/vfs.ts
2070
2205
  var VirtualFileSystem = class {
2071
2206
  constructor(archives = []) {
@@ -2590,7 +2725,7 @@ function wireFileInput(input, handler) {
2590
2725
  var BSP_MAGIC = "IBSP";
2591
2726
  var BSP_VERSION = 38;
2592
2727
  var HEADER_LUMPS = 19;
2593
- var HEADER_SIZE2 = 4 + 4 + HEADER_LUMPS * 8;
2728
+ var HEADER_SIZE3 = 4 + 4 + HEADER_LUMPS * 8;
2594
2729
  var BspParseError = class extends Error {
2595
2730
  };
2596
2731
  var BspLoader = class {
@@ -2605,7 +2740,7 @@ var BspLoader = class {
2605
2740
  }
2606
2741
  };
2607
2742
  function parseBsp(buffer) {
2608
- if (buffer.byteLength < HEADER_SIZE2) {
2743
+ if (buffer.byteLength < HEADER_SIZE3) {
2609
2744
  throw new BspParseError("BSP too small to contain header");
2610
2745
  }
2611
2746
  const view = new DataView(buffer);
@@ -3058,7 +3193,7 @@ function createFaceLightmap(face, lightMaps, info) {
3058
3193
  // src/assets/md2.ts
3059
3194
  var MD2_MAGIC = 844121161;
3060
3195
  var MD2_VERSION = 8;
3061
- var HEADER_SIZE3 = 17 * 4;
3196
+ var HEADER_SIZE4 = 17 * 4;
3062
3197
  var MD2_NORMALS = [
3063
3198
  { x: -0.525731, y: 0, z: 0.850651 },
3064
3199
  { x: -0.442863, y: 0.238856, z: 0.864188 },
@@ -3256,12 +3391,12 @@ function readCString2(view, offset, maxLength) {
3256
3391
  }
3257
3392
  function validateSection(buffer, offset, length, label) {
3258
3393
  if (length === 0) return;
3259
- if (offset < HEADER_SIZE3 || offset + length > buffer.byteLength) {
3394
+ if (offset < HEADER_SIZE4 || offset + length > buffer.byteLength) {
3260
3395
  throw new Md2ParseError(`${label} section is out of bounds`);
3261
3396
  }
3262
3397
  }
3263
3398
  function parseHeader(buffer) {
3264
- if (buffer.byteLength < HEADER_SIZE3) {
3399
+ if (buffer.byteLength < HEADER_SIZE4) {
3265
3400
  throw new Md2ParseError("MD2 buffer too small to contain header");
3266
3401
  }
3267
3402
  const view = new DataView(buffer);
@@ -3698,7 +3833,7 @@ var Md3Loader = class {
3698
3833
  var IDSPRITEHEADER = 844317769;
3699
3834
  var SPRITE_VERSION = 2;
3700
3835
  var MAX_SKINNAME = 64;
3701
- var HEADER_SIZE4 = 12;
3836
+ var HEADER_SIZE5 = 12;
3702
3837
  var SpriteParseError = class extends Error {
3703
3838
  };
3704
3839
  function readCString3(view, offset, maxLength) {
@@ -3711,7 +3846,7 @@ function readCString3(view, offset, maxLength) {
3711
3846
  return String.fromCharCode(...chars);
3712
3847
  }
3713
3848
  function parseSprite(buffer) {
3714
- if (buffer.byteLength < HEADER_SIZE4) {
3849
+ if (buffer.byteLength < HEADER_SIZE5) {
3715
3850
  throw new SpriteParseError("Sprite buffer too small to contain header");
3716
3851
  }
3717
3852
  const view = new DataView(buffer);
@@ -3726,7 +3861,7 @@ function parseSprite(buffer) {
3726
3861
  }
3727
3862
  const frames = [];
3728
3863
  const frameSize = 16 + MAX_SKINNAME;
3729
- let offset = HEADER_SIZE4;
3864
+ let offset = HEADER_SIZE5;
3730
3865
  for (let i = 0; i < numFrames; i += 1) {
3731
3866
  if (offset + frameSize > buffer.byteLength) {
3732
3867
  throw new SpriteParseError("Sprite frame data exceeds buffer length");
@@ -4442,6 +4577,7 @@ var AssetManager = class {
4442
4577
  this.textures = new TextureCache({ capacity: options.textureCacheCapacity ?? 128 });
4443
4578
  this.audio = new AudioRegistry(vfs, { cacheSize: options.audioCacheSize ?? 64 });
4444
4579
  this.dependencyTracker = options.dependencyTracker ?? new AssetDependencyTracker();
4580
+ this.resourceTracker = options.resourceTracker;
4445
4581
  this.md2 = new Md2Loader(vfs);
4446
4582
  this.md3 = new Md3Loader(vfs);
4447
4583
  this.sprite = new SpriteLoader(vfs);
@@ -4478,6 +4614,9 @@ var AssetManager = class {
4478
4614
  this.dependencyTracker.markLoaded(key);
4479
4615
  }
4480
4616
  async loadTexture(path) {
4617
+ if (this.resourceTracker) {
4618
+ this.resourceTracker.recordLoad("texture" /* Texture */, path);
4619
+ }
4481
4620
  const cached = this.textures.get(path);
4482
4621
  if (cached) return cached;
4483
4622
  const buffer = await this.vfs.readFile(path);
@@ -4496,6 +4635,9 @@ var AssetManager = class {
4496
4635
  return texture;
4497
4636
  }
4498
4637
  async loadSound(path) {
4638
+ if (this.resourceTracker) {
4639
+ this.resourceTracker.recordLoad("sound" /* Sound */, path);
4640
+ }
4499
4641
  const audio = await this.audio.load(path);
4500
4642
  const key = this.makeKey("sound", path);
4501
4643
  this.dependencyTracker.register(key);
@@ -4503,6 +4645,9 @@ var AssetManager = class {
4503
4645
  return audio;
4504
4646
  }
4505
4647
  async loadMd2Model(path, textureDependencies = []) {
4648
+ if (this.resourceTracker) {
4649
+ this.resourceTracker.recordLoad("model" /* Model */, path);
4650
+ }
4506
4651
  const modelKey = this.makeKey("model", path);
4507
4652
  const dependencyKeys = textureDependencies.map((dep) => this.makeKey("texture", dep));
4508
4653
  this.dependencyTracker.register(modelKey, dependencyKeys);
@@ -4515,9 +4660,15 @@ var AssetManager = class {
4515
4660
  return model;
4516
4661
  }
4517
4662
  getMd2Model(path) {
4663
+ if (this.resourceTracker) {
4664
+ this.resourceTracker.recordLoad("model" /* Model */, path);
4665
+ }
4518
4666
  return this.md2.get(path);
4519
4667
  }
4520
4668
  async loadMd3Model(path, textureDependencies = []) {
4669
+ if (this.resourceTracker) {
4670
+ this.resourceTracker.recordLoad("model" /* Model */, path);
4671
+ }
4521
4672
  const modelKey = this.makeKey("model", path);
4522
4673
  const dependencyKeys = textureDependencies.map((dep) => this.makeKey("texture", dep));
4523
4674
  this.dependencyTracker.register(modelKey, dependencyKeys);
@@ -4530,9 +4681,15 @@ var AssetManager = class {
4530
4681
  return model;
4531
4682
  }
4532
4683
  getMd3Model(path) {
4684
+ if (this.resourceTracker) {
4685
+ this.resourceTracker.recordLoad("model" /* Model */, path);
4686
+ }
4533
4687
  return this.md3.get(path);
4534
4688
  }
4535
4689
  async loadSprite(path) {
4690
+ if (this.resourceTracker) {
4691
+ this.resourceTracker.recordLoad("sprite" /* Sprite */, path);
4692
+ }
4536
4693
  const spriteKey = this.makeKey("sprite", path);
4537
4694
  this.dependencyTracker.register(spriteKey);
4538
4695
  const sprite = await this.sprite.load(path);
@@ -4540,6 +4697,9 @@ var AssetManager = class {
4540
4697
  return sprite;
4541
4698
  }
4542
4699
  async loadMap(path) {
4700
+ if (this.resourceTracker) {
4701
+ this.resourceTracker.recordLoad("map" /* Map */, path);
4702
+ }
4543
4703
  const mapKey = this.makeKey("map", path);
4544
4704
  if (this.maps.has(path)) {
4545
4705
  return this.maps.get(path);
@@ -13607,6 +13767,8 @@ var DemoPlaybackController = class {
13607
13767
  this.cachedStatistics = null;
13608
13768
  this.cachedPlayerStats = null;
13609
13769
  this.cachedWeaponStats = null;
13770
+ // Resource Tracking
13771
+ this.tracker = null;
13610
13772
  // Camera State
13611
13773
  this.cameraMode = 0 /* FirstPerson */;
13612
13774
  this.thirdPersonDistance = 80;
@@ -13720,6 +13882,41 @@ var DemoPlaybackController = class {
13720
13882
  seekToFrame(frameIndex) {
13721
13883
  this.seek(frameIndex);
13722
13884
  }
13885
+ frameToTime(frame) {
13886
+ return frame * this.frameDuration / 1e3;
13887
+ }
13888
+ timeToFrame(seconds) {
13889
+ return Math.floor(seconds * 1e3 / this.frameDuration);
13890
+ }
13891
+ playFrom(offset) {
13892
+ if (offset.type === "frame") {
13893
+ this.seek(offset.frame);
13894
+ } else {
13895
+ this.seekToTime(offset.seconds);
13896
+ }
13897
+ this.play();
13898
+ }
13899
+ playRange(start, end) {
13900
+ this.playFrom(start);
13901
+ const endFrame = end.type === "frame" ? end.frame : this.timeToFrame(end.seconds);
13902
+ const originalOnFrameUpdate = this.callbacks?.onFrameUpdate;
13903
+ const rangeCallback = {
13904
+ ...this.callbacks,
13905
+ onFrameUpdate: (frame) => {
13906
+ if (originalOnFrameUpdate) originalOnFrameUpdate(frame);
13907
+ if (this.currentFrameIndex >= endFrame) {
13908
+ this.pause();
13909
+ if (this.callbacks === rangeCallback) {
13910
+ this.setCallbacks({ ...this.callbacks, onFrameUpdate: originalOnFrameUpdate });
13911
+ }
13912
+ if (this.callbacks?.onPlaybackComplete) {
13913
+ this.callbacks.onPlaybackComplete();
13914
+ }
13915
+ }
13916
+ }
13917
+ };
13918
+ this.setCallbacks(rangeCallback);
13919
+ }
13723
13920
  /**
13724
13921
  * Seeks to a specific frame number.
13725
13922
  */
@@ -13797,6 +13994,10 @@ var DemoPlaybackController = class {
13797
13994
  return false;
13798
13995
  }
13799
13996
  this.currentFrameIndex++;
13997
+ if (this.tracker) {
13998
+ this.tracker.setCurrentFrame(this.currentFrameIndex);
13999
+ this.tracker.setCurrentTime(this.getCurrentTime());
14000
+ }
13800
14001
  try {
13801
14002
  const proxyHandler = {
13802
14003
  ...this.handler,
@@ -14031,6 +14232,93 @@ var DemoPlaybackController = class {
14031
14232
  setThirdPersonOffset(offset) {
14032
14233
  this.thirdPersonOffset = offset;
14033
14234
  }
14235
+ async playWithTracking(tracker, options = {}) {
14236
+ this.tracker = tracker;
14237
+ tracker.startTracking();
14238
+ if (options.fastForward) {
14239
+ try {
14240
+ if (this.state === 0 /* Stopped */ && this.reader) {
14241
+ }
14242
+ this.transitionState(1 /* Playing */);
14243
+ const CHUNK_SIZE = 100;
14244
+ const processChunk = async () => {
14245
+ if (this.state !== 1 /* Playing */) {
14246
+ throw new Error("Playback stopped unexpectedly during fast forward");
14247
+ }
14248
+ let count = 0;
14249
+ while (count < CHUNK_SIZE) {
14250
+ if (!this.processNextFrame()) {
14251
+ const log = tracker.stopTracking();
14252
+ this.tracker = null;
14253
+ if (this.callbacks?.onPlaybackComplete) this.callbacks.onPlaybackComplete();
14254
+ return log;
14255
+ }
14256
+ count++;
14257
+ }
14258
+ await new Promise((resolve) => setTimeout(resolve, 0));
14259
+ return processChunk();
14260
+ };
14261
+ return await processChunk();
14262
+ } catch (e) {
14263
+ tracker.stopTracking();
14264
+ this.tracker = null;
14265
+ throw e;
14266
+ }
14267
+ } else {
14268
+ return new Promise((resolve, reject) => {
14269
+ const originalComplete = this.callbacks?.onPlaybackComplete;
14270
+ const originalError = this.callbacks?.onPlaybackError;
14271
+ const cleanup = () => {
14272
+ this.setCallbacks({ ...this.callbacks, onPlaybackComplete: originalComplete, onPlaybackError: originalError });
14273
+ this.tracker = null;
14274
+ };
14275
+ this.setCallbacks({
14276
+ ...this.callbacks,
14277
+ onPlaybackComplete: () => {
14278
+ const log = tracker.stopTracking();
14279
+ if (originalComplete) originalComplete();
14280
+ cleanup();
14281
+ resolve(log);
14282
+ },
14283
+ onPlaybackError: (err2) => {
14284
+ tracker.stopTracking();
14285
+ if (originalError) originalError(err2);
14286
+ cleanup();
14287
+ reject(err2);
14288
+ }
14289
+ });
14290
+ this.play();
14291
+ });
14292
+ }
14293
+ }
14294
+ async playRangeWithTracking(start, end, tracker) {
14295
+ this.tracker = tracker;
14296
+ tracker.startTracking();
14297
+ return new Promise((resolve, reject) => {
14298
+ const originalComplete = this.callbacks?.onPlaybackComplete;
14299
+ const originalError = this.callbacks?.onPlaybackError;
14300
+ const cleanup = () => {
14301
+ this.setCallbacks({ ...this.callbacks, onPlaybackComplete: originalComplete, onPlaybackError: originalError });
14302
+ this.tracker = null;
14303
+ };
14304
+ this.setCallbacks({
14305
+ ...this.callbacks,
14306
+ onPlaybackComplete: () => {
14307
+ const log = tracker.stopTracking();
14308
+ if (originalComplete) originalComplete();
14309
+ cleanup();
14310
+ resolve(log);
14311
+ },
14312
+ onPlaybackError: (err2) => {
14313
+ tracker.stopTracking();
14314
+ if (originalError) originalError(err2);
14315
+ cleanup();
14316
+ reject(err2);
14317
+ }
14318
+ });
14319
+ this.playRange(start, end);
14320
+ });
14321
+ }
14034
14322
  };
14035
14323
 
14036
14324
  // src/demo/recorder.ts
@@ -14106,6 +14394,165 @@ var DemoValidator = class {
14106
14394
  }
14107
14395
  };
14108
14396
 
14397
+ // src/demo/delta.ts
14398
+ function applyEntityDelta(to, from) {
14399
+ const bits = from.bits;
14400
+ const bitsHigh = from.bitsHigh;
14401
+ to.number = from.number;
14402
+ if (bits & U_MODEL5) to.modelindex = from.modelindex;
14403
+ if (bits & U_MODEL22) to.modelindex2 = from.modelindex2;
14404
+ if (bits & U_MODEL32) to.modelindex3 = from.modelindex3;
14405
+ if (bits & U_MODEL42) to.modelindex4 = from.modelindex4;
14406
+ if (bits & U_FRAME8) to.frame = from.frame;
14407
+ if (bits & U_FRAME16) to.frame = from.frame;
14408
+ if (bits & U_SKIN8 || bits & U_SKIN16) to.skinnum = from.skinnum;
14409
+ if (bits & U_EFFECTS8 || bits & U_EFFECTS16) to.effects = from.effects;
14410
+ if (bits & U_RENDERFX8 || bits & U_RENDERFX16) to.renderfx = from.renderfx;
14411
+ if (bits & U_ORIGIN12) to.origin.x = from.origin.x;
14412
+ if (bits & U_ORIGIN22) to.origin.y = from.origin.y;
14413
+ if (bits & U_ORIGIN32) to.origin.z = from.origin.z;
14414
+ if (bits & U_ANGLE12) to.angles.x = from.angles.x;
14415
+ if (bits & U_ANGLE22) to.angles.y = from.angles.y;
14416
+ if (bits & U_ANGLE32) to.angles.z = from.angles.z;
14417
+ if (bits & U_OLDORIGIN) {
14418
+ to.old_origin.x = from.old_origin.x;
14419
+ to.old_origin.y = from.old_origin.y;
14420
+ to.old_origin.z = from.old_origin.z;
14421
+ }
14422
+ if (bits & U_SOUND2) to.sound = from.sound;
14423
+ if (bits & U_EVENT2) to.event = from.event;
14424
+ if (bits & U_SOLID2) to.solid = from.solid;
14425
+ if (bits & U_ALPHA) to.alpha = from.alpha;
14426
+ if (bits & U_SCALE) to.scale = from.scale;
14427
+ if (bits & U_INSTANCE_BITS) to.instanceBits = from.instanceBits;
14428
+ if (bits & U_LOOP_VOLUME) to.loopVolume = from.loopVolume;
14429
+ if (bitsHigh & U_LOOP_ATTENUATION_HIGH) to.loopAttenuation = from.loopAttenuation;
14430
+ if (bitsHigh & U_OWNER_HIGH) to.owner = from.owner;
14431
+ if (bitsHigh & U_OLD_FRAME_HIGH) to.oldFrame = from.oldFrame;
14432
+ }
14433
+
14434
+ // src/demo/clipper.ts
14435
+ var DemoClipper = class {
14436
+ static extractClip(demoData, startFrame, endFrame) {
14437
+ const reader = new DemoReader(demoData);
14438
+ if (!reader.seekToMessage(startFrame)) {
14439
+ throw new Error(`Start frame ${startFrame} out of bounds`);
14440
+ }
14441
+ const startOffset = reader.getOffset();
14442
+ let endOffset = demoData.byteLength;
14443
+ if (endFrame < reader.getMessageCount()) {
14444
+ if (reader.seekToMessage(endFrame)) {
14445
+ endOffset = reader.getOffset();
14446
+ }
14447
+ }
14448
+ const length = endOffset - startOffset;
14449
+ const result = new Uint8Array(length + 4);
14450
+ const sourceView = new Uint8Array(demoData);
14451
+ result.set(sourceView.subarray(startOffset, endOffset), 0);
14452
+ const view = new DataView(result.buffer);
14453
+ view.setInt32(length, -1, true);
14454
+ return result;
14455
+ }
14456
+ static async captureWorldState(demoData, atFrame) {
14457
+ const reader = new DemoReader(demoData);
14458
+ const configStrings = /* @__PURE__ */ new Map();
14459
+ const entityBaselines = /* @__PURE__ */ new Map();
14460
+ let serverData = null;
14461
+ let playerState = createEmptyProtocolPlayerState();
14462
+ const entities = /* @__PURE__ */ new Map();
14463
+ let currentFrame = -1;
14464
+ const handler = {
14465
+ onServerData: (protocol, serverCount, attractLoop, gameDir, playerNum, levelName, tickRate, demoType) => {
14466
+ serverData = {
14467
+ protocolVersion: protocol,
14468
+ serverCount,
14469
+ attractLoop: attractLoop !== 0,
14470
+ gameDirectory: gameDir,
14471
+ levelName
14472
+ };
14473
+ },
14474
+ onConfigString: (index, str) => {
14475
+ configStrings.set(index, str);
14476
+ },
14477
+ onSpawnBaseline: (entity) => {
14478
+ entityBaselines.set(entity.number, entity);
14479
+ },
14480
+ onFrame: (frameData) => {
14481
+ playerState = frameData.playerState;
14482
+ const packetEntities = frameData.packetEntities;
14483
+ if (!packetEntities.delta) {
14484
+ entities.clear();
14485
+ }
14486
+ for (const partial of packetEntities.entities) {
14487
+ if (partial.bits & U_REMOVE2) {
14488
+ entities.delete(partial.number);
14489
+ continue;
14490
+ }
14491
+ const number = partial.number;
14492
+ let source;
14493
+ if (packetEntities.delta && entities.has(number)) {
14494
+ source = entities.get(number);
14495
+ } else if (entityBaselines.has(number)) {
14496
+ source = entityBaselines.get(number);
14497
+ } else {
14498
+ source = createEmptyEntityState();
14499
+ }
14500
+ const final = structuredClone(source);
14501
+ applyEntityDelta(final, partial);
14502
+ entities.set(number, final);
14503
+ }
14504
+ },
14505
+ onCenterPrint: () => {
14506
+ },
14507
+ onStuffText: () => {
14508
+ },
14509
+ onSound: () => {
14510
+ },
14511
+ onPrint: () => {
14512
+ },
14513
+ onMuzzleFlash: () => {
14514
+ },
14515
+ onMuzzleFlash2: () => {
14516
+ },
14517
+ onTempEntity: () => {
14518
+ },
14519
+ onLayout: () => {
14520
+ },
14521
+ onInventory: () => {
14522
+ },
14523
+ onDisconnect: () => {
14524
+ },
14525
+ onReconnect: () => {
14526
+ },
14527
+ onDownload: () => {
14528
+ }
14529
+ };
14530
+ while (reader.hasMore()) {
14531
+ if (currentFrame >= atFrame) {
14532
+ break;
14533
+ }
14534
+ const block = reader.readNextBlock();
14535
+ if (!block) break;
14536
+ currentFrame++;
14537
+ const parser = new NetworkMessageParser(block.data, handler);
14538
+ const currentProtocol = serverData ? serverData.protocolVersion : 34;
14539
+ parser.setProtocolVersion(currentProtocol);
14540
+ parser.parseMessage();
14541
+ }
14542
+ return {
14543
+ serverData,
14544
+ configStrings,
14545
+ entityBaselines,
14546
+ playerState,
14547
+ entities,
14548
+ gameTime: 0
14549
+ };
14550
+ }
14551
+ static extractStandaloneClip(demoData, startFrame, endFrame, worldState) {
14552
+ throw new Error("extractStandaloneClip not fully implemented: requires NetworkMessageWriter");
14553
+ }
14554
+ };
14555
+
14109
14556
  // src/assets/fileType.ts
14110
14557
  var FileType = /* @__PURE__ */ ((FileType2) => {
14111
14558
  FileType2["Unknown"] = "unknown";
@@ -14484,6 +14931,7 @@ function createEngine(imports) {
14484
14931
  CvarRegistry,
14485
14932
  DemoAnalyzer,
14486
14933
  DemoCameraMode,
14934
+ DemoClipper,
14487
14935
  DemoEventType,
14488
14936
  DemoPlaybackController,
14489
14937
  DemoReader,
@@ -14523,10 +14971,13 @@ function createEngine(imports) {
14523
14971
  PakParseError,
14524
14972
  PakValidationError,
14525
14973
  PakValidator,
14974
+ PakWriter,
14526
14975
  ParticleRenderer,
14527
14976
  ParticleSystem,
14528
14977
  PlaybackState,
14529
14978
  RERELEASE_KNOWN_PAKS,
14979
+ ResourceLoadTracker,
14980
+ ResourceType,
14530
14981
  SKYBOX_FRAGMENT_SHADER,
14531
14982
  SKYBOX_VERTEX_SHADER,
14532
14983
  SOUND_FULLVOLUME,
@@ -14576,6 +15027,7 @@ function createEngine(imports) {
14576
15027
  VertexBuffer,
14577
15028
  VirtualFileSystem,
14578
15029
  advanceAnimation,
15030
+ applyEntityDelta,
14579
15031
  applySurfaceState,
14580
15032
  attenuationToDistanceMultiplier,
14581
15033
  boxIntersectsFrustum,