quake2ts 0.0.7 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +425 -0
  2. package/apps/viewer/dist/browser/index.global.js +1 -1
  3. package/apps/viewer/dist/browser/index.global.js.map +1 -1
  4. package/apps/viewer/dist/cjs/index.cjs +2097 -295
  5. package/apps/viewer/dist/cjs/index.cjs.map +1 -1
  6. package/apps/viewer/dist/esm/index.js +2097 -295
  7. package/apps/viewer/dist/esm/index.js.map +1 -1
  8. package/apps/viewer/dist/tsconfig.tsbuildinfo +1 -1
  9. package/apps/viewer/dist/types/index.d.ts +1 -1
  10. package/package.json +1 -1
  11. package/packages/client/dist/browser/index.global.js +1 -1
  12. package/packages/client/dist/browser/index.global.js.map +1 -1
  13. package/packages/client/dist/cjs/index.cjs +1200 -13
  14. package/packages/client/dist/cjs/index.cjs.map +1 -1
  15. package/packages/client/dist/esm/index.js +1186 -12
  16. package/packages/client/dist/esm/index.js.map +1 -1
  17. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  18. package/packages/client/dist/types/index.d.ts +14 -6
  19. package/packages/client/dist/types/index.d.ts.map +1 -1
  20. package/packages/client/dist/types/input/bindings.d.ts +18 -0
  21. package/packages/client/dist/types/input/bindings.d.ts.map +1 -0
  22. package/packages/client/dist/types/input/command-buffer.d.ts +15 -0
  23. package/packages/client/dist/types/input/command-buffer.d.ts.map +1 -0
  24. package/packages/client/dist/types/input/controller.d.ts +125 -0
  25. package/packages/client/dist/types/input/controller.d.ts.map +1 -0
  26. package/packages/client/dist/types/prediction.d.ts +38 -0
  27. package/packages/client/dist/types/prediction.d.ts.map +1 -0
  28. package/packages/client/dist/types/view-effects.d.ts +41 -0
  29. package/packages/client/dist/types/view-effects.d.ts.map +1 -0
  30. package/packages/engine/dist/browser/index.global.js +257 -1
  31. package/packages/engine/dist/browser/index.global.js.map +1 -1
  32. package/packages/engine/dist/cjs/index.cjs +2408 -2
  33. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  34. package/packages/engine/dist/esm/index.js +2340 -2
  35. package/packages/engine/dist/esm/index.js.map +1 -1
  36. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  37. package/packages/engine/dist/types/assets/animation.d.ts +33 -0
  38. package/packages/engine/dist/types/assets/animation.d.ts.map +1 -0
  39. package/packages/engine/dist/types/assets/audio.d.ts +21 -0
  40. package/packages/engine/dist/types/assets/audio.d.ts.map +1 -0
  41. package/packages/engine/dist/types/assets/bsp.d.ts +1 -1
  42. package/packages/engine/dist/types/assets/bsp.d.ts.map +1 -1
  43. package/packages/engine/dist/types/assets/ingestion.d.ts +31 -0
  44. package/packages/engine/dist/types/assets/ingestion.d.ts.map +1 -1
  45. package/packages/engine/dist/types/assets/manager.d.ts +43 -0
  46. package/packages/engine/dist/types/assets/manager.d.ts.map +1 -0
  47. package/packages/engine/dist/types/assets/md3.d.ts +69 -0
  48. package/packages/engine/dist/types/assets/md3.d.ts.map +1 -0
  49. package/packages/engine/dist/types/assets/ogg.d.ts +12 -0
  50. package/packages/engine/dist/types/assets/ogg.d.ts.map +1 -0
  51. package/packages/engine/dist/types/assets/pakIndexStore.d.ts +19 -0
  52. package/packages/engine/dist/types/assets/pakIndexStore.d.ts.map +1 -0
  53. package/packages/engine/dist/types/assets/pakValidation.d.ts +28 -0
  54. package/packages/engine/dist/types/assets/pakValidation.d.ts.map +1 -0
  55. package/packages/engine/dist/types/assets/pcx.d.ts +13 -0
  56. package/packages/engine/dist/types/assets/pcx.d.ts.map +1 -0
  57. package/packages/engine/dist/types/assets/texture.d.ts +29 -0
  58. package/packages/engine/dist/types/assets/texture.d.ts.map +1 -0
  59. package/packages/engine/dist/types/assets/wal.d.ts +21 -0
  60. package/packages/engine/dist/types/assets/wal.d.ts.map +1 -0
  61. package/packages/engine/dist/types/assets/wav.d.ts +11 -0
  62. package/packages/engine/dist/types/assets/wav.d.ts.map +1 -0
  63. package/packages/engine/dist/types/audio/api.d.ts +29 -0
  64. package/packages/engine/dist/types/audio/api.d.ts.map +1 -0
  65. package/packages/engine/dist/types/audio/channels.d.ts +15 -0
  66. package/packages/engine/dist/types/audio/channels.d.ts.map +1 -0
  67. package/packages/engine/dist/types/audio/constants.d.ts +24 -0
  68. package/packages/engine/dist/types/audio/constants.d.ts.map +1 -0
  69. package/packages/engine/dist/types/audio/context.d.ts +67 -0
  70. package/packages/engine/dist/types/audio/context.d.ts.map +1 -0
  71. package/packages/engine/dist/types/audio/music.d.ts +42 -0
  72. package/packages/engine/dist/types/audio/music.d.ts.map +1 -0
  73. package/packages/engine/dist/types/audio/precache.d.ts +28 -0
  74. package/packages/engine/dist/types/audio/precache.d.ts.map +1 -0
  75. package/packages/engine/dist/types/audio/registry.d.ts +13 -0
  76. package/packages/engine/dist/types/audio/registry.d.ts.map +1 -0
  77. package/packages/engine/dist/types/audio/spatialization.d.ts +14 -0
  78. package/packages/engine/dist/types/audio/spatialization.d.ts.map +1 -0
  79. package/packages/engine/dist/types/audio/system.d.ts +101 -0
  80. package/packages/engine/dist/types/audio/system.d.ts.map +1 -0
  81. package/packages/engine/dist/types/configstrings.d.ts +1 -0
  82. package/packages/engine/dist/types/configstrings.d.ts.map +1 -1
  83. package/packages/engine/dist/types/index.d.ts +26 -1
  84. package/packages/engine/dist/types/index.d.ts.map +1 -1
  85. package/packages/engine/dist/types/render/bspPipeline.d.ts +42 -0
  86. package/packages/engine/dist/types/render/bspPipeline.d.ts.map +1 -0
  87. package/packages/engine/dist/types/render/bspTraversal.d.ts +11 -0
  88. package/packages/engine/dist/types/render/bspTraversal.d.ts.map +1 -0
  89. package/packages/engine/dist/types/render/culling.d.ts +8 -0
  90. package/packages/engine/dist/types/render/culling.d.ts.map +1 -0
  91. package/packages/engine/dist/types/render/md2Pipeline.d.ts +51 -0
  92. package/packages/engine/dist/types/render/md2Pipeline.d.ts.map +1 -0
  93. package/packages/engine/dist/types/render/resources.d.ts +10 -0
  94. package/packages/engine/dist/types/render/resources.d.ts.map +1 -1
  95. package/packages/engine/dist/types/render/skybox.d.ts +26 -0
  96. package/packages/engine/dist/types/render/skybox.d.ts.map +1 -0
  97. package/packages/game/dist/browser/index.global.js +1 -1
  98. package/packages/game/dist/browser/index.global.js.map +1 -1
  99. package/packages/game/dist/cjs/index.cjs +2926 -116
  100. package/packages/game/dist/cjs/index.cjs.map +1 -1
  101. package/packages/game/dist/esm/index.js +2863 -115
  102. package/packages/game/dist/esm/index.js.map +1 -1
  103. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  104. package/packages/game/dist/types/ai/constants.d.ts +13 -0
  105. package/packages/game/dist/types/ai/constants.d.ts.map +1 -0
  106. package/packages/game/dist/types/ai/index.d.ts +4 -0
  107. package/packages/game/dist/types/ai/index.d.ts.map +1 -0
  108. package/packages/game/dist/types/ai/movement.d.ts +20 -0
  109. package/packages/game/dist/types/ai/movement.d.ts.map +1 -0
  110. package/packages/game/dist/types/ai/perception.d.ts +21 -0
  111. package/packages/game/dist/types/ai/perception.d.ts.map +1 -0
  112. package/packages/game/dist/types/checksum.d.ts +3 -0
  113. package/packages/game/dist/types/checksum.d.ts.map +1 -0
  114. package/packages/game/dist/types/combat/armor.d.ts +39 -0
  115. package/packages/game/dist/types/combat/armor.d.ts.map +1 -0
  116. package/packages/game/dist/types/combat/damage.d.ts +52 -0
  117. package/packages/game/dist/types/combat/damage.d.ts.map +1 -0
  118. package/packages/game/dist/types/combat/damageFlags.d.ts +15 -0
  119. package/packages/game/dist/types/combat/damageFlags.d.ts.map +1 -0
  120. package/packages/game/dist/types/combat/damageMods.d.ts +79 -0
  121. package/packages/game/dist/types/combat/damageMods.d.ts.map +1 -0
  122. package/packages/game/dist/types/combat/index.d.ts +6 -0
  123. package/packages/game/dist/types/combat/index.d.ts.map +1 -0
  124. package/packages/game/dist/types/combat/specialDamage.d.ts +88 -0
  125. package/packages/game/dist/types/combat/specialDamage.d.ts.map +1 -0
  126. package/packages/game/dist/types/entities/entity.d.ts +46 -2
  127. package/packages/game/dist/types/entities/entity.d.ts.map +1 -1
  128. package/packages/game/dist/types/entities/index.d.ts +6 -2
  129. package/packages/game/dist/types/entities/index.d.ts.map +1 -1
  130. package/packages/game/dist/types/entities/pool.d.ts +9 -0
  131. package/packages/game/dist/types/entities/pool.d.ts.map +1 -1
  132. package/packages/game/dist/types/entities/spawn.d.ts +27 -0
  133. package/packages/game/dist/types/entities/spawn.d.ts.map +1 -0
  134. package/packages/game/dist/types/entities/system.d.ts +32 -1
  135. package/packages/game/dist/types/entities/system.d.ts.map +1 -1
  136. package/packages/game/dist/types/entities/thinkScheduler.d.ts +6 -0
  137. package/packages/game/dist/types/entities/thinkScheduler.d.ts.map +1 -1
  138. package/packages/game/dist/types/entities/triggers.d.ts +3 -0
  139. package/packages/game/dist/types/entities/triggers.d.ts.map +1 -0
  140. package/packages/game/dist/types/entities/utils.d.ts +4 -0
  141. package/packages/game/dist/types/entities/utils.d.ts.map +1 -0
  142. package/packages/game/dist/types/index.d.ts +5 -0
  143. package/packages/game/dist/types/index.d.ts.map +1 -1
  144. package/packages/game/dist/types/inventory/ammo.d.ts +17 -0
  145. package/packages/game/dist/types/inventory/ammo.d.ts.map +1 -0
  146. package/packages/game/dist/types/inventory/index.d.ts +2 -0
  147. package/packages/game/dist/types/inventory/index.d.ts.map +1 -0
  148. package/packages/game/dist/types/level.d.ts +1 -0
  149. package/packages/game/dist/types/level.d.ts.map +1 -1
  150. package/packages/game/dist/types/save/index.d.ts +4 -0
  151. package/packages/game/dist/types/save/index.d.ts.map +1 -0
  152. package/packages/game/dist/types/save/rerelease.d.ts +25 -0
  153. package/packages/game/dist/types/save/rerelease.d.ts.map +1 -0
  154. package/packages/game/dist/types/save/save.d.ts +49 -0
  155. package/packages/game/dist/types/save/save.d.ts.map +1 -0
  156. package/packages/game/dist/types/save/storage.d.ts +37 -0
  157. package/packages/game/dist/types/save/storage.d.ts.map +1 -0
  158. package/packages/shared/dist/browser/index.global.js +1 -1
  159. package/packages/shared/dist/browser/index.global.js.map +1 -1
  160. package/packages/shared/dist/cjs/index.cjs +638 -9
  161. package/packages/shared/dist/cjs/index.cjs.map +1 -1
  162. package/packages/shared/dist/esm/index.js +616 -9
  163. package/packages/shared/dist/esm/index.js.map +1 -1
  164. package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
  165. package/packages/shared/dist/types/bsp/collision.d.ts +56 -0
  166. package/packages/shared/dist/types/bsp/collision.d.ts.map +1 -1
  167. package/packages/shared/dist/types/bsp/contents.d.ts +1 -0
  168. package/packages/shared/dist/types/bsp/contents.d.ts.map +1 -1
  169. package/packages/shared/dist/types/index.d.ts +2 -0
  170. package/packages/shared/dist/types/index.d.ts.map +1 -1
  171. package/packages/shared/dist/types/math/random.d.ts +11 -0
  172. package/packages/shared/dist/types/math/random.d.ts.map +1 -1
  173. package/packages/shared/dist/types/protocol/contracts.d.ts +17 -0
  174. package/packages/shared/dist/types/protocol/contracts.d.ts.map +1 -0
  175. package/packages/shared/dist/types/protocol/usercmd.d.ts +30 -0
  176. package/packages/shared/dist/types/protocol/usercmd.d.ts.map +1 -0
  177. package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
package/README.md ADDED
@@ -0,0 +1,425 @@
1
+ # quake2ts
2
+
3
+ > A TypeScript/WebGL port of the Quake II re-release engine for creating interactive web-based game engine visualizations and experiments.
4
+
5
+ [![License: GPL-2.0](https://img.shields.io/badge/License-GPL%202.0-blue.svg)](https://opensource.org/licenses/GPL-2.0)
6
+ [![npm version](https://badge.fury.io/js/quake2ts.svg)](https://www.npmjs.com/package/quake2ts)
7
+
8
+ ## Overview
9
+
10
+ **quake2ts** is a modular, browser-first implementation of the Quake II engine in TypeScript with WebGL2 rendering. It provides a complete game engine architecture broken into composable packages, making it ideal for:
11
+
12
+ - 🎮 **Interactive engine visualizations** - Build web apps that explore game engine internals
13
+ - 🔬 **Educational projects** - Learn game engine architecture through a well-structured codebase
14
+ - 🛠️ **Asset viewers** - Create BSP map viewers, MD2 model viewers, or PAK file explorers
15
+ - 🧪 **Physics experiments** - Test and visualize player movement, collision detection, and game mechanics
16
+ - 🎨 **Retro game development** - Build new games using classic Quake II technology
17
+
18
+ The library is organized as a **pnpm monorepo** with independent packages for different engine layers, allowing you to use only what you need.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install quake2ts
24
+ # or
25
+ pnpm add quake2ts
26
+ # or
27
+ yarn add quake2ts
28
+ ```
29
+
30
+ ### Browser CDN
31
+
32
+ ```html
33
+ <script src="https://unpkg.com/@quake2ts/shared"></script>
34
+ <script src="https://unpkg.com/@quake2ts/engine"></script>
35
+ <script src="https://unpkg.com/@quake2ts/game"></script>
36
+ <script>
37
+ // Access via global: window.Quake2Shared, window.Quake2Engine, etc.
38
+ </script>
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### Running the Complete Engine
44
+
45
+ ```typescript
46
+ import { bootstrapViewer } from '@quake2ts/viewer';
47
+
48
+ // Bootstrap the complete engine with game logic and rendering
49
+ const { engine, game, client, runtime } = bootstrapViewer();
50
+ // Runtime automatically starts at 40Hz simulation tick
51
+ ```
52
+
53
+ ### Building Custom Visualizations
54
+
55
+ For interactive engine exploration, import individual packages:
56
+
57
+ ```typescript
58
+ import { createEngineRuntime } from '@quake2ts/engine';
59
+ import { createGame } from '@quake2ts/game';
60
+ import { createClient } from '@quake2ts/client';
61
+ import { ingestPakFiles, wireFileInput } from '@quake2ts/engine';
62
+
63
+ // 1. Set up asset loading with progress tracking
64
+ const input = document.getElementById('pak-input') as HTMLInputElement;
65
+ wireFileInput(input, async (pakSources) => {
66
+ await ingestPakFiles(pakSources, (progress) => {
67
+ console.log(`Loading assets: ${progress.percent}%`);
68
+ });
69
+
70
+ // 2. Create engine, game, and client
71
+ const engine = createEngine({ /* trace callback */ });
72
+ const game = createGame({ /* trace callback */ });
73
+ const client = createClient({ engine });
74
+
75
+ // 3. Bootstrap runtime
76
+ const runtime = createEngineRuntime(engine, game, client);
77
+ runtime.start();
78
+ });
79
+ ```
80
+
81
+ ## Package Architecture
82
+
83
+ quake2ts is organized into **5 core packages** and a viewer app:
84
+
85
+ ### 📦 `@quake2ts/shared` - Foundation Layer
86
+
87
+ Shared math, physics, and protocol utilities.
88
+
89
+ **Key Exports:**
90
+ - **Math utilities:** `Vec3` operations, angle conversions, color blending, random number generation
91
+ - **Collision detection:** `CONTENTS_*`, `SURF_*`, `MASK_*` constants
92
+ - **Player movement physics:** Complete `pmove` implementation with friction, acceleration, jumping, water/air movement, ducking, and stuck detection
93
+
94
+ ```typescript
95
+ import { Vec3, pmove, MASK_PLAYERSOLID } from '@quake2ts/shared';
96
+
97
+ // Use Quake II's player movement physics
98
+ const result = pmove(playerState, userCmd, traceFunction);
99
+ console.log('New position:', result.origin);
100
+ console.log('New velocity:', result.velocity);
101
+ ```
102
+
103
+ ### 🎨 `@quake2ts/engine` - Platform & Rendering Layer
104
+
105
+ Web-facing services: WebGL2 rendering, asset loading, virtual filesystem, and engine runtime.
106
+
107
+ **Key Exports:**
108
+
109
+ **Asset System:**
110
+ - `PakArchive`, `VirtualFileSystem` - PAK file reading and virtual filesystem
111
+ - `ingestPaks`, `ingestPakFiles` - Asset ingestion pipeline with progress callbacks
112
+ - `wireFileInput`, `wireDropTarget` - Browser file handling helpers
113
+ - `Md2Loader`, `parseMd2` - MD2 model parsing with animation support
114
+ - `LruCache` - Asset caching system
115
+
116
+ **Rendering:**
117
+ - `createWebGLContext` - WebGL2 context initialization
118
+ - `ShaderProgram`, `VertexBuffer`, `IndexBuffer`, `Texture2D` - GPU resource wrappers
119
+ - `buildBspGeometry` - BSP geometry builder with lightmap atlas packing
120
+
121
+ **Engine Core:**
122
+ - `FixedTimestepLoop` - Deterministic 40Hz simulation loop with interpolation
123
+ - `EngineHost` - Game/client lifecycle manager
124
+ - `EngineRuntime`, `createEngineRuntime` - Complete runtime bootstrap
125
+ - `CvarRegistry`, `Cvar` - Configuration variable system
126
+ - `ConfigStringRegistry` - Deterministic asset indexing
127
+
128
+ ```typescript
129
+ import {
130
+ ingestPakFiles,
131
+ buildBspGeometry,
132
+ createWebGLContext,
133
+ FixedTimestepLoop
134
+ } from '@quake2ts/engine';
135
+
136
+ // Create a BSP map viewer
137
+ const vfs = await ingestPakFiles(pakSources);
138
+ const bspData = vfs.readFile('maps/base1.bsp');
139
+ const geometry = buildBspGeometry(bspData);
140
+
141
+ // Set up WebGL rendering
142
+ const gl = createWebGLContext(canvas);
143
+ const loop = new FixedTimestepLoop(
144
+ (dt) => { /* simulate */ },
145
+ (alpha) => { /* render with interpolation */ }
146
+ );
147
+ loop.start();
148
+ ```
149
+
150
+ ### 🎯 `@quake2ts/game` - Game Logic Layer
151
+
152
+ Authoritative simulation and entity system.
153
+
154
+ **Key Exports:**
155
+ - `createGame` - Main game factory function
156
+ - `EntitySystem` - Entity management with pooling
157
+ - `Entity` - Base entity class with `MoveType`, `Solid`, think callbacks, and field metadata
158
+ - `GameFrameLoop` - Frame timing with prep/simulate/post stages
159
+ - `LevelClock` - Level time tracking
160
+
161
+ ```typescript
162
+ import { createGame, Entity, MoveType, Solid } from '@quake2ts/game';
163
+
164
+ const game = createGame(
165
+ { trace: /* collision trace */ },
166
+ { gravity: { x: 0, y: 0, z: -800 } }
167
+ );
168
+
169
+ // Create custom entities
170
+ const platform = new Entity();
171
+ platform.moveType = MoveType.Push;
172
+ platform.solid = Solid.Bsp;
173
+ platform.think = () => {
174
+ // Update logic
175
+ };
176
+
177
+ game.entitySystem.spawn(platform);
178
+ ```
179
+
180
+ ### 🖥️ `@quake2ts/client` - Client & Prediction Layer
181
+
182
+ Client-side rendering interface and state prediction.
183
+
184
+ **Key Exports:**
185
+ - `createClient` - Client factory with prediction support
186
+ - `ClientRenderer` - Rendering interface
187
+ - `PredictionState` - Client-side state prediction
188
+
189
+ ```typescript
190
+ import { createClient } from '@quake2ts/client';
191
+
192
+ const client = createClient({ engine });
193
+ // Client handles HUD rendering and prediction
194
+ ```
195
+
196
+ ### 🔧 `@quake2ts/tools` - Asset Tools
197
+
198
+ Utilities for asset preparation and inspection.
199
+
200
+ **Key Exports:**
201
+ - `describeAsset` - Asset summary and metadata extraction
202
+
203
+ ```typescript
204
+ import { describeAsset } from '@quake2ts/tools';
205
+
206
+ const info = describeAsset(assetBuffer);
207
+ console.log(info);
208
+ ```
209
+
210
+ ### 👁️ `@quake2ts/viewer` - Demo Application
211
+
212
+ Minimal viewer harness demonstrating complete engine integration.
213
+
214
+ **Key Exports:**
215
+ - `bootstrapViewer` - Complete engine/game/client bootstrap
216
+
217
+ ## Interactive Visualization Examples
218
+
219
+ ### Example 1: BSP Map Explorer
220
+
221
+ ```typescript
222
+ import { ingestPakFiles, buildBspGeometry, VirtualFileSystem } from '@quake2ts/engine';
223
+
224
+ // Load PAK files
225
+ const vfs = await ingestPakFiles(pakSources, (progress) => {
226
+ updateProgressBar(progress.percent);
227
+ });
228
+
229
+ // List all maps
230
+ const maps = vfs.listFiles('maps/*.bsp');
231
+ console.log('Available maps:', maps);
232
+
233
+ // Render a specific map
234
+ const bspData = vfs.readFile('maps/base1.bsp');
235
+ const geometry = buildBspGeometry(bspData);
236
+
237
+ // Display geometry stats
238
+ console.log(`Vertices: ${geometry.vertices.length}`);
239
+ console.log(`Faces: ${geometry.indices.length / 3}`);
240
+ console.log(`Lightmaps: ${geometry.lightmaps.length}`);
241
+
242
+ // Render with WebGL
243
+ renderBspGeometry(gl, geometry);
244
+ ```
245
+
246
+ ### Example 2: MD2 Model Viewer with Animations
247
+
248
+ ```typescript
249
+ import { Md2Loader, parseMd2 } from '@quake2ts/engine';
250
+
251
+ // Load MD2 model
252
+ const modelData = vfs.readFile('models/monsters/soldier/tris.md2');
253
+ const model = parseMd2(modelData);
254
+
255
+ // Display model info
256
+ console.log(`Frames: ${model.header.numFrames}`);
257
+ console.log(`Animation groups:`, model.animations);
258
+
259
+ // Animate between frames
260
+ let currentFrame = 0;
261
+ const animationGroup = model.animations.find(a => a.name === 'run');
262
+
263
+ function animate() {
264
+ currentFrame = (currentFrame + 1) % animationGroup.frameCount;
265
+ const frame = model.frames[animationGroup.firstFrame + currentFrame];
266
+ renderMd2Frame(gl, frame, model.triangles);
267
+ requestAnimationFrame(animate);
268
+ }
269
+ animate();
270
+ ```
271
+
272
+ ### Example 3: Player Movement Physics Visualization
273
+
274
+ ```typescript
275
+ import { pmove, Vec3 } from '@quake2ts/shared';
276
+ import { createGame } from '@quake2ts/game';
277
+
278
+ // Set up player state
279
+ const playerState = {
280
+ origin: { x: 0, y: 0, z: 24 },
281
+ velocity: { x: 0, y: 0, z: 0 },
282
+ // ... other pmove fields
283
+ };
284
+
285
+ // Simulate user input
286
+ const userCmd = {
287
+ forwardmove: 400, // Move forward
288
+ sidemove: 0,
289
+ upmove: 0,
290
+ buttons: 0,
291
+ angles: { pitch: 0, yaw: 90, roll: 0 }
292
+ };
293
+
294
+ // Run one physics tick
295
+ const result = pmove(playerState, userCmd, traceFunction);
296
+
297
+ // Visualize the movement
298
+ drawPlayerPosition(result.origin);
299
+ drawVelocityVector(result.origin, result.velocity);
300
+ console.log(`Speed: ${Vec3.length(result.velocity)} units/sec`);
301
+ ```
302
+
303
+ ### Example 4: Entity System Explorer
304
+
305
+ ```typescript
306
+ import { createGame, MoveType, Solid } from '@quake2ts/game';
307
+
308
+ const game = createGame({ trace }, { gravity: { x: 0, y: 0, z: -800 } });
309
+
310
+ // Spawn various entity types
311
+ const entities = [
312
+ { type: 'static', moveType: MoveType.None, solid: Solid.Bsp },
313
+ { type: 'physics', moveType: MoveType.Toss, solid: Solid.BoundingBox },
314
+ { type: 'player', moveType: MoveType.Walk, solid: Solid.BoundingBox },
315
+ { type: 'trigger', moveType: MoveType.None, solid: Solid.Trigger }
316
+ ];
317
+
318
+ entities.forEach(config => {
319
+ const entity = new Entity();
320
+ entity.moveType = config.moveType;
321
+ entity.solid = config.solid;
322
+ entity.think = () => {
323
+ // Visualize entity state in real-time
324
+ highlightEntity(entity);
325
+ };
326
+ game.entitySystem.spawn(entity);
327
+ });
328
+
329
+ // Run simulation and visualize
330
+ game.loop.start();
331
+ ```
332
+
333
+ ## Build Formats
334
+
335
+ Each package is published in **three formats**:
336
+
337
+ - **ESM** - `dist/esm/index.js` - Modern ES modules (recommended)
338
+ - **CJS** - `dist/cjs/index.cjs` - CommonJS for Node.js
339
+ - **Browser** - `dist/browser/index.js` - Minified IIFE for `<script>` tags
340
+
341
+ TypeScript declarations are included at `dist/types/index.d.ts`.
342
+
343
+ ## Key Features
344
+
345
+ ### ✅ Implemented
346
+
347
+ - ✅ **Complete player movement physics** - Full pmove with friction, acceleration, jumping, water/air movement
348
+ - ✅ **Asset loading pipeline** - PAK files, virtual filesystem, MD2 models, BSP geometry
349
+ - ✅ **WebGL2 rendering foundation** - Context management, shader compilation, GPU resources
350
+ - ✅ **Deterministic engine loop** - Fixed 40Hz simulation with frame interpolation
351
+ - ✅ **Entity system** - Component-based entities with pooling and think scheduler
352
+ - ✅ **Configuration system** - Cvars and ConfigStrings for runtime configuration
353
+ - ✅ **Lightmap atlas packing** - Efficient texture packing for BSP lightmaps
354
+
355
+ ### 🚧 In Progress
356
+
357
+ - 🚧 BSP traversal and PVS (Potentially Visible Set) culling
358
+ - 🚧 Complete rendering pipeline (BSP surfaces, MD2/MD3 models, particles)
359
+ - 🚧 HUD and UI rendering
360
+ - 🚧 Audio system (WebAudio integration)
361
+ - 🚧 Combat and items
362
+ - 🚧 AI and monsters
363
+ - 🚧 Save/load serialization
364
+
365
+ ## Documentation
366
+
367
+ Comprehensive documentation is available in the `quake2ts/docs` folder:
368
+
369
+ - **[overview.md](docs/overview.md)** - Architecture overview and package descriptions
370
+ - **[progress.md](docs/progress.md)** - Detailed progress log with completed features
371
+ - **[implementation.md](implementation.md)** - Detailed implementation plan and milestones
372
+ - **Section guides** - 10 detailed guides covering asset loading, rendering, physics, entities, combat, AI, audio, input, save/load, and testing
373
+
374
+ ## Development
375
+
376
+ ```bash
377
+ # Install dependencies
378
+ pnpm install
379
+
380
+ # Build all packages
381
+ pnpm run build
382
+
383
+ # Run tests
384
+ pnpm run test
385
+
386
+ # Watch mode for development
387
+ pnpm run dev
388
+ ```
389
+
390
+ ## Requirements
391
+
392
+ - **Node.js** 18+ (for development)
393
+ - **pnpm** 8.15.7+ (package manager)
394
+ - **Modern browser** with WebGL2 support (for runtime)
395
+
396
+ ## Browser Compatibility
397
+
398
+ - Chrome 56+
399
+ - Firefox 51+
400
+ - Safari 15+
401
+ - Edge 79+
402
+
403
+ All browsers must support **WebGL2** and **ES6 modules**.
404
+
405
+ ## License
406
+
407
+ **GPL-2.0** - This project is a port of Quake II, which is licensed under GPL-2.0. See [LICENSE](LICENSE) for details.
408
+
409
+ ## Repository
410
+
411
+ - **GitHub:** https://github.com/jburnhams/quake2
412
+ - **Issues:** https://github.com/jburnhams/quake2/issues
413
+ - **npm:** https://www.npmjs.com/package/quake2ts
414
+
415
+ ## Contributing
416
+
417
+ Contributions are welcome! Please see the [implementation.md](implementation.md) for the current roadmap and open issues.
418
+
419
+ ## Credits
420
+
421
+ Based on the [Quake II re-release](https://github.com/id-Software/quake2-rerelease-dll) by id Software. This project is an independent TypeScript/WebGL port for educational and experimental purposes.
422
+
423
+ ---
424
+
425
+ **Happy hacking!** 🚀
@@ -1,2 +1,2 @@
1
- "use strict";var Quake2=(()=>{var R=Object.defineProperty;var it=Object.getOwnPropertyDescriptor;var at=Object.getOwnPropertyNames;var ot=Object.prototype.hasOwnProperty;var ct=(t,e)=>{for(var r in e)R(t,r,{get:e[r],enumerable:!0})},lt=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of at(e))!ot.call(t,n)&&n!==r&&R(t,n,{get:()=>e[n],enumerable:!(s=it(e,n))||s.enumerable});return t};var ut=t=>lt(R({},"__esModule",{value:!0}),t);var Le={};ct(Le,{bootstrapViewer:()=>Ne});function I(t){let e;return{init(r){e=r,t.engine.trace({x:0,y:0,z:0},{x:1,y:0,z:0})},predict(r){let{origin:s,velocity:n}=r;return{origin:{x:s.x+n.x,y:s.y+n.y,z:s.z+n.z},velocity:n}},render(r){e=r.latest??e},shutdown(){e=void 0}}}var B=25,ht=5,ft=()=>typeof performance<"u"?performance.now():Date.now(),mt=t=>{typeof requestAnimationFrame=="function"?requestAnimationFrame(()=>t()):setTimeout(t,B)},V=class{constructor(t,e={}){this.callbacks=t,this.accumulatorMs=0,this.frame=0,this.running=!1,this.tick=()=>{if(!this.running)return;let n=this.options.now(),a=this.lastTimeMs===void 0?0:n-this.lastTimeMs;this.lastTimeMs=n,this.advance(a,n),this.running&&this.options.schedule(this.tick)};let r=e.fixedDeltaMs??B,s=e.maxSubSteps??ht;this.options={fixedDeltaMs:r,maxSubSteps:s,maxDeltaMs:e.maxDeltaMs??r*s,startTimeMs:e.startTimeMs,now:e.now??ft,schedule:e.schedule??mt}}start(){this.running||(this.running=!0,this.lastTimeMs=this.options.startTimeMs??this.options.now(),this.options.schedule(this.tick))}stop(){this.running=!1}pump(t){let e=(this.lastTimeMs??0)+t;this.lastTimeMs=e,this.advance(t,e)}isRunning(){return this.running}get frameNumber(){return this.frame}advance(t,e){let r=Math.min(Math.max(t,0),this.options.maxDeltaMs);this.accumulatorMs=Math.min(this.accumulatorMs+r,this.options.fixedDeltaMs*this.options.maxSubSteps);let s=0;for(;this.accumulatorMs>=this.options.fixedDeltaMs&&s<this.options.maxSubSteps;)this.frame+=1,this.callbacks.simulate({frame:this.frame,deltaMs:this.options.fixedDeltaMs,nowMs:e}),this.accumulatorMs-=this.options.fixedDeltaMs,s+=1;let n=this.options.fixedDeltaMs===0?0:this.accumulatorMs/this.options.fixedDeltaMs;this.callbacks.render?.({alpha:n,nowMs:e,accumulatorMs:this.accumulatorMs,frame:this.frame})}},dt=class{constructor(t,e,r={}){this.game=t,this.client=e,this.started=!1,this.stepSimulation=n=>{this.previousFrame=this.latestFrame,this.latestFrame=this.game.frame(n)},this.renderClient=n=>{this.client&&this.client.render({...n,previous:this.previousFrame,latest:this.latestFrame})};let s=r.loop?.now?.()??Date.now();this.startTimeMs=r.startTimeMs??r.loop?.startTimeMs??s,this.loop=new V({simulate:this.stepSimulation,render:this.renderClient},{...r.loop,startTimeMs:this.startTimeMs})}start(){this.started||(this.latestFrame=this.game.init(this.startTimeMs)??this.latestFrame,this.client?.init(this.latestFrame),this.started=!0,this.loop.start())}stop(){this.started&&(this.loop.stop(),this.client?.shutdown(),this.game.shutdown(),this.previousFrame=void 0,this.latestFrame=void 0,this.started=!1)}pump(t){this.loop.pump(t)}getLatestFrame(){return this.latestFrame}isRunning(){return this.loop.isRunning()}},Me=Math.PI/180,Oe=Math.PI/180,De=180/Math.PI,u=1,m=2;var D=8,k=16,yt=32;var vt=16384;var v=65536,H=1<<17,pt=1<<18,xt=1<<19,Tt=1<<20,Et=1<<21,zt=1<<22,_t=1<<23,ke=1<<24,p=1<<25,At=1<<26,Ce=1<<27,Pe=1<<28,Fe=1<<29,x=1<<30,Ue=1<<31;var be=1<<25,We=1<<28,Ge=1<<29,Ie=1<<30,Be=1<<31,Ve=u|m,He=u|v|m|p|x,Ke=u|v|m,Xe=u|H|m|p|x,Ye=yt|D|k,Je=u|k|D,St=u|p|x|m|At,$e=pt|xt|Tt|Et|zt|_t,qe=u|D|k|p|x,je=u|v|m,Ze=u|m,Qe=u|v|m|H,tr=St|vt;var K=256,wt=256,Nt=8192,Lt=2048,Rt=512,gt=256,Mt=K*2,Ot=256,g=32;var X=(t=>(t[t.Name=0]="Name",t[t.CdTrack=1]="CdTrack",t[t.Sky=2]="Sky",t[t.SkyAxis=3]="SkyAxis",t[t.SkyRotate=4]="SkyRotate",t[t.StatusBar=5]="StatusBar",t[t.AirAccel=59]="AirAccel",t[t.MaxClients=60]="MaxClients",t[t.MapChecksum=61]="MapChecksum",t[t.Models=62]="Models",t[t.Sounds=62+Nt]="Sounds",t[t.Images=t.Sounds+Lt]="Images",t[t.Lights=t.Images+Rt]="Lights",t[t.ShadowLights=t.Lights+wt]="ShadowLights",t[t.Items=t.ShadowLights+Ot]="Items",t[t.PlayerSkins=t.Items+gt]="PlayerSkins",t[t.General=t.PlayerSkins+K]="General",t[t.WheelWeapons=t.General+Mt]="WheelWeapons",t[t.WheelAmmo=t.WheelWeapons+g]="WheelAmmo",t[t.WheelPowerups=t.WheelAmmo+g]="WheelPowerups",t[t.CdLoopCount=t.WheelPowerups+g]="CdLoopCount",t[t.GameStyle=t.CdLoopCount+1]="GameStyle",t[t.MaxConfigStrings=t.GameStyle+1]="MaxConfigStrings",t))(X||{}),er=X.MaxConfigStrings;var Dt=class{constructor(t,e){this.engine=t,this.host=e,this.started=!1}start(){this.started||(this.engine.init(),this.host.start(),this.started=!0)}stop(){this.started&&(this.host.stop(),this.engine.shutdown(),this.started=!1)}pump(t){this.host.pump(t)}getLatestFrame(){return this.host.getLatestFrame()}isRunning(){return this.started&&this.host.isRunning()}};function Y(t,e,r,s){return new Dt(t,new dt(e,r,s))}function kt(){let t=new Uint32Array(256);for(let e=0;e<256;e+=1){let r=e;for(let s=0;s<8;s+=1)r=(r&1)!==0?3988292384^r>>>1:r>>>1;t[e]=r>>>0}return t}var rr=kt();var O=4,M=7*O,sr=[{index:0,size:3,type:5126,stride:M,offset:0},{index:1,size:2,type:5126,stride:M,offset:3*O},{index:2,size:2,type:5126,stride:M,offset:5*O}];function J(t){return{init(){t.trace({x:0,y:0,z:0},{x:0,y:0,z:0})},shutdown(){},createMainLoop(e,r){return new V(e,r)}}}var Ct={x:0,y:0,z:0},cr=Math.PI/180,lr=Math.PI/180,ur=180/Math.PI,h=1,d=2;var P=8,F=16,Pt=32;var Ft=16384;var z=65536,q=1<<17,Ut=1<<18,bt=1<<19,Wt=1<<20,Gt=1<<21,It=1<<22,Bt=1<<23,hr=1<<24,_=1<<25,Vt=1<<26,fr=1<<27,mr=1<<28,dr=1<<29,A=1<<30,yr=1<<31;var vr=1<<25,pr=1<<28,xr=1<<29,Tr=1<<30,Er=1<<31,zr=h|d,_r=h|z|d|_|A,Ar=h|z|d,Sr=h|q|d|_|A,wr=Pt|P|F,Nr=h|F|P,Ht=h|_|A|d|Vt,Lr=Ut|bt|Wt|Gt|It|Bt,Rr=h|P|F|_|A,gr=h|z|d,Mr=h|d,Or=h|z|d|q,Dr=Ht|Ft,j=256,Kt=256,Xt=8192,Yt=2048,Jt=512,$t=256,qt=j*2,jt=256,C=32,Z=(t=>(t[t.Name=0]="Name",t[t.CdTrack=1]="CdTrack",t[t.Sky=2]="Sky",t[t.SkyAxis=3]="SkyAxis",t[t.SkyRotate=4]="SkyRotate",t[t.StatusBar=5]="StatusBar",t[t.AirAccel=59]="AirAccel",t[t.MaxClients=60]="MaxClients",t[t.MapChecksum=61]="MapChecksum",t[t.Models=62]="Models",t[t.Sounds=62+Xt]="Sounds",t[t.Images=t.Sounds+Yt]="Images",t[t.Lights=t.Images+Jt]="Lights",t[t.ShadowLights=t.Lights+Kt]="ShadowLights",t[t.Items=t.ShadowLights+jt]="Items",t[t.PlayerSkins=t.Items+$t]="PlayerSkins",t[t.General=t.PlayerSkins+j]="General",t[t.WheelWeapons=t.General+qt]="WheelWeapons",t[t.WheelAmmo=t.WheelWeapons+C]="WheelAmmo",t[t.WheelPowerups=t.WheelAmmo+C]="WheelPowerups",t[t.CdLoopCount=t.WheelPowerups+C]="CdLoopCount",t[t.GameStyle=t.CdLoopCount+1]="GameStyle",t[t.MaxConfigStrings=t.GameStyle+1]="MaxConfigStrings",t))(Z||{}),kr=Z.MaxConfigStrings;var Zt={...Ct};function o(){return{...Zt}}var Qt=class{constructor(t){this.inUse=!1,this.freePending=!1,this.linkPrevious=null,this.linkNext=null,this.classname="",this.spawnflags=0,this.origin=o(),this.old_origin=o(),this.velocity=o(),this.avelocity=o(),this.angles=o(),this.mins=o(),this.maxs=o(),this.size=o(),this.mass=0,this.gravity=1,this.movetype=0,this.modelindex=0,this.frame=0,this.skin=0,this.effects=0,this.renderfx=0,this.health=0,this.max_health=0,this.takedamage=!1,this.dmg=0,this.deadflag=0,this.enemy=null,this.movetarget=null,this.goalentity=null,this.ideal_yaw=0,this.yaw_speed=0,this.groundentity=null,this.groundentity_linkcount=0,this.waterlevel=0,this.watertype=0,this.nextthink=0,this.solid=0,this.flags=0,this.svflags=0,this.index=t}reset(){this.inUse=!1,this.freePending=!1,this.linkPrevious=null,this.linkNext=null,this.classname="",this.spawnflags=0,this.target=void 0,this.targetname=void 0,this.team=void 0,this.message=void 0,this.origin=o(),this.old_origin=o(),this.velocity=o(),this.avelocity=o(),this.angles=o(),this.mins=o(),this.maxs=o(),this.size=o(),this.mass=0,this.gravity=1,this.movetype=0,this.modelindex=0,this.frame=0,this.skin=0,this.effects=0,this.renderfx=0,this.health=0,this.max_health=0,this.takedamage=!1,this.dmg=0,this.deadflag=0,this.enemy=null,this.movetarget=null,this.goalentity=null,this.ideal_yaw=0,this.yaw_speed=0,this.groundentity=null,this.groundentity_linkcount=0,this.waterlevel=0,this.watertype=0,this.nextthink=0,this.think=void 0,this.touch=void 0,this.use=void 0,this.pain=void 0,this.die=void 0,this.solid=0,this.flags=0,this.svflags=0}};var te=2048,T=0,ee=class{constructor(t=te){if(this.freeList=[],this.pendingFree=[],this.activeHead=null,t<1||!Number.isInteger(t))throw new Error("EntityPool requires a positive integer size");this.entities=new Array(t);for(let r=0;r<t;r+=1)this.entities[r]=new Qt(r),r!==T&&this.freeList.push(r);let e=this.entities[T];e.inUse=!0,e.classname="worldspawn",this.activeHead=e}get world(){return this.entities[T]}get capacity(){return this.entities.length}get activeCount(){let t=0;for(let e of this)t+=1;return t}[Symbol.iterator](){let t=this.activeHead;return{next:()=>{if(!t)return{done:!0,value:void 0};let e=t;return t=t.linkNext,{done:!1,value:e}}}}spawn(){let t=this.freeList.pop();if(t===void 0)throw new Error("No free entities available");let e=this.entities[t];return e.reset(),e.inUse=!0,this.link(e),e}deferFree(t){if(t.index===T)throw new Error("Cannot free world entity");!t.inUse||t.freePending||(this.unlink(t),t.inUse=!1,t.freePending=!0,this.pendingFree.push(t.index))}flushFreeList(){if(this.pendingFree.length!==0){for(let t of this.pendingFree)this.entities[t].reset(),this.freeList.push(t);this.pendingFree.length=0}}link(t){t.linkNext=this.activeHead,this.activeHead&&(this.activeHead.linkPrevious=t),this.activeHead=t,t.linkPrevious=null}unlink(t){t.linkPrevious&&(t.linkPrevious.linkNext=t.linkNext),t.linkNext&&(t.linkNext.linkPrevious=t.linkPrevious),this.activeHead===t&&(this.activeHead=t.linkNext),t.linkPrevious=null,t.linkNext=null}},re=class{constructor(){this.queue=[]}schedule(t,e){t.nextthink=e,this.queue.push({entity:t,time:e}),this.queue.sort((r,s)=>r.time===s.time?r.entity.index-s.entity.index:r.time-s.time)}cancel(t){if(this.queue.length!==0)for(let e=this.queue.length-1;e>=0;e-=1)this.queue[e].entity===t&&this.queue.splice(e,1)}runDueThinks(t){for(;this.queue.length>0;){let e=this.queue[0];if(e.time>t)break;this.queue.shift();let{entity:r,time:s}=e;!r.inUse||r.freePending||r.think&&r.nextthink===s&&r.think(r)}}};function $(t){return{min:{x:t.origin.x+t.mins.x,y:t.origin.y+t.mins.y,z:t.origin.z+t.mins.z},max:{x:t.origin.x+t.maxs.x,y:t.origin.y+t.maxs.y,z:t.origin.z+t.maxs.z}}}function se(t,e){return!(t.min.x>e.max.x||t.max.x<e.min.x||t.min.y>e.max.y||t.max.y<e.min.y||t.min.z>e.max.z||t.max.z<e.min.z)}var ne=class{constructor(t){this.currentTimeSeconds=0,this.pool=new ee(t),this.thinkScheduler=new re}get world(){return this.pool.world}get activeCount(){return this.pool.activeCount}get timeSeconds(){return this.currentTimeSeconds}forEachEntity(t){for(let e of this.pool)t(e)}spawn(){return this.pool.spawn()}free(t){this.thinkScheduler.cancel(t),this.pool.deferFree(t)}scheduleThink(t,e){this.thinkScheduler.schedule(t,e)}beginFrame(t){this.currentTimeSeconds=t}runFrame(){this.thinkScheduler.runDueThinks(this.currentTimeSeconds),this.runTouches(),this.pool.flushFreeList()}runTouches(){let t=this.pool.world,e=[];for(let r of this.pool)r!==t&&(!r.inUse||r.freePending||e.push(r));for(let r=0;r<e.length;r+=1){let s=e[r],n=null;for(let a=r+1;a<e.length;a+=1){let c=e[a];if(!s.touch&&!c.touch)continue;n||(n=$(s));let l=$(c);se(n,l)&&(s.touch&&s.touch(s,c),c.touch&&c.touch(c,s))}}}},ie=["prep","simulate","finish"],ae=class{constructor(t){if(this.timeMs=0,this.frame=0,this.stageHandlers={prep:[],simulate:[],finish:[]},this.stageCounts={prep:0,simulate:0,finish:0},this.stageCompactionNeeded={prep:!1,simulate:!1,finish:!1},t)for(let e of ie){let r=t[e];r&&this.addStage(e,r)}}addStage(t,e){let r=this.stageHandlers[t];return r.push(e),this.stageCounts[t]+=1,()=>{let s=r.indexOf(e);s>=0&&r[s]&&(r[s]=void 0,this.stageCounts[t]-=1,this.stageCompactionNeeded[t]=!0)}}reset(t){this.timeMs=t,this.frame=0}advance(t){let e=this.timeMs;this.timeMs=e+t.deltaMs,this.frame=t.frame;let r={...t,timeMs:this.timeMs,previousTimeMs:e,deltaSeconds:t.deltaMs/1e3};if(this.runStage("prep",r),this.stageCounts.simulate===0)throw new Error("GameFrameLoop requires at least one simulate stage");return this.runStage("simulate",r),this.runStage("finish",r),r}runStage(t,e){let r=this.stageHandlers[t];for(let s=0;s<r.length;s+=1){let n=r[s];n&&n(e)}this.stageCompactionNeeded[t]&&this.compactStageHandlers(t)}compactStageHandlers(t){let e=this.stageHandlers[t],r=0;for(let s=0;s<e.length;s+=1){let n=e[s];n&&(e[r]=n,r+=1)}e.length=r,this.stageCompactionNeeded[t]=!1}get time(){return this.timeMs}get frameNumber(){return this.frame}},oe={frameNumber:0,timeSeconds:0,previousTimeSeconds:0,deltaSeconds:0},ce=class{constructor(){this.state=oe}start(t){let e=t/1e3;this.state={frameNumber:0,timeSeconds:e,previousTimeSeconds:e,deltaSeconds:0}}tick(t){return this.state={frameNumber:t.frame,timeSeconds:t.timeMs/1e3,previousTimeSeconds:t.previousTimeMs/1e3,deltaSeconds:t.deltaSeconds},this.state}get current(){return this.state}},E={x:0,y:0,z:0};function Q(t,e){let r=e.gravity,s=new ce,n=new ae,a=new ne;n.addStage("prep",i=>{s.tick(i),a.beginFrame(s.current.timeSeconds)}),n.addStage("simulate",({deltaSeconds:i})=>{l={x:l.x+r.x*i,y:l.y+r.y*i,z:l.z+r.z*i},c={x:c.x+l.x*i,y:c.y+l.y*i,z:c.z+l.z*i},a.runFrame()});let c={...E},l={...E},G=i=>({frame:i,timeMs:n.time,state:{gravity:{...r},origin:{...c},velocity:{...l},level:{...s.current},entities:{activeCount:a.activeCount,worldClassname:a.world.classname}}}),st=i=>{n.reset(i),s.start(i),c={...E},l={...E},a.beginFrame(i/1e3),a.runFrame()};return{init(i){return st(i),t.trace({x:0,y:0,z:0},r),G(0)},shutdown(){},spawnWorld(){},frame(i){let nt=n.advance(i);return G(nt.frame)},entities:a}}var S={x:0,y:0,z:0};var Ir=Math.PI/180;var Br=Math.PI/180,Vr=180/Math.PI;var f=1,y=2;var b=8,W=16,le=32;var ue=16384;var w=65536,tt=1<<17,he=1<<18,fe=1<<19,me=1<<20,de=1<<21,ye=1<<22,ve=1<<23,Hr=1<<24,N=1<<25,pe=1<<26,Kr=1<<27,Xr=1<<28,Yr=1<<29,L=1<<30,Jr=1<<31;var $r=1<<25,qr=1<<28,jr=1<<29,Zr=1<<30,Qr=1<<31;var ts=f|y,es=f|w|y|N|L,rs=f|w|y,ss=f|tt|y|N|L,ns=le|b|W,is=f|W|b,xe=f|N|L|y|pe,as=he|fe|me|de|ye|ve,os=f|b|W|N|L,cs=f|w|y,ls=f|y,us=f|w|y|tt,hs=xe|ue;var et=256;var Te=256,Ee=8192,ze=2048,_e=512,Ae=256,Se=et*2,we=256,U=32;var rt=(t=>(t[t.Name=0]="Name",t[t.CdTrack=1]="CdTrack",t[t.Sky=2]="Sky",t[t.SkyAxis=3]="SkyAxis",t[t.SkyRotate=4]="SkyRotate",t[t.StatusBar=5]="StatusBar",t[t.AirAccel=59]="AirAccel",t[t.MaxClients=60]="MaxClients",t[t.MapChecksum=61]="MapChecksum",t[t.Models=62]="Models",t[t.Sounds=62+Ee]="Sounds",t[t.Images=t.Sounds+ze]="Images",t[t.Lights=t.Images+_e]="Lights",t[t.ShadowLights=t.Lights+Te]="ShadowLights",t[t.Items=t.ShadowLights+we]="Items",t[t.PlayerSkins=t.Items+Ae]="PlayerSkins",t[t.General=t.PlayerSkins+et]="General",t[t.WheelWeapons=t.General+Se]="WheelWeapons",t[t.WheelAmmo=t.WheelWeapons+U]="WheelAmmo",t[t.WheelPowerups=t.WheelAmmo+U]="WheelPowerups",t[t.CdLoopCount=t.WheelPowerups+U]="CdLoopCount",t[t.GameStyle=t.CdLoopCount+1]="GameStyle",t[t.MaxConfigStrings=t.GameStyle+1]="MaxConfigStrings",t))(rt||{}),fs=rt.MaxConfigStrings;function Ne(){let t=J({trace(n,a){return{start:n,end:a,fraction:1}}}),e=Q({trace(n,a){return{start:n,end:a,fraction:1}}},{gravity:S}),r=I({engine:{trace:()=>({start:S,end:S,fraction:1})}}),s=Y(t,e,r);return s.start(),{engine:t,game:e,client:r,runtime:s}}return ut(Le);})();
1
+ "use strict";var Quake2=(()=>{var oe=Object.defineProperty;var ft=Object.getOwnPropertyDescriptor;var mt=Object.getOwnPropertyNames;var pt=Object.prototype.hasOwnProperty;var yt=(e,t)=>{for(var n in t)oe(e,n,{get:t[n],enumerable:!0})},vt=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of mt(t))!pt.call(e,s)&&s!==n&&oe(e,s,{get:()=>t[s],enumerable:!(r=ft(t,s))||r.enumerable});return e};var gt=e=>vt(oe({},"__esModule",{value:!0}),e);var Hr={};yt(Hr,{bootstrapViewer:()=>Vr});var xt={x:0,y:0,z:0},Xr=Math.PI/180;function I(e,t){return{x:e.x+t.x,y:e.y+t.y,z:e.z+t.z}}function D(e,t){return{x:e.x*t,y:e.y*t,z:e.z*t}}function P(e,t){return e.x*t.x+e.y*t.y+e.z*t.z}function Et(e){return P(e,e)}function G(e){return Math.sqrt(Et(e))}function be(e){let t=G(e);return t===0?e:D(e,1/t)}var ke=0,Ce=1,Pe=2,St=Math.PI/180,$r=180/Math.PI;function ae(e,t){switch(t){case ke:return e.x;case Ce:return e.y;case Pe:default:return e.z}}function ce(e){return e*St}function Le(e){let t=e%360;return t<0?360+t:t}function De(e){let t=ce(ae(e,Ce)),n=ce(ae(e,ke)),r=ce(ae(e,Pe)),s=Math.sin(t),o=Math.cos(t),i=Math.sin(n),a=Math.cos(n),u=Math.sin(r),l=Math.cos(r),c={x:a*o,y:a*s,z:-i},m={x:-u*i*o-l*-s,y:-u*i*s-l*o,z:-u*a},p={x:l*i*o-u*-s,y:l*i*s-u*o,z:l*a};return{forward:c,right:m,up:p}}var y=1,T=2;var he=8,de=16,Tt=32;var _t=16384;var W=65536,Ue=1<<17,wt=1<<18,At=1<<19,zt=1<<20,Nt=1<<21,Rt=1<<22,Lt=1<<23,Yr=1<<24,V=1<<25,Ot=1<<26,jr=1<<27,qr=1<<28,Jr=1<<29,H=1<<30,Zr=1<<31;var Qr=1<<25,es=1<<28,ts=1<<29,ns=1<<30,rs=1<<31,ss=y|T,is=y|W|T|V|H,os=y|W|T,as=y|Ue|T|V|H,cs=Tt|he|de,us=y|de|he,Mt=y|V|H|T|Ot,ls=wt|At|zt|Nt|Rt|Lt,hs=y|he|de|V|H,ds=y|W|T,fs=y|T,ms=y|W|T|Ue,ps=Mt|_t,ys=Number.MAX_SAFE_INTEGER-1,Fe=256,bt=256,kt=8192,Ct=2048,Pt=512,Dt=256,Ut=Fe*2,Ft=256,ue=32,Ie=(e=>(e[e.Name=0]="Name",e[e.CdTrack=1]="CdTrack",e[e.Sky=2]="Sky",e[e.SkyAxis=3]="SkyAxis",e[e.SkyRotate=4]="SkyRotate",e[e.StatusBar=5]="StatusBar",e[e.AirAccel=59]="AirAccel",e[e.MaxClients=60]="MaxClients",e[e.MapChecksum=61]="MapChecksum",e[e.Models=62]="Models",e[e.Sounds=62+kt]="Sounds",e[e.Images=e.Sounds+Ct]="Images",e[e.Lights=e.Images+Pt]="Lights",e[e.ShadowLights=e.Lights+bt]="ShadowLights",e[e.Items=e.ShadowLights+Ft]="Items",e[e.PlayerSkins=e.Items+Dt]="PlayerSkins",e[e.General=e.PlayerSkins+Fe]="General",e[e.WheelWeapons=e.General+Ut]="WheelWeapons",e[e.WheelAmmo=e.WheelWeapons+ue]="WheelAmmo",e[e.WheelPowerups=e.WheelAmmo+ue]="WheelPowerups",e[e.CdLoopCount=e.WheelPowerups+ue]="CdLoopCount",e[e.GameStyle=e.CdLoopCount+1]="GameStyle",e[e.MaxConfigStrings=e.GameStyle+1]="MaxConfigStrings",e))(Ie||{}),vs=Ie.MaxConfigStrings,B=(e=>(e[e.None=0]="None",e[e.Feet=1]="Feet",e[e.Waist=2]="Waist",e[e.Under=3]="Under",e))(B||{}),S=(e=>(e[e.Ducked=1]="Ducked",e[e.JumpHeld=2]="JumpHeld",e[e.OnGround=4]="OnGround",e[e.TimeWaterJump=8]="TimeWaterJump",e[e.TimeLand=16]="TimeLand",e[e.TimeTeleport=32]="TimeTeleport",e[e.NoPositionalPrediction=64]="NoPositionalPrediction",e[e.OnLadder=128]="OnLadder",e[e.NoAngularPrediction=256]="NoAngularPrediction",e[e.IgnorePlayerCollision=512]="IgnorePlayerCollision",e[e.TimeTrick=1024]="TimeTrick",e))(S||{});function O(e,t){return(e&t)!==0}var Be=(e=>(e[e.Normal=0]="Normal",e[e.Grapple=1]="Grapple",e[e.NoClip=2]="NoClip",e[e.Spectator=3]="Spectator",e[e.Dead=4]="Dead",e[e.Gib=5]="Gib",e[e.Freeze=6]="Freeze",e))(Be||{}),R=(e=>(e[e.None=0]="None",e[e.Attack=1]="Attack",e[e.Use=2]="Use",e[e.Holster=4]="Holster",e[e.Jump=8]="Jump",e[e.Crouch=16]="Crouch",e[e.Any=128]="Any",e))(R||{});function It(e){let{velocity:t,frametime:n,onGround:r,groundIsSlick:s,onLadder:o,waterlevel:i,pmFriction:a,pmStopSpeed:u,pmWaterFriction:l}=e,c=G(t);if(c<1)return{x:0,y:0,z:t.z};let m=0;if(r&&!s||o){let k=c<u?u:c;m+=k*a*n}i>0&&!o&&(m+=c*l*i*n);let p=c-m;if(p<0&&(p=0),p===c)return t;let z=p/c;return D(t,z)}function Oe(e){let{velocity:t,wishdir:n,wishspeed:r,accel:s,frametime:o}=e,i=P(t,n),a=r-i;if(a<=0)return t;let u=s*o*r;return u>a&&(u=a),{x:t.x+n.x*u,y:t.y+n.y*u,z:t.z+n.z*u}}function Bt(e){let{velocity:t,wishdir:n,wishspeed:r,accel:s,frametime:o}=e,i=Math.min(r,30),a=P(t,n),u=i-a;if(u<=0)return t;let l=s*r*o;return l>u&&(l=u),{x:t.x+n.x*l,y:t.y+n.y*l,z:t.z+n.z*l}}function Gt(e){let{forward:t,right:n,cmd:r,maxSpeed:s}=e,o={x:t.x*r.forwardmove+n.x*r.sidemove,y:t.y*r.forwardmove+n.y*r.sidemove,z:0},i=G(o);if(i>s){let a=s/i;o=D(o,a),i=s}return{wishdir:i===0?o:be(o),wishspeed:i}}function Wt(e){let{forward:t,right:n,cmd:r,maxSpeed:s}=e,o={x:t.x*r.forwardmove+n.x*r.sidemove,y:t.y*r.forwardmove+n.y*r.sidemove,z:0};r.upmove>10?o=I(o,{x:0,y:0,z:r.upmove}):r.upmove<-10?o=I(o,{x:0,y:0,z:r.upmove}):o=I(o,{x:0,y:0,z:10});let i=G(o);if(i>s){let a=s/i;o=D(o,a),i=s}return i*=.5,{wishdir:i===0?o:be(o),wishspeed:i}}function Vt(e,t){return{x:e.x+t.x,y:e.y+t.y,z:e.z+t.z}}function Ht(e){return e>89&&e<180?89:e<271&&e>=180?271:e}function Ge(e){let{pmFlags:t,cmdAngles:n,deltaAngles:r}=e,s;(t&32)!==0?s={x:0,y:n.y+r.y,z:0}:(s=Vt(n,r),s={...s,x:Ht(s.x)});let o=De(s);return{viewangles:s,...o}}var Kt={pmFriction:6,pmStopSpeed:100,pmAccelerate:10,pmAirAccelerate:1,pmWaterAccelerate:4,pmWaterFriction:1,pmMaxSpeed:300,pmDuckSpeed:100,pmWaterSpeed:400,groundIsSlick:!1},Xt=800,L={x:0,y:0,z:0},$t=250;function C(){return{origin:L,velocity:L,viewangles:L,pmFlags:S.OnGround,pmType:Be.Normal,waterlevel:B.None,gravity:Xt,deltaAngles:L}}function Me(e){return e?{...C(),...e,origin:{...e.origin},velocity:{...e.velocity},viewangles:{...e.viewangles},deltaAngles:e.deltaAngles?{...e.deltaAngles}:L}:C()}function N(e,t,n){return e+(t-e)*n}function le(e,t,n){let r=Le(t-e);return r>180&&(r-=360),Le(e+r*n)}function Yt(e,t,n){let r=Math.max(0,Math.min(n,1));return{origin:{x:N(e.origin.x,t.origin.x,r),y:N(e.origin.y,t.origin.y,r),z:N(e.origin.z,t.origin.z,r)},velocity:{x:N(e.velocity.x,t.velocity.x,r),y:N(e.velocity.y,t.velocity.y,r),z:N(e.velocity.z,t.velocity.z,r)},viewangles:{x:le(e.viewangles.x,t.viewangles.x,r),y:le(e.viewangles.y,t.viewangles.y,r),z:le(e.viewangles.z,t.viewangles.z,r)},pmFlags:t.pmFlags,pmType:t.pmType,waterlevel:t.waterlevel,gravity:t.gravity,deltaAngles:t.deltaAngles}}function jt(e,t,n){let r=Math.min(Math.max(t.msec,0),$t)/1e3,s=O(e.pmFlags,S.OnGround),o=O(e.pmFlags,S.OnLadder),i=It({velocity:e.velocity,frametime:r,onGround:s,groundIsSlick:n.groundIsSlick,onLadder:o,waterlevel:e.waterlevel,pmFriction:n.pmFriction,pmStopSpeed:n.pmStopSpeed,pmWaterFriction:n.pmWaterFriction}),{viewangles:a,forward:u,right:l}=Ge({pmFlags:e.pmFlags,cmdAngles:t.angles,deltaAngles:e.deltaAngles??L}),c=e.waterlevel>B.None?Wt({forward:u,right:l,cmd:t,maxSpeed:n.pmWaterSpeed}):Gt({forward:u,right:l,cmd:t,maxSpeed:n.pmMaxSpeed});if(e.waterlevel>B.None)i=Oe({velocity:i,wishdir:c.wishdir,wishspeed:c.wishspeed,accel:n.pmWaterAccelerate,frametime:r});else if(s||o){let z=O(e.pmFlags,S.Ducked)?n.pmDuckSpeed:n.pmMaxSpeed,k=c.wishspeed>z?{wishdir:c.wishdir,wishspeed:z}:c;i=Oe({velocity:i,wishdir:k.wishdir,wishspeed:k.wishspeed,accel:n.pmAccelerate,frametime:r})}else i=Bt({velocity:i,wishdir:c.wishdir,wishspeed:c.wishspeed,accel:n.pmAirAccelerate,frametime:r}),i={...i,z:i.z-e.gravity*r};let m=D(i,r),p=I(e.origin,m);return{...e,origin:p,velocity:i,viewangles:a}}var qt=class{constructor(e={}){this.baseFrame={frame:0,timeMs:0,state:C()},this.commands=[],this.predicted=C(),this.settings={...Kt,...e},this.predicted=this.baseFrame.state??C()}setAuthoritative(e){let t=Me(e.state);return this.baseFrame={...e,state:t},this.commands=this.commands.filter(n=>(n.serverFrame??Number.MAX_SAFE_INTEGER)>e.frame),this.recompute()}enqueueCommand(e){return this.commands.push(e),this.recompute()}getPredictedState(){return this.predicted}recompute(){let e=Me(this.baseFrame.state);for(let t of this.commands)e=jt(e,t,this.settings);return this.predicted=e,e}},Jt={runPitch:.002,runRoll:.005,bobUp:.005,bobPitch:.002,bobRoll:.002,maxBobHeight:6,maxBobAngle:1.2};function Zt(e){return{x:Math.max(-14,Math.min(14,e.x)),y:Math.max(-14,Math.min(14,e.y)),z:Math.max(-22,Math.min(30,e.z))}}function Qt(e,t,n){return t?e>210?n/400:e>100?n/800:n/1600:0}function en(e,t,n,r,s){if(t<5)return{bobTime:0,bobCycle:0,bobCycleRun:0,bobFracSin:0};let o=Qt(t,r,s),i=e+o,u=O(n,S.Ducked)&&r?i*4:i;return{bobTime:i,bobCycle:Math.floor(u),bobCycleRun:Math.floor(i),bobFracSin:Math.abs(Math.sin(u*Math.PI))}}var tn=class{constructor(e={}){this.bobTime=0,this.bobCycle=0,this.bobCycleRun=0,this.bobFracSin=0,this.settings={...Jt,...e}}addKick(e){e.durationMs<=0||(this.kick={...e,remainingMs:e.durationMs})}get last(){return this.lastSample}sample(e,t){let{forward:n,right:r}=De(Ge({pmFlags:e.pmFlags,cmdAngles:e.viewangles,deltaAngles:e.deltaAngles??xt}).viewangles),s=Math.sqrt(e.velocity.x*e.velocity.x+e.velocity.y*e.velocity.y),o=O(e.pmFlags,S.OnGround),i=en(this.bobTime,s,e.pmFlags,o,t);this.bobTime=i.bobTime,this.bobCycle=i.bobCycle,this.bobCycleRun=i.bobCycleRun,this.bobFracSin=i.bobFracSin;let a=P(e.velocity,n)*this.settings.runPitch,u=P(e.velocity,r)*this.settings.runRoll,l=this.bobFracSin*this.settings.bobPitch*s,c=this.bobFracSin*this.settings.bobRoll*s;O(e.pmFlags,S.Ducked)&&o&&(l*=6,c*=6),a+=Math.min(l,this.settings.maxBobAngle),c=Math.min(c,this.settings.maxBobAngle),this.bobCycle&1&&(c=-c),u+=c;let m=Math.min(this.bobFracSin*s*this.settings.bobUp,this.settings.maxBobHeight),p=0,z=0;if(this.kick&&this.kick.remainingMs>0){let Re=Math.max(0,Math.min(1,this.kick.remainingMs/this.kick.durationMs));p+=Re*this.kick.pitch,z+=Re*this.kick.roll,this.kick.remainingMs=Math.max(0,this.kick.remainingMs-t),this.kick.remainingMs===0&&(this.kick=void 0)}let Ne={angles:{x:a+p,y:0,z:u+z},offset:Zt({x:0,y:0,z:m}),bobCycle:this.bobCycle,bobCycleRun:this.bobCycleRun,bobFracSin:this.bobFracSin,xyspeed:s};return this.lastSample=Ne,Ne}};function nn(e){return e.trim().toLowerCase()}var We=(e=>(e.Forward="+forward",e.Back="+back",e.MoveLeft="+moveleft",e.MoveRight="+moveright",e.MoveUp="+moveup",e.MoveDown="+movedown",e.Jump="+jump",e.Crouch="+crouch",e.Attack="+attack",e.Use="+use",e.Holster="+holster",e.TurnLeft="+left",e.TurnRight="+right",e.LookUp="+lookup",e.LookDown="+lookdown",e.SpeedModifier="+speed",e.Zoom="+zoom",e))(We||{}),gs={"+attack":R.Attack,"+use":R.Use,"+holster":R.Holster,"+jump":R.Jump,"+crouch":R.Crouch},xs=new Map(Object.values(We).map(e=>[nn(e),e]));function Ve(e){let t=new qt,n=new tn,r,s,o;return{init(i){r=i,i?.state&&t.setAuthoritative(i),e.engine.trace({x:0,y:0,z:0},{x:1,y:0,z:0})},predict(i){return t.enqueueCommand(i)},render(i){i.latest?.state&&(t.setAuthoritative(i.latest),r=i.latest),i.previous?.state&&i.latest?.state?s=Yt(i.previous.state,i.latest.state,i.alpha):s=i.latest?.state??i.previous?.state??t.getPredictedState();let a=i.latest&&i.previous?Math.max(0,i.latest.timeMs-i.previous.timeMs):0;o=n.sample(s,a)},shutdown(){r=void 0,s=void 0},get prediction(){return t},get lastRendered(){return s},get view(){return n},get lastView(){return o}}}var He=25,rn=5,sn=()=>typeof performance<"u"?performance.now():Date.now(),on=e=>{typeof requestAnimationFrame=="function"?requestAnimationFrame(()=>e()):setTimeout(e,He)},Ke=class{constructor(e,t={}){this.callbacks=e,this.accumulatorMs=0,this.frame=0,this.running=!1,this.tick=()=>{if(!this.running)return;let s=this.options.now(),o=this.lastTimeMs===void 0?0:s-this.lastTimeMs;this.lastTimeMs=s,this.advance(o,s),this.running&&this.options.schedule(this.tick)};let n=t.fixedDeltaMs??He,r=t.maxSubSteps??rn;this.options={fixedDeltaMs:n,maxSubSteps:r,maxDeltaMs:t.maxDeltaMs??n*r,startTimeMs:t.startTimeMs,now:t.now??sn,schedule:t.schedule??on}}start(){this.running||(this.running=!0,this.lastTimeMs=this.options.startTimeMs??this.options.now(),this.options.schedule(this.tick))}stop(){this.running=!1}pump(e){let t=(this.lastTimeMs??0)+e;this.lastTimeMs=t,this.advance(e,t)}isRunning(){return this.running}get frameNumber(){return this.frame}advance(e,t){let n=Math.min(Math.max(e,0),this.options.maxDeltaMs);this.accumulatorMs=Math.min(this.accumulatorMs+n,this.options.fixedDeltaMs*this.options.maxSubSteps);let r=0;for(;this.accumulatorMs>=this.options.fixedDeltaMs&&r<this.options.maxSubSteps;)this.frame+=1,this.callbacks.simulate({frame:this.frame,deltaMs:this.options.fixedDeltaMs,nowMs:t}),this.accumulatorMs-=this.options.fixedDeltaMs,r+=1;let s=this.options.fixedDeltaMs===0?0:this.accumulatorMs/this.options.fixedDeltaMs;this.callbacks.render?.({alpha:s,nowMs:t,accumulatorMs:this.accumulatorMs,frame:this.frame})}},an=class{constructor(e,t,n={}){this.game=e,this.client=t,this.started=!1,this.stepSimulation=s=>{this.previousFrame=this.latestFrame,this.latestFrame=this.game.frame(s)},this.renderClient=s=>{this.client&&this.client.render({...s,previous:this.previousFrame,latest:this.latestFrame})};let r=n.loop?.now?.()??Date.now();this.startTimeMs=n.startTimeMs??n.loop?.startTimeMs??r,this.loop=new Ke({simulate:this.stepSimulation,render:this.renderClient},{...n.loop,startTimeMs:this.startTimeMs})}start(){this.started||(this.latestFrame=this.game.init(this.startTimeMs)??this.latestFrame,this.client?.init(this.latestFrame),this.started=!0,this.loop.start())}stop(){this.started&&(this.loop.stop(),this.client?.shutdown(),this.game.shutdown(),this.previousFrame=void 0,this.latestFrame=void 0,this.started=!1)}pump(e){this.loop.pump(e)}getLatestFrame(){return this.latestFrame}isRunning(){return this.loop.isRunning()}};var _s=Math.PI/180;var ws=Math.PI/180,As=180/Math.PI,v=1,_=2;var ye=8,ve=16,cn=32;var un=16384;var K=65536,Xe=1<<17,ln=1<<18,hn=1<<19,dn=1<<20,fn=1<<21,mn=1<<22,pn=1<<23,zs=1<<24,X=1<<25,yn=1<<26,Ns=1<<27,Rs=1<<28,Ls=1<<29,$=1<<30,Os=1<<31;var Ms=1<<25,bs=1<<28,ks=1<<29,Cs=1<<30,Ps=1<<31,Ds=v|_,Us=v|K|_|X|$,Fs=v|K|_,Is=v|Xe|_|X|$,Bs=cn|ye|ve,Gs=v|ve|ye,vn=v|X|$|_|yn,Ws=ln|hn|dn|fn|mn|pn,Vs=v|ye|ve|X|$,Hs=v|K|_,Ks=v|_,Xs=v|K|_|Xe,$s=vn|un,Ys=Number.MAX_SAFE_INTEGER-1;var $e=256,gn=256,xn=8192,En=2048,Sn=512,Tn=256,_n=$e*2,wn=256,fe=32;var Ye=(e=>(e[e.Name=0]="Name",e[e.CdTrack=1]="CdTrack",e[e.Sky=2]="Sky",e[e.SkyAxis=3]="SkyAxis",e[e.SkyRotate=4]="SkyRotate",e[e.StatusBar=5]="StatusBar",e[e.AirAccel=59]="AirAccel",e[e.MaxClients=60]="MaxClients",e[e.MapChecksum=61]="MapChecksum",e[e.Models=62]="Models",e[e.Sounds=62+xn]="Sounds",e[e.Images=e.Sounds+En]="Images",e[e.Lights=e.Images+Sn]="Lights",e[e.ShadowLights=e.Lights+gn]="ShadowLights",e[e.Items=e.ShadowLights+wn]="Items",e[e.PlayerSkins=e.Items+Tn]="PlayerSkins",e[e.General=e.PlayerSkins+$e]="General",e[e.WheelWeapons=e.General+_n]="WheelWeapons",e[e.WheelAmmo=e.WheelWeapons+fe]="WheelAmmo",e[e.WheelPowerups=e.WheelAmmo+fe]="WheelPowerups",e[e.CdLoopCount=e.WheelPowerups+fe]="CdLoopCount",e[e.GameStyle=e.CdLoopCount+1]="GameStyle",e[e.MaxConfigStrings=e.GameStyle+1]="MaxConfigStrings",e))(Ye||{}),js=Ye.MaxConfigStrings;var An=class{constructor(e,t){this.engine=e,this.host=t,this.started=!1}start(){this.started||(this.engine.init(),this.host.start(),this.started=!0)}stop(){this.started&&(this.host.stop(),this.engine.shutdown(),this.started=!1)}pump(e){this.host.pump(e)}getLatestFrame(){return this.host.getLatestFrame()}isRunning(){return this.started&&this.host.isRunning()}};function je(e,t,n,r){return new An(e,new an(t,n,r))}function zn(){let e=new Uint32Array(256);for(let t=0;t<256;t+=1){let n=t;for(let r=0;r<8;r+=1)n=(n&1)!==0?3988292384^n>>>1:n>>>1;e[t]=n>>>0}return e}var qs=zn();var Js=Object.freeze([{name:"pak0.pak",checksum:2378051181,description:"Base game assets"},{name:"pak0.pak@baseq2",checksum:2378051181,description:"Base game assets (baseq2)"},{name:"pak0.pak@rogue",checksum:3373211245,description:"Ground Zero (rogue) mission pack"},{name:"pak0.pak@xatrix",checksum:1358269824,description:"The Reckoning (xatrix) mission pack"}]);var pe=4,me=7*pe,Zs=[{index:0,size:3,type:5126,stride:me,offset:0},{index:1,size:2,type:5126,stride:me,offset:3*pe},{index:2,size:2,type:5126,stride:me,offset:5*pe}];var Qs=new Float32Array([-1,-1,1,1,-1,1,1,1,1,-1,-1,1,1,1,1,-1,1,1,-1,-1,-1,-1,1,-1,1,1,-1,-1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,-1,1,-1,1,1,-1,-1,-1,-1,1,1,-1,1,-1,1,-1,-1,1,1,-1,1,1,1,1,-1,-1,1,1,1,1,-1,1,-1,1,-1,-1,1,1,1,1,1,-1,1,-1,1,1,1,1,1,-1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,-1,1,-1,1,-1,-1,1]);function qe(e){return{init(){e.trace({x:0,y:0,z:0},{x:0,y:0,z:0})},shutdown(){},createMainLoop(t,n){return new Ke(t,n)}}}var Nn={x:0,y:0,z:0},si=Math.PI/180;var ii=Math.PI/180,oi=180/Math.PI;var g=624,Rn=397,Ln=2567483615,On=2147483648,Mn=2147483647,ge=4294967296,bn=class{constructor(e=5489){this.state=new Uint32Array(g),this.index=g,this.seed(e)}seed(e){this.state[0]=e>>>0;for(let t=1;t<g;t++){let n=this.state[t-1]^this.state[t-1]>>>30,r=Math.imul(n>>>0,1812433253)+t;this.state[t]=r>>>0}this.index=g}nextUint32(){this.index>=g&&this.twist();let e=this.state[this.index++];return e^=e>>>11,e^=e<<7&2636928640,e^=e<<15&4022730752,e^=e>>>18,e>>>0}twist(){for(let e=0;e<g;e++){let t=this.state[e]&On|this.state[(e+1)%g]&Mn,n=this.state[(e+Rn)%g]^t>>>1;(t&1)!==0&&(n^=Ln),this.state[e]=n>>>0}this.index=0}getState(){return{index:this.index,state:Array.from(this.state)}}setState(e){if(e.state.length!==g)throw new Error(`Expected ${g} MT state values, received ${e.state.length}`);this.index=e.index,this.state=Uint32Array.from(e.state,t=>t>>>0)}},et=class{constructor(e={}){this.mt=new bn(e.seed)}frandom(){return this.mt.nextUint32()/ge}frandomRange(e,t){return e+(t-e)*this.frandom()}frandomMax(e){return this.frandomRange(0,e)}crandom(){return this.frandomRange(-1,1)}crandomOpen(){let e=Number.EPSILON;return this.frandomRange(-1+e,1)}irandomUint32(){return this.mt.nextUint32()}irandomRange(e,t){if(t-e<=1)return e;let n=t-e,r=ge-ge%n,s;do s=this.mt.nextUint32();while(s>=r);return e+s%n}irandom(e){return e<=0?0:this.irandomRange(0,e)}randomTimeRange(e,t){return t<=e?e:this.irandomRange(e,t)}randomTime(e){return this.irandom(e)}randomIndex(e){return this.irandom(e.length)}getState(){return{mt:this.mt.getState()}}setState(e){this.mt.setState(e.mt)}};function kn(e){return new et(e)}var x=1,w=2;var Se=8,Te=16,Cn=32;var Pn=16384;var Q=65536,tt=1<<17,Dn=1<<18,Un=1<<19,Fn=1<<20,In=1<<21,Bn=1<<22,Gn=1<<23,ai=1<<24,ee=1<<25,Wn=1<<26,ci=1<<27,ui=1<<28,li=1<<29,te=1<<30,hi=1<<31;var di=1<<25,fi=1<<28,mi=1<<29,pi=1<<30,yi=1<<31,vi=x|w,gi=x|Q|w|ee|te,xi=x|Q|w,Ei=x|tt|w|ee|te,Si=Cn|Se|Te,Ti=x|Te|Se,Vn=x|ee|te|w|Wn,_i=Dn|Un|Fn|In|Bn|Gn,wi=x|Se|Te|ee|te,Ai=x|Q|w,zi=x|w,Ni=x|Q|w|tt,Ri=Vn|Pn,Li=Number.MAX_SAFE_INTEGER-1;var nt=256,Hn=256,Kn=8192,Xn=2048,$n=512,Yn=256,jn=nt*2,qn=256,xe=32,rt=(e=>(e[e.Name=0]="Name",e[e.CdTrack=1]="CdTrack",e[e.Sky=2]="Sky",e[e.SkyAxis=3]="SkyAxis",e[e.SkyRotate=4]="SkyRotate",e[e.StatusBar=5]="StatusBar",e[e.AirAccel=59]="AirAccel",e[e.MaxClients=60]="MaxClients",e[e.MapChecksum=61]="MapChecksum",e[e.Models=62]="Models",e[e.Sounds=62+Kn]="Sounds",e[e.Images=e.Sounds+Xn]="Images",e[e.Lights=e.Images+$n]="Lights",e[e.ShadowLights=e.Lights+Hn]="ShadowLights",e[e.Items=e.ShadowLights+qn]="Items",e[e.PlayerSkins=e.Items+Yn]="PlayerSkins",e[e.General=e.PlayerSkins+nt]="General",e[e.WheelWeapons=e.General+jn]="WheelWeapons",e[e.WheelAmmo=e.WheelWeapons+xe]="WheelAmmo",e[e.WheelPowerups=e.WheelAmmo+xe]="WheelPowerups",e[e.CdLoopCount=e.WheelPowerups+xe]="CdLoopCount",e[e.GameStyle=e.CdLoopCount+1]="GameStyle",e[e.MaxConfigStrings=e.GameStyle+1]="MaxConfigStrings",e))(rt||{}),Oi=rt.MaxConfigStrings;var Jn={...Nn};function h(){return{...Jn}}var Je=Object.freeze({aiflags:0}),Zn=class{constructor(e){this.inUse=!1,this.freePending=!1,this.linkPrevious=null,this.linkNext=null,this.classname="",this.spawnflags=0,this.inventory={},this.origin=h(),this.old_origin=h(),this.velocity=h(),this.avelocity=h(),this.angles=h(),this.viewheight=0,this.mins=h(),this.maxs=h(),this.size=h(),this.mass=0,this.gravity=1,this.movetype=0,this.movedir=h(),this.modelindex=0,this.frame=0,this.skin=0,this.effects=0,this.renderfx=0,this.health=0,this.max_health=0,this.takedamage=!1,this.dmg=0,this.speed=0,this.deadflag=0,this.count=0,this.wait=0,this.delay=0,this.timestamp=0,this.sounds=0,this.noise_index=0,this.fly_sound_debounce_time=0,this.enemy=null,this.movetarget=null,this.target_ent=null,this.goalentity=null,this.ideal_yaw=0,this.yaw_speed=0,this.search_time=0,this.attack_finished_time=0,this.pain_finished_time=0,this.trail_time=0,this.groundentity=null,this.groundentity_linkcount=0,this.waterlevel=0,this.watertype=0,this.nextthink=0,this.activator=null,this.solid=0,this.flags=0,this.svflags=0,this.monsterinfo={...Je},this.index=e}reset(){this.inUse=!1,this.freePending=!1,this.linkPrevious=null,this.linkNext=null,this.classname="",this.spawnflags=0,this.target=void 0,this.targetname=void 0,this.killtarget=void 0,this.team=void 0,this.message=void 0,this.pathtarget=void 0,this.model=void 0,this.item=void 0,this.inventory={},this.origin=h(),this.old_origin=h(),this.velocity=h(),this.avelocity=h(),this.angles=h(),this.viewheight=0,this.mins=h(),this.maxs=h(),this.size=h(),this.mass=0,this.gravity=1,this.movetype=0,this.movedir=h(),this.modelindex=0,this.frame=0,this.skin=0,this.effects=0,this.renderfx=0,this.health=0,this.max_health=0,this.takedamage=!1,this.dmg=0,this.speed=0,this.deadflag=0,this.count=0,this.wait=0,this.delay=0,this.timestamp=0,this.sounds=0,this.noise_index=0,this.fly_sound_debounce_time=0,this.enemy=null,this.movetarget=null,this.target_ent=null,this.goalentity=null,this.ideal_yaw=0,this.yaw_speed=0,this.search_time=0,this.attack_finished_time=0,this.pain_finished_time=0,this.trail_time=0,this.groundentity=null,this.groundentity_linkcount=0,this.waterlevel=0,this.watertype=0,this.nextthink=0,this.think=void 0,this.touch=void 0,this.use=void 0,this.pain=void 0,this.die=void 0,this.activator=null,this.solid=0,this.flags=0,this.svflags=0,this.monsterinfo={...Je}}},st=[{name:"classname",type:"string",save:!0},{name:"spawnflags",type:"int",save:!0},{name:"target",type:"string",save:!0},{name:"targetname",type:"string",save:!0},{name:"killtarget",type:"string",save:!0},{name:"team",type:"string",save:!0},{name:"message",type:"string",save:!0},{name:"pathtarget",type:"string",save:!0},{name:"model",type:"string",save:!0},{name:"item",type:"string",save:!0},{name:"inventory",type:"inventory",save:!0},{name:"origin",type:"vec3",save:!0},{name:"old_origin",type:"vec3",save:!0},{name:"velocity",type:"vec3",save:!0},{name:"avelocity",type:"vec3",save:!0},{name:"angles",type:"vec3",save:!0},{name:"viewheight",type:"int",save:!0},{name:"mins",type:"vec3",save:!0},{name:"maxs",type:"vec3",save:!0},{name:"size",type:"vec3",save:!0},{name:"mass",type:"int",save:!0},{name:"gravity",type:"float",save:!0},{name:"movetype",type:"int",save:!0},{name:"movedir",type:"vec3",save:!0},{name:"modelindex",type:"int",save:!0},{name:"frame",type:"int",save:!0},{name:"skin",type:"int",save:!0},{name:"effects",type:"int",save:!0},{name:"renderfx",type:"int",save:!0},{name:"health",type:"int",save:!0},{name:"max_health",type:"int",save:!0},{name:"takedamage",type:"boolean",save:!0},{name:"dmg",type:"int",save:!0},{name:"speed",type:"float",save:!0},{name:"deadflag",type:"int",save:!0},{name:"count",type:"int",save:!0},{name:"wait",type:"float",save:!0},{name:"delay",type:"float",save:!0},{name:"timestamp",type:"float",save:!0},{name:"sounds",type:"int",save:!0},{name:"noise_index",type:"int",save:!0},{name:"fly_sound_debounce_time",type:"float",save:!0},{name:"enemy",type:"entity",save:!0},{name:"movetarget",type:"entity",save:!0},{name:"target_ent",type:"entity",save:!0},{name:"goalentity",type:"entity",save:!0},{name:"ideal_yaw",type:"float",save:!0},{name:"yaw_speed",type:"float",save:!0},{name:"search_time",type:"float",save:!0},{name:"attack_finished_time",type:"float",save:!0},{name:"pain_finished_time",type:"float",save:!0},{name:"trail_time",type:"float",save:!0},{name:"groundentity",type:"entity",save:!0},{name:"groundentity_linkcount",type:"int",save:!0},{name:"waterlevel",type:"int",save:!0},{name:"watertype",type:"int",save:!0},{name:"nextthink",type:"float",save:!0},{name:"solid",type:"int",save:!0},{name:"flags",type:"int",save:!0},{name:"svflags",type:"int",save:!0},{name:"think",type:"callback",save:!1},{name:"touch",type:"callback",save:!1},{name:"use",type:"callback",save:!1},{name:"pain",type:"callback",save:!1},{name:"die",type:"callback",save:!1}],Qn=2048,M=0,er=class{constructor(e=Qn){if(this.freeList=[],this.pendingFree=[],this.activeHead=null,e<1||!Number.isInteger(e))throw new Error("EntityPool requires a positive integer size");this.entities=new Array(e);for(let n=0;n<e;n+=1)this.entities[n]=new Zn(n),n!==M&&this.freeList.push(n);let t=this.entities[M];t.inUse=!0,t.classname="worldspawn",this.activeHead=t}get world(){return this.entities[M]}get capacity(){return this.entities.length}get activeCount(){let e=0;for(let t of this)e+=1;return e}[Symbol.iterator](){let e=this.activeHead;return{next:()=>{if(!e)return{done:!0,value:void 0};let t=e;return e=e.linkNext,{done:!1,value:t}}}}spawn(){let e=this.freeList.pop();if(e===void 0)throw new Error("No free entities available");let t=this.entities[e];return t.reset(),t.inUse=!0,this.link(t),t}deferFree(e){if(e.index===M)throw new Error("Cannot free world entity");!e.inUse||e.freePending||(this.unlink(e),e.inUse=!1,e.freePending=!0,this.pendingFree.push(e.index))}freeImmediate(e){if(e.index===M)throw new Error("Cannot free world entity");e.inUse&&(this.unlink(e),e.reset(),this.freeList.push(e.index))}flushFreeList(){if(this.pendingFree.length!==0){for(let e of this.pendingFree)this.entities[e].reset(),this.freeList.push(e);this.pendingFree.length=0}}createSnapshot(){let e=Array.from(this,t=>t.index);return{capacity:this.entities.length,activeOrder:e,freeList:[...this.freeList],pendingFree:[...this.pendingFree]}}restore(e){if(e.capacity!==this.entities.length)throw new Error(`Snapshot capacity ${e.capacity} does not match pool capacity ${this.entities.length}`);let t=new Set,n=(r,s)=>{if(r<0||r>=this.entities.length)throw new Error(`Invalid entity index ${r} in ${s}`);if(t.has(r))throw new Error(`Duplicate entity index ${r} in snapshot`);t.add(r)};for(let r of e.activeOrder)n(r,"activeOrder");for(let r of e.freeList)n(r,"freeList");for(let r of e.pendingFree)n(r,"pendingFree");this.activeHead=null,this.freeList.length=0,this.pendingFree.length=0;for(let r of this.entities)r.reset();for(let r=e.activeOrder.length-1;r>=0;r-=1){let s=this.entities[e.activeOrder[r]];s.inUse=!0,this.link(s)}for(let r of e.pendingFree){let s=this.entities[r];s.inUse=!1,s.freePending=!0,s.linkNext=null,s.linkPrevious=null,this.pendingFree.push(r)}for(let r of e.freeList){let s=this.entities[r];s.inUse=!1,s.freePending=!1,s.linkNext=null,s.linkPrevious=null,this.freeList.push(r)}if(!e.activeOrder.includes(M))throw new Error("Snapshot must include the world entity as active")}link(e){e.linkNext=this.activeHead,this.activeHead&&(this.activeHead.linkPrevious=e),this.activeHead=e,e.linkPrevious=null}unlink(e){e.linkPrevious&&(e.linkPrevious.linkNext=e.linkNext),e.linkNext&&(e.linkNext.linkPrevious=e.linkPrevious),this.activeHead===e&&(this.activeHead=e.linkNext),e.linkPrevious=null,e.linkNext=null}},tr=class{constructor(){this.queue=[]}schedule(e,t){e.nextthink=t,this.queue.push({entity:e,time:t}),this.queue.sort((n,r)=>n.time===r.time?n.entity.index-r.entity.index:n.time-r.time)}cancel(e){if(this.queue.length!==0)for(let t=this.queue.length-1;t>=0;t-=1)this.queue[t].entity===e&&this.queue.splice(t,1)}snapshot(){return this.queue.map(({time:e,entity:t})=>({time:e,entityIndex:t.index}))}restore(e,t){this.queue.length=0;for(let n of e){let r=t(n.entityIndex);r&&this.schedule(r,n.time)}}runDueThinks(e){for(;this.queue.length>0;){let t=this.queue[0];if(t.time>e)break;this.queue.shift();let{entity:n,time:r}=t;!n.inUse||n.freePending||n.think&&n.nextthink===r&&n.think(n)}}};function Y(e){return{min:{x:e.origin.x+e.mins.x,y:e.origin.y+e.mins.y,z:e.origin.z+e.mins.z},max:{x:e.origin.x+e.maxs.x,y:e.origin.y+e.maxs.y,z:e.origin.z+e.maxs.z}}}function Ze(e,t){return!(e.min.x>t.max.x||e.max.x<t.min.x||e.min.y>t.max.y||e.max.y<t.min.y||e.min.z>t.max.z||e.max.z<t.min.z)}var it=st.filter(e=>e.save),nr=new Map(it.map(e=>[e.name,e]));function rr(e){return[e.x,e.y,e.z]}function sr(e){let t=e;if(!Array.isArray(t)||t.length!==3)throw new Error("Invalid vec3 serialization");let[n,r,s]=t;return{x:n,y:r,z:s}}function U(e,t,n){e[t]=n}function ir(e){return{...e}}function or(e){if(e===null||typeof e!="object"||Array.isArray(e))throw new Error("Invalid inventory serialization");let t={};for(let[n,r]of Object.entries(e))t[n]=Number(r);return t}var ar=class{constructor(e){this.targetNameIndex=new Map,this.random=kn(),this.currentTimeSeconds=0,this.pool=new er(e),this.thinkScheduler=new tr}get world(){return this.pool.world}get activeCount(){return this.pool.activeCount}get timeSeconds(){return this.currentTimeSeconds}forEachEntity(e){for(let t of this.pool)e(t)}spawn(){return this.pool.spawn()}free(e){this.unregisterTarget(e),this.thinkScheduler.cancel(e),this.pool.deferFree(e)}freeImmediate(e){this.unregisterTarget(e),this.thinkScheduler.cancel(e),this.pool.freeImmediate(e)}scheduleThink(e,t){this.thinkScheduler.schedule(e,t)}beginFrame(e){this.currentTimeSeconds=e}finalizeSpawn(e){!e.inUse||e.freePending||this.registerTarget(e)}findByClassname(e){let t=[];for(let n of this.pool)n.classname===e&&n.inUse&&!n.freePending&&t.push(n);return t}findByTargetName(e){let t=this.targetNameIndex.get(e);return t?Array.from(t).filter(n=>n.inUse&&!n.freePending):[]}pickTarget(e){if(!e)return null;let t=this.findByTargetName(e);if(t.length===0)return null;let n=this.random.randomIndex(t);return t[n]??null}killBox(e){let t=Y(e);for(let n of this.pool)n===e||n===this.pool.world||!n.inUse||n.freePending||n.solid===0||n.svflags&2||Ze(t,Y(n))&&(n.health=0,n.deadflag=2,this.free(n))}useTargets(e,t=null){if(e.delay>0){let n=this.spawn();n.classname="DelayedUse",n.target=e.target,n.killtarget=e.killtarget,n.message=e.message,n.think=r=>{this.useTargetsImmediate(r,t??e),this.free(r)},this.scheduleThink(n,this.currentTimeSeconds+e.delay);return}this.useTargetsImmediate(e,t??e)}runFrame(){this.thinkScheduler.runDueThinks(this.currentTimeSeconds),this.runTouches(),this.pool.flushFreeList()}createSnapshot(){let e=[];for(let t of this.pool){let n={};for(let r of it){let s=t[r.name];switch(r.type){case"vec3":n[r.name]=rr(s);break;case"entity":n[r.name]=s?.index??null;break;case"inventory":n[r.name]=ir(s);break;default:n[r.name]=s??null;break}}e.push({index:t.index,fields:n})}return{timeSeconds:this.currentTimeSeconds,pool:this.pool.createSnapshot(),entities:e,thinks:this.thinkScheduler.snapshot()}}restore(e){this.currentTimeSeconds=e.timeSeconds,this.pool.restore(e.pool);let t=new Map;for(let r of this.pool)t.set(r.index,r);let n=[];for(let r of e.entities){let s=t.get(r.index);if(s)for(let[o,i]of Object.entries(r.fields)){let a=nr.get(o);if(!(!a||i===void 0))switch(a.type){case"vec3":U(s,o,sr(i));break;case"entity":n.push({entity:s,name:a.name,targetIndex:i});break;case"inventory":U(s,o,or(i));break;case"boolean":U(s,o,!!i);break;default:U(s,o,i);break}}}for(let r of n){let s=r.targetIndex===null?null:t.get(r.targetIndex)??null;U(r.entity,r.name,s)}this.thinkScheduler.restore(e.thinks,r=>t.get(r))}runTouches(){let e=this.pool.world,t=[];for(let n of this.pool)n!==e&&(!n.inUse||n.freePending||n.solid===0||t.push(n));for(let n=0;n<t.length;n+=1){let r=t[n],s=null;for(let o=n+1;o<t.length;o+=1){let i=t[o];if(!r.touch&&!i.touch)continue;s||(s=Y(r));let a=Y(i);Ze(s,a)&&(r.touch&&r.touch(r,i),i.touch&&i.touch(i,r))}}}registerTarget(e){if(!e.targetname)return;let t=this.targetNameIndex.get(e.targetname);t||(t=new Set,this.targetNameIndex.set(e.targetname,t)),t.add(e)}unregisterTarget(e){if(!e.targetname)return;let t=this.targetNameIndex.get(e.targetname);t&&(t.delete(e),t.size===0&&this.targetNameIndex.delete(e.targetname))}useTargetsImmediate(e,t){if(e.target)for(let n of this.findByTargetName(e.target))n!==e&&n.use?.(n,e,t);if(e.killtarget)for(let n of this.findByTargetName(e.killtarget))n!==e&&this.free(n)}};var Mi=1/40;var bi=new Map(st.map(e=>[e.name,e]));var cr=["prep","simulate","finish"],ur=class{constructor(e){if(this.timeMs=0,this.frame=0,this.stageHandlers={prep:[],simulate:[],finish:[]},this.stageCounts={prep:0,simulate:0,finish:0},this.stageCompactionNeeded={prep:!1,simulate:!1,finish:!1},e)for(let t of cr){let n=e[t];n&&this.addStage(t,n)}}addStage(e,t){let n=this.stageHandlers[e];return n.push(t),this.stageCounts[e]+=1,()=>{let r=n.indexOf(t);r>=0&&n[r]&&(n[r]=void 0,this.stageCounts[e]-=1,this.stageCompactionNeeded[e]=!0)}}reset(e){this.timeMs=e,this.frame=0}advance(e){let t=this.timeMs;this.timeMs=t+e.deltaMs,this.frame=e.frame;let n={...e,timeMs:this.timeMs,previousTimeMs:t,deltaSeconds:e.deltaMs/1e3};if(this.runStage("prep",n),this.stageCounts.simulate===0)throw new Error("GameFrameLoop requires at least one simulate stage");return this.runStage("simulate",n),this.runStage("finish",n),n}runStage(e,t){let n=this.stageHandlers[e];for(let r=0;r<n.length;r+=1){let s=n[r];s&&s(t)}this.stageCompactionNeeded[e]&&this.compactStageHandlers(e)}compactStageHandlers(e){let t=this.stageHandlers[e],n=0;for(let r=0;r<t.length;r+=1){let s=t[r];s&&(t[n]=s,n+=1)}t.length=n,this.stageCompactionNeeded[e]=!1}get time(){return this.timeMs}get frameNumber(){return this.frame}},lr={frameNumber:0,timeSeconds:0,previousTimeSeconds:0,deltaSeconds:0},hr=class{constructor(){this.state=lr}start(e){let t=e/1e3;this.state={frameNumber:0,timeSeconds:t,previousTimeSeconds:t,deltaSeconds:0}}tick(e){return this.state={frameNumber:e.frame,timeSeconds:e.timeMs/1e3,previousTimeSeconds:e.previousTimeMs/1e3,deltaSeconds:e.deltaSeconds},this.state}get current(){return this.state}restore(e){this.state={...e}}};var ki=1<<24;var Ee=1,dr=1;function f(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`${t} must be an object`);return e}function d(e,t){if(typeof e!="number"||!Number.isFinite(e))throw new Error(`${t} must be a finite number`);return e}function j(e,t,n){return e===void 0?n:d(e,t)}function Z(e,t){if(typeof e!="string")throw new Error(`${t} must be a string`);return e}function J(e,t){if(!Array.isArray(e))throw new Error(`${t} must be an array`);for(let n of e)d(n,`${t} element`);return e}function fr(e){if(e===void 0)return{frameNumber:0,timeSeconds:0,previousTimeSeconds:0,deltaSeconds:0};let t=f(e,"level");return{frameNumber:j(t.frameNumber,"level.frameNumber",0),timeSeconds:j(t.timeSeconds,"level.timeSeconds",0),previousTimeSeconds:j(t.previousTimeSeconds,"level.previousTimeSeconds",0),deltaSeconds:j(t.deltaSeconds,"level.deltaSeconds",0)}}function mr(e){if(e===void 0)return new et().getState();let t=f(e,"rng"),n=f(t.mt,"rng.mt"),r=J(n.state,"rng.mt.state");return{mt:{index:d(n.index,"rng.mt.index"),state:r}}}function pr(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("thinks must be an array");return e.map((t,n)=>{let r=f(t,`thinks[${n}]`);return{time:d(r.time,`thinks[${n}].time`),entityIndex:d(r.entityIndex,`thinks[${n}].entityIndex`)}})}function yr(e){if(e===void 0)return{};let t=f(e,"entity.fields"),n={};for(let[r,s]of Object.entries(t)){if(s===null){n[r]=null;continue}switch(typeof s){case"number":case"string":case"boolean":n[r]=s;break;default:{if(!Array.isArray(s)){let o=f(s,r),i={};for(let[a,u]of Object.entries(o))i[a]=d(u,`${r}.${a}`);n[r]=i;break}if(Array.isArray(s)&&s.length===3){let[o,i,a]=s;n[r]=[d(o,`${r}[0]`),d(i,`${r}[1]`),d(a,`${r}[2]`)];break}throw new Error(`Unsupported entity field value for ${r}`)}}}return n}function vr(e){if(!Array.isArray(e))throw new Error("entities must be an array");return e.map((t,n)=>{let r=f(t,`entities[${n}]`);return{index:d(r.index,`entities[${n}].index`),fields:yr(r.fields)}})}function gr(e){let t=f(e,"pool");return{capacity:d(t.capacity,"pool.capacity"),activeOrder:J(t.activeOrder,"pool.activeOrder"),freeList:J(t.freeList,"pool.freeList"),pendingFree:J(t.pendingFree,"pool.pendingFree")}}function xr(e){let t=f(e,"entities");return{timeSeconds:d(t.timeSeconds,"entities.timeSeconds"),pool:gr(t.pool),entities:vr(t.entities),thinks:pr(t.thinks)}}function Er(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("cvars must be an array");return e.map((t,n)=>{let r=f(t,`cvars[${n}]`);return{name:Z(r.name,`cvars[${n}].name`),value:Z(r.value,`cvars[${n}].value`),flags:d(r.flags,`cvars[${n}].flags`)}})}function Sr(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("configstrings must be an array");return e.map((t,n)=>Z(t,`configstrings[${n}]`))}function Tr(e){return e===void 0?{}:f(e,"gameState")}function ot(e,t={}){let{allowNewerVersion:n=!0}=t,r=typeof e=="string"?JSON.parse(e):e,s=f(r,"save"),o=s.version??Ee,i=d(o,"version");if(i<dr)throw new Error(`Unsupported save version ${i}`);if(i>Ee&&!n)throw new Error(`Save version ${i} is newer than supported ${Ee}`);return{version:i,timestamp:d(s.timestamp,"timestamp"),map:Z(s.map,"map"),difficulty:d(s.difficulty,"difficulty"),playtimeSeconds:d(s.playtimeSeconds,"playtimeSeconds"),gameState:Tr(s.gameState),level:fr(s.level),rng:mr(s.rng),entities:xr(s.entities),cvars:Er(s.cvars),configstrings:Sr(s.configstrings)}}var Qe=globalThis.TextEncoder;function b(e){return ot({...e},{allowNewerVersion:!0})}function _r(e){return Qe?new Qe().encode(JSON.stringify(e)).length:JSON.stringify(e).length}var wr=class{constructor(){this.records=new Map}async init(){return Promise.resolve()}async put(e){let t={id:e.id,metadata:{...e.metadata},save:b(e.save)};this.records.set(e.id,t)}async get(e){let t=this.records.get(e);return t?{id:t.id,metadata:{...t.metadata},save:b(t.save)}:null}async delete(e){return this.records.delete(e)}async list(){return Array.from(this.records.values()).map(e=>({id:e.id,metadata:{...e.metadata},save:b(e.save)}))}},Ar=class{constructor(e,t,n){this.indexedDB=e,this.dbName=t,this.storeName=n,this.db=null}async init(){this.db||(this.db=await new Promise((e,t)=>{let n=this.indexedDB.open(this.dbName,1);n.onupgradeneeded=()=>{n.result.createObjectStore(this.storeName,{keyPath:"id"})},n.onerror=()=>t(n.error??new Error("Failed to open IndexedDB")),n.onsuccess=()=>e(n.result)}))}async runTransaction(e,t){await this.init();let n=this.db;return new Promise((r,s)=>{let i=n.transaction(this.storeName,e).objectStore(this.storeName),a=t(i);a.onsuccess=()=>r(a.result),a.onerror=()=>s(a.error??new Error("IndexedDB request failed"))})}async put(e){await this.runTransaction("readwrite",t=>t.put(e))}async get(e){let t=await this.runTransaction("readonly",n=>n.get(e));return t?{id:t.id,metadata:{...t.metadata},save:b(t.save)}:null}async delete(e){return await this.get(e)?(await this.runTransaction("readwrite",n=>n.delete(e)),!0):!1}async list(){return(await this.runTransaction("readonly",t=>t.getAll())).map(t=>({id:t.id,metadata:{...t.metadata},save:b(t.save)}))}},_e=class F{constructor(t={}){let{dbName:n=F.DEFAULT_DB_NAME,storeName:r=F.DEFAULT_STORE}=t,s=t.indexedDB??globalThis.indexedDB;s?this.adapter=new Ar(s,n,r):this.adapter=new wr}async save(t,n,r={}){let s=b(n),o={id:t,name:r.name??t,map:s.map,difficulty:s.difficulty,playtimeSeconds:s.playtimeSeconds,timestamp:s.timestamp,version:s.version,bytes:_r(s)};return await this.adapter.init(),await this.adapter.put({id:t,metadata:o,save:s}),o}async load(t,n={}){await this.adapter.init();let r=await this.adapter.get(t);if(!r)throw new Error(`Save slot ${t} not found`);return ot(r.save,n)}async delete(t){return await this.adapter.init(),this.adapter.delete(t)}async list(){return await this.adapter.init(),(await this.adapter.list()).map(n=>({...n.metadata})).sort((n,r)=>r.timestamp-n.timestamp||n.id.localeCompare(r.id))}async quickSave(t){return this.save(F.QUICK_SLOT,t,{name:"Quick Save"})}async quickLoad(t={}){return this.load(F.QUICK_SLOT,t)}};_e.DEFAULT_DB_NAME="quake2ts-saves";_e.DEFAULT_STORE="saves";_e.QUICK_SLOT="quicksave";var at=(e=>(e[e.Bullets=0]="Bullets",e[e.Shells=1]="Shells",e[e.Rockets=2]="Rockets",e[e.Grenades=3]="Grenades",e[e.Cells=4]="Cells",e[e.Slugs=5]="Slugs",e))(at||{}),Ci=Object.keys(at).length/2;var q={x:0,y:0,z:0};function ct(e,t){let n=t.gravity,r=new hr,s=new ur,o=new ar;s.addStage("prep",c=>{r.tick(c),o.beginFrame(r.current.timeSeconds)}),s.addStage("simulate",({deltaSeconds:c})=>{a={x:a.x+n.x*c,y:a.y+n.y*c,z:a.z+n.z*c},i={x:i.x+a.x*c,y:i.y+a.y*c,z:i.z+a.z*c},o.runFrame()});let i={...q},a={...q},u=c=>({frame:c,timeMs:s.time,state:{gravity:{...n},origin:{...i},velocity:{...a},level:{...r.current},entities:{activeCount:o.activeCount,worldClassname:o.world.classname}}}),l=c=>{s.reset(c),r.start(c),i={...q},a={...q},o.beginFrame(c/1e3),o.runFrame()};return{init(c){return l(c),e.trace({x:0,y:0,z:0},n),u(0)},shutdown(){},spawnWorld(){},frame(c){let m=s.advance(c);return u(m.frame)},entities:o}}var ne={x:0,y:0,z:0};var Xi=Math.PI/180;var $i=Math.PI/180,Yi=180/Math.PI;var E=1,A=2;var Ae=8,ze=16,zr=32;var Nr=16384;var re=65536,ut=1<<17,Rr=1<<18,Lr=1<<19,Or=1<<20,Mr=1<<21,br=1<<22,kr=1<<23,ji=1<<24,se=1<<25,Cr=1<<26,qi=1<<27,Ji=1<<28,Zi=1<<29,ie=1<<30,Qi=1<<31;var eo=1<<25,to=1<<28,no=1<<29,ro=1<<30,so=1<<31;var io=E|A,oo=E|re|A|se|ie,ao=E|re|A,co=E|ut|A|se|ie,uo=zr|Ae|ze,lo=E|ze|Ae,Pr=E|se|ie|A|Cr,ho=Rr|Lr|Or|Mr|br|kr,fo=E|Ae|ze|se|ie,mo=E|re|A,po=E|A,yo=E|re|A|ut,vo=Pr|Nr;var go=Number.MAX_SAFE_INTEGER-1;var lt=256;var Dr=256,Ur=8192,Fr=2048,Ir=512,Br=256,Gr=lt*2,Wr=256,we=32;var ht=(e=>(e[e.Name=0]="Name",e[e.CdTrack=1]="CdTrack",e[e.Sky=2]="Sky",e[e.SkyAxis=3]="SkyAxis",e[e.SkyRotate=4]="SkyRotate",e[e.StatusBar=5]="StatusBar",e[e.AirAccel=59]="AirAccel",e[e.MaxClients=60]="MaxClients",e[e.MapChecksum=61]="MapChecksum",e[e.Models=62]="Models",e[e.Sounds=62+Ur]="Sounds",e[e.Images=e.Sounds+Fr]="Images",e[e.Lights=e.Images+Ir]="Lights",e[e.ShadowLights=e.Lights+Dr]="ShadowLights",e[e.Items=e.ShadowLights+Wr]="Items",e[e.PlayerSkins=e.Items+Br]="PlayerSkins",e[e.General=e.PlayerSkins+lt]="General",e[e.WheelWeapons=e.General+Gr]="WheelWeapons",e[e.WheelAmmo=e.WheelWeapons+we]="WheelAmmo",e[e.WheelPowerups=e.WheelAmmo+we]="WheelPowerups",e[e.CdLoopCount=e.WheelPowerups+we]="CdLoopCount",e[e.GameStyle=e.CdLoopCount+1]="GameStyle",e[e.MaxConfigStrings=e.GameStyle+1]="MaxConfigStrings",e))(ht||{}),xo=ht.MaxConfigStrings;function Vr(){let e=qe({trace(s,o){return{start:s,end:o,fraction:1}}}),t=ct({trace(s,o){return{start:s,end:o,fraction:1}}},{gravity:ne}),n=Ve({engine:{trace:()=>({start:ne,end:ne,fraction:1})}}),r=je(e,t,n);return r.start(),{engine:e,game:t,client:n,runtime:r}}return gt(Hr);})();
2
2
  //# sourceMappingURL=index.global.js.map