@netforge/engine 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/GAME-DESIGN.md +195 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +30 -0
  4. package/README.md +100 -2
  5. package/dist/api/appearance.d.ts +37 -0
  6. package/dist/api/appearance.js +41 -0
  7. package/dist/api/cards.d.ts +105 -0
  8. package/dist/api/cards.js +133 -0
  9. package/dist/api/collector.d.ts +114 -0
  10. package/dist/api/collector.js +204 -0
  11. package/dist/api/dungeon.d.ts +58 -0
  12. package/dist/api/dungeon.js +216 -0
  13. package/dist/api/harness.d.ts +149 -0
  14. package/dist/api/harness.js +549 -0
  15. package/dist/api/idle.d.ts +71 -0
  16. package/dist/api/idle.js +104 -0
  17. package/dist/api/race-harness.d.ts +3 -0
  18. package/dist/api/race-harness.js +148 -0
  19. package/dist/api/registry.d.ts +241 -0
  20. package/dist/api/registry.js +394 -0
  21. package/dist/api/scheduler.d.ts +56 -0
  22. package/dist/api/scheduler.js +160 -0
  23. package/dist/api/schemas.d.ts +958 -0
  24. package/dist/api/schemas.js +859 -0
  25. package/dist/api/tower.d.ts +127 -0
  26. package/dist/api/tower.js +183 -0
  27. package/dist/game/adapter.d.ts +69 -0
  28. package/dist/game/adapter.js +341 -0
  29. package/dist/game/arena-bot.d.ts +66 -0
  30. package/dist/game/arena-bot.js +188 -0
  31. package/dist/game/bot.d.ts +95 -0
  32. package/dist/game/bot.js +285 -0
  33. package/dist/game/compile.d.ts +97 -0
  34. package/dist/game/compile.js +476 -0
  35. package/dist/game/director.d.ts +42 -0
  36. package/dist/game/director.js +179 -0
  37. package/dist/game/earn-source.d.ts +122 -0
  38. package/dist/game/earn-source.js +386 -0
  39. package/dist/game/earn.d.ts +49 -0
  40. package/dist/game/earn.js +55 -0
  41. package/dist/game/index.d.ts +37 -0
  42. package/dist/game/index.js +25 -0
  43. package/dist/game/platformer.d.ts +156 -0
  44. package/dist/game/platformer.js +619 -0
  45. package/dist/game/profile.d.ts +97 -0
  46. package/dist/game/profile.js +140 -0
  47. package/dist/game/race.d.ts +177 -0
  48. package/dist/game/race.js +386 -0
  49. package/dist/game/racer.d.ts +98 -0
  50. package/dist/game/racer.js +379 -0
  51. package/dist/game/runner.d.ts +286 -0
  52. package/dist/game/runner.js +824 -0
  53. package/dist/game/threats.d.ts +23 -0
  54. package/dist/game/threats.js +49 -0
  55. package/dist/game/view.d.ts +47 -0
  56. package/dist/game/view.js +123 -0
  57. package/dist/index.d.ts +33 -0
  58. package/dist/index.js +24 -0
  59. package/dist/net/bits.d.ts +33 -0
  60. package/dist/net/bits.js +135 -0
  61. package/dist/net/client.d.ts +75 -0
  62. package/dist/net/client.js +151 -0
  63. package/dist/net/index.d.ts +23 -0
  64. package/dist/net/index.js +23 -0
  65. package/dist/net/input.d.ts +49 -0
  66. package/dist/net/input.js +112 -0
  67. package/dist/net/interpolation.d.ts +37 -0
  68. package/dist/net/interpolation.js +97 -0
  69. package/dist/net/netsim.d.ts +22 -0
  70. package/dist/net/netsim.js +52 -0
  71. package/dist/net/prediction.d.ts +31 -0
  72. package/dist/net/prediction.js +101 -0
  73. package/dist/net/profile-client.d.ts +75 -0
  74. package/dist/net/profile-client.js +173 -0
  75. package/dist/net/protocol.d.ts +215 -0
  76. package/dist/net/protocol.js +10 -0
  77. package/dist/net/room-client.d.ts +65 -0
  78. package/dist/net/room-client.js +156 -0
  79. package/dist/net/schema.d.ts +78 -0
  80. package/dist/net/schema.js +121 -0
  81. package/dist/net/sim-adapter.d.ts +134 -0
  82. package/dist/net/sim-adapter.js +22 -0
  83. package/dist/net/snapshot.d.ts +61 -0
  84. package/dist/net/snapshot.js +301 -0
  85. package/dist/rules/adapter.d.ts +53 -0
  86. package/dist/rules/adapter.js +261 -0
  87. package/dist/rules/cards.d.ts +115 -0
  88. package/dist/rules/cards.js +547 -0
  89. package/dist/rules/collector.d.ts +161 -0
  90. package/dist/rules/collector.js +732 -0
  91. package/dist/rules/genres.d.ts +13 -0
  92. package/dist/rules/genres.js +38 -0
  93. package/dist/rules/harness.d.ts +16 -0
  94. package/dist/rules/harness.js +127 -0
  95. package/dist/rules/idle.d.ts +115 -0
  96. package/dist/rules/idle.js +370 -0
  97. package/dist/rules/index.d.ts +16 -0
  98. package/dist/rules/index.js +14 -0
  99. package/dist/rules/kit.d.ts +171 -0
  100. package/dist/rules/kit.js +88 -0
  101. package/dist/rules/tower.d.ts +214 -0
  102. package/dist/rules/tower.js +655 -0
  103. package/dist/runtime/arena.d.ts +86 -0
  104. package/dist/runtime/arena.js +269 -0
  105. package/dist/runtime/assets.d.ts +26 -0
  106. package/dist/runtime/assets.js +35 -0
  107. package/dist/runtime/camera.d.ts +113 -0
  108. package/dist/runtime/camera.js +247 -0
  109. package/dist/runtime/crowd.d.ts +18 -0
  110. package/dist/runtime/crowd.js +62 -0
  111. package/dist/runtime/feel/audio.d.ts +150 -0
  112. package/dist/runtime/feel/audio.js +327 -0
  113. package/dist/runtime/feel/clock.d.ts +52 -0
  114. package/dist/runtime/feel/clock.js +80 -0
  115. package/dist/runtime/feel/flash.d.ts +28 -0
  116. package/dist/runtime/feel/flash.js +60 -0
  117. package/dist/runtime/feel/index.d.ts +27 -0
  118. package/dist/runtime/feel/index.js +16 -0
  119. package/dist/runtime/feel/kit.d.ts +159 -0
  120. package/dist/runtime/feel/kit.js +226 -0
  121. package/dist/runtime/feel/knockback.d.ts +41 -0
  122. package/dist/runtime/feel/knockback.js +70 -0
  123. package/dist/runtime/feel/noise.d.ts +6 -0
  124. package/dist/runtime/feel/noise.js +30 -0
  125. package/dist/runtime/feel/numbers.d.ts +91 -0
  126. package/dist/runtime/feel/numbers.js +172 -0
  127. package/dist/runtime/feel/rumble.d.ts +40 -0
  128. package/dist/runtime/feel/rumble.js +58 -0
  129. package/dist/runtime/feel/sound.d.ts +96 -0
  130. package/dist/runtime/feel/sound.js +176 -0
  131. package/dist/runtime/feel/spring.d.ts +42 -0
  132. package/dist/runtime/feel/spring.js +95 -0
  133. package/dist/runtime/feel/trauma.d.ts +48 -0
  134. package/dist/runtime/feel/trauma.js +76 -0
  135. package/dist/runtime/feel/tween.d.ts +66 -0
  136. package/dist/runtime/feel/tween.js +180 -0
  137. package/dist/runtime/feel/types.d.ts +30 -0
  138. package/dist/runtime/feel/types.js +5 -0
  139. package/dist/runtime/index.d.ts +28 -0
  140. package/dist/runtime/index.js +26 -0
  141. package/dist/runtime/input.d.ts +38 -0
  142. package/dist/runtime/input.js +159 -0
  143. package/dist/runtime/interpolate.d.ts +6 -0
  144. package/dist/runtime/interpolate.js +13 -0
  145. package/dist/runtime/render/animation.d.ts +63 -0
  146. package/dist/runtime/render/animation.js +128 -0
  147. package/dist/runtime/render/atmosphere.d.ts +32 -0
  148. package/dist/runtime/render/atmosphere.js +59 -0
  149. package/dist/runtime/render/crowd-motion.d.ts +68 -0
  150. package/dist/runtime/render/crowd-motion.js +163 -0
  151. package/dist/runtime/render/decals.d.ts +64 -0
  152. package/dist/runtime/render/decals.js +222 -0
  153. package/dist/runtime/render/highlight.d.ts +29 -0
  154. package/dist/runtime/render/highlight.js +110 -0
  155. package/dist/runtime/render/index.d.ts +35 -0
  156. package/dist/runtime/render/index.js +22 -0
  157. package/dist/runtime/render/lighting.d.ts +50 -0
  158. package/dist/runtime/render/lighting.js +142 -0
  159. package/dist/runtime/render/particles.d.ts +117 -0
  160. package/dist/runtime/render/particles.js +289 -0
  161. package/dist/runtime/render/post.d.ts +92 -0
  162. package/dist/runtime/render/post.js +191 -0
  163. package/dist/runtime/render/presets.d.ts +57 -0
  164. package/dist/runtime/render/presets.js +116 -0
  165. package/dist/runtime/render/quality.d.ts +113 -0
  166. package/dist/runtime/render/quality.js +180 -0
  167. package/dist/runtime/render/rig.d.ts +29 -0
  168. package/dist/runtime/render/rig.js +107 -0
  169. package/dist/runtime/render/sprite.d.ts +34 -0
  170. package/dist/runtime/render/sprite.js +58 -0
  171. package/dist/runtime/render/stack.d.ts +74 -0
  172. package/dist/runtime/render/stack.js +132 -0
  173. package/dist/runtime/render/vat.d.ts +78 -0
  174. package/dist/runtime/render/vat.js +219 -0
  175. package/dist/runtime/renderer.d.ts +19 -0
  176. package/dist/runtime/renderer.js +18 -0
  177. package/dist/runtime/stats.d.ts +11 -0
  178. package/dist/runtime/stats.js +29 -0
  179. package/dist/runtime/styles/handdrawn/ground.d.ts +11 -0
  180. package/dist/runtime/styles/handdrawn/ground.js +282 -0
  181. package/dist/runtime/styles/handdrawn/index.d.ts +4 -0
  182. package/dist/runtime/styles/handdrawn/index.js +94 -0
  183. package/dist/runtime/styles/handdrawn/ink.d.ts +31 -0
  184. package/dist/runtime/styles/handdrawn/ink.js +68 -0
  185. package/dist/runtime/styles/handdrawn/looks.d.ts +18 -0
  186. package/dist/runtime/styles/handdrawn/looks.js +42 -0
  187. package/dist/runtime/styles/handdrawn/paint.d.ts +59 -0
  188. package/dist/runtime/styles/handdrawn/paint.js +129 -0
  189. package/dist/runtime/styles/handdrawn/palette.d.ts +49 -0
  190. package/dist/runtime/styles/handdrawn/palette.js +70 -0
  191. package/dist/runtime/styles/handdrawn/textures.d.ts +27 -0
  192. package/dist/runtime/styles/handdrawn/textures.js +150 -0
  193. package/dist/runtime/styles/handdrawn/vfx.d.ts +14 -0
  194. package/dist/runtime/styles/handdrawn/vfx.js +73 -0
  195. package/dist/runtime/styles/index.d.ts +9 -0
  196. package/dist/runtime/styles/index.js +32 -0
  197. package/dist/runtime/styles/kit.d.ts +193 -0
  198. package/dist/runtime/styles/kit.js +1 -0
  199. package/dist/runtime/styles/pixel/index.d.ts +46 -0
  200. package/dist/runtime/styles/pixel/index.js +167 -0
  201. package/dist/runtime/styles/ps1/index.d.ts +29 -0
  202. package/dist/runtime/styles/ps1/index.js +41 -0
  203. package/dist/runtime/styles/ps1/lights.d.ts +47 -0
  204. package/dist/runtime/styles/ps1/lights.js +146 -0
  205. package/dist/runtime/styles/ps1/materials.d.ts +62 -0
  206. package/dist/runtime/styles/ps1/materials.js +285 -0
  207. package/dist/runtime/styles/ps1/pipeline.d.ts +52 -0
  208. package/dist/runtime/styles/ps1/pipeline.js +111 -0
  209. package/dist/runtime/styles/ps1/surfaces.d.ts +44 -0
  210. package/dist/runtime/styles/ps1/surfaces.js +228 -0
  211. package/dist/runtime/styles/toon/index.d.ts +5 -0
  212. package/dist/runtime/styles/toon/index.js +178 -0
  213. package/dist/runtime/styles/toon/ink.d.ts +39 -0
  214. package/dist/runtime/styles/toon/ink.js +73 -0
  215. package/dist/runtime/styles/toon/look.d.ts +10 -0
  216. package/dist/runtime/styles/toon/look.js +112 -0
  217. package/dist/runtime/styles/toon/material.d.ts +74 -0
  218. package/dist/runtime/styles/toon/material.js +208 -0
  219. package/dist/runtime/styles/toon/normals.d.ts +26 -0
  220. package/dist/runtime/styles/toon/normals.js +127 -0
  221. package/dist/runtime/styles/toon/shadows.d.ts +24 -0
  222. package/dist/runtime/styles/toon/shadows.js +42 -0
  223. package/dist/runtime/styles/toon/vfx.d.ts +15 -0
  224. package/dist/runtime/styles/toon/vfx.js +47 -0
  225. package/dist/runtime/styles/view.d.ts +35 -0
  226. package/dist/runtime/styles/view.js +34 -0
  227. package/dist/runtime/styles/voxel/core.d.ts +114 -0
  228. package/dist/runtime/styles/voxel/core.js +577 -0
  229. package/dist/runtime/styles/voxel/geometry.d.ts +27 -0
  230. package/dist/runtime/styles/voxel/geometry.js +40 -0
  231. package/dist/runtime/styles/voxel/index.d.ts +21 -0
  232. package/dist/runtime/styles/voxel/index.js +23 -0
  233. package/dist/runtime/styles/voxel/kit.d.ts +6 -0
  234. package/dist/runtime/styles/voxel/kit.js +11 -0
  235. package/dist/runtime/styles/voxel/look.d.ts +10 -0
  236. package/dist/runtime/styles/voxel/look.js +16 -0
  237. package/dist/runtime/styles/voxel/props.d.ts +9 -0
  238. package/dist/runtime/styles/voxel/props.js +52 -0
  239. package/dist/runtime/styles/voxel/shapes.d.ts +41 -0
  240. package/dist/runtime/styles/voxel/shapes.js +83 -0
  241. package/dist/runtime/styles/voxel/world.d.ts +49 -0
  242. package/dist/runtime/styles/voxel/world.js +422 -0
  243. package/llms-full.txt +460 -0
  244. package/llms.txt +183 -0
  245. package/package.json +104 -3
@@ -0,0 +1,285 @@
1
+ /**
2
+ * A playtest bot that plays a survivor-style game reasonably: circles the
3
+ * arena, steers away from nearby enemies, dashes out of crowds and takes an
4
+ * upgrade whenever one is offered. Used by the headless harness and by the
5
+ * networked load bots (which see only their area of interest).
6
+ */
7
+ import { Button } from "@netforge/engine-sim";
8
+ /** One tick of input. `enemies` are nearby enemy positions [x, z]; `id` varies the bot's path. */
9
+ export function botInput(me, enemies, tick, id) {
10
+ const ring = Math.min(me.half * 0.5, 22) + (id % 4) * 3;
11
+ const r = Math.hypot(me.x, me.z) || 1;
12
+ const dir = id % 2 === 0 ? 1 : -1;
13
+ // Tangent around the centre plus a pull back onto the ring.
14
+ let dx = (-me.z / r) * dir + (me.x / r) * ((ring - r) / ring) * 2;
15
+ let dz = (me.x / r) * dir + (me.z / r) * ((ring - r) / ring) * 2;
16
+ let crowd = 0;
17
+ for (const [ex, ez] of enemies) {
18
+ const ox = me.x - ex;
19
+ const oz = me.z - ez;
20
+ const d2 = ox * ox + oz * oz;
21
+ if (d2 > 36 || d2 < 1e-6)
22
+ continue;
23
+ const w = 3 / d2;
24
+ dx += ox * w;
25
+ dz += oz * w;
26
+ if (d2 < 4)
27
+ crowd++;
28
+ }
29
+ const len = Math.hypot(dx, dz) || 1;
30
+ let buttons = 0;
31
+ if ((me.choices & 0xff) !== 0 && tick % 2 === 0)
32
+ buttons |= Button.Choice1 << ((id + (tick >> 1)) % 3);
33
+ if (crowd >= 6 && tick % 8 === 0)
34
+ buttons |= Button.Sprint;
35
+ // yaw 0: moveX is +x, moveZ is -z.
36
+ return { seq: 0, tick, moveX: dx / len, moveZ: -dz / len, yaw: 0, buttons };
37
+ }
38
+ /**
39
+ * A playtest bot that plays like a person, for fun metrics
40
+ * (GAME-DESIGN.md "measure fun with bots"): it reacts about 200 ms late,
41
+ * kites in wide arcs instead of circling the centre, walks over gems when
42
+ * it is safe, dashes when something is about to touch it or a telegraph is
43
+ * about to land under it, and takes a second or so to read each card.
44
+ * Template-agnostic: it needs positions only. Deterministic for a given
45
+ * input sequence (its own xorshift, never the sim's PRNG).
46
+ */
47
+ export class HumanBot {
48
+ delay;
49
+ picks;
50
+ nWeapons;
51
+ evolved;
52
+ pairs;
53
+ s;
54
+ seen = [];
55
+ arc;
56
+ heading = 0;
57
+ downSince = -1;
58
+ cardSince = -1;
59
+ cardWait = 30;
60
+ lastDash = -999;
61
+ lastTurn = 0;
62
+ constructor(opts = {}) {
63
+ this.delay = Math.max(0, Math.floor(opts.reactionTicks ?? 6));
64
+ this.picks = opts.picks ?? "random";
65
+ this.nWeapons = opts.weaponCount ?? 0;
66
+ this.evolved = new Set(opts.evolved ?? []);
67
+ this.pairs = opts.pairs ?? [];
68
+ this.s = ((opts.seed ?? 1) * 2654435761) >>> 0 || 1;
69
+ this.arc = this.rnd() < 0.5 ? 1 : -1;
70
+ this.heading = this.rnd() * Math.PI * 2;
71
+ }
72
+ rnd() {
73
+ let s = this.s;
74
+ s ^= s << 13;
75
+ s ^= s >>> 17;
76
+ s ^= s << 5;
77
+ this.s = s >>> 0;
78
+ return this.s / 4294967296;
79
+ }
80
+ /** One tick of input from what the player sees now. */
81
+ input(now, tick) {
82
+ this.seen.push(now);
83
+ if (this.seen.length > this.delay + 1)
84
+ this.seen.shift();
85
+ // Perception is late; the bot's own position is not (you know where you are).
86
+ const late = this.seen[0] ?? now;
87
+ let buttons = 0;
88
+ const pending = now.choices >>> 24;
89
+ if (pending > 0 && now.up) {
90
+ if (this.cardSince < 0) {
91
+ this.cardSince = tick;
92
+ this.cardWait = 24 + Math.floor(this.rnd() * 12);
93
+ }
94
+ if (tick - this.cardSince >= this.cardWait) {
95
+ const k = this.choose(now.choices, now.weapons);
96
+ if (k >= 0)
97
+ buttons |= Button.Choice1 << k;
98
+ this.cardSince = -1;
99
+ }
100
+ }
101
+ else
102
+ this.cardSince = -1;
103
+ if (!now.up) {
104
+ // Down: read the prompt for a beat, then press dash once (a free self-revive, if one is left).
105
+ if (this.downSince < 0)
106
+ this.downSince = tick;
107
+ const press = tick - this.downSince === 45 ? Button.Sprint : 0;
108
+ return { seq: 0, tick, moveX: 0, moveZ: 0, yaw: 0, buttons: press };
109
+ }
110
+ this.downSince = -1;
111
+ // Threat: nearby enemies push, nearer ones much harder.
112
+ let tx = 0;
113
+ let tz = 0;
114
+ let nearest = Infinity;
115
+ let close = 0;
116
+ for (const e of late.enemies) {
117
+ const ox = now.x - e[0];
118
+ const oz = now.z - e[1];
119
+ const d2 = ox * ox + oz * oz;
120
+ if (d2 > 100 || d2 < 1e-6)
121
+ continue;
122
+ const d = Math.sqrt(d2);
123
+ nearest = Math.min(nearest, d);
124
+ if (d < 4)
125
+ close++;
126
+ const w = 1 / (d2 * d);
127
+ tx += ox * w;
128
+ tz += oz * w;
129
+ }
130
+ const threat = Math.hypot(tx, tz);
131
+ let dx = Math.cos(this.heading);
132
+ let dz = Math.sin(this.heading);
133
+ let drift = false;
134
+ const boss = late.bosses?.[0];
135
+ let downedMate;
136
+ let dd = 25 * 25;
137
+ for (const d of now.downed ?? []) {
138
+ const d2 = (d[0] - now.x) ** 2 + (d[1] - now.z) ** 2;
139
+ if (d2 < dd) {
140
+ dd = d2;
141
+ downedMate = d;
142
+ }
143
+ }
144
+ if (downedMate && close < 6) {
145
+ // A teammate is down nearby: go and stand by them (the revive needs 3 s within 2.5 m).
146
+ dx = downedMate[0] - now.x;
147
+ dz = downedMate[1] - now.z;
148
+ if (dx * dx + dz * dz < 1.5) {
149
+ dx = -dz * 0.2;
150
+ dz = dx * 0.2;
151
+ }
152
+ }
153
+ else if (boss && close < 5) {
154
+ // A boss on screen: circle it at reach, so the weapons keep landing.
155
+ const bx = now.x - boss[0];
156
+ const bz = now.z - boss[1];
157
+ const bd = Math.hypot(bx, bz) || 1;
158
+ const reach = (now.reach ?? 5) * 0.85 + 1.5;
159
+ const radial = bd < reach * 0.8 ? 1 : bd > reach * 1.1 ? -1 : 0;
160
+ dx = (-bz / bd) * this.arc + (bx / bd) * radial;
161
+ dz = (bx / bd) * this.arc + (bz / bd) * radial;
162
+ }
163
+ else if (threat > 0.004) {
164
+ // Kite: circle the threat at about the weapons' reach. Too close: step out; too far: lean in.
165
+ const ax = tx / threat;
166
+ const az = tz / threat;
167
+ const reach = now.reach ?? 5;
168
+ const radial = close >= 5 ? 1.6 : nearest < reach * 0.55 ? 1 : nearest > reach * 0.95 ? -0.5 : 0.25;
169
+ dx = -az * this.arc + ax * radial;
170
+ dz = ax * this.arc + az * radial;
171
+ }
172
+ else {
173
+ // Calm: go for the nearest gem, else wander on a slowly turning heading.
174
+ let best = 81;
175
+ for (const g of late.gems) {
176
+ const gx = g[0] - now.x;
177
+ const gz = g[1] - now.z;
178
+ const d2 = gx * gx + gz * gz;
179
+ if (d2 < best) {
180
+ best = d2;
181
+ dx = gx;
182
+ dz = gz;
183
+ }
184
+ }
185
+ if (best === 81) {
186
+ // No gem near: close in on the nearest enemy on screen to about 6 m (players farm XP).
187
+ let near = Infinity;
188
+ for (const e of late.enemies) {
189
+ const ex = e[0] - now.x;
190
+ const ez = e[1] - now.z;
191
+ const d2 = ex * ex + ez * ez;
192
+ if (d2 < near) {
193
+ near = d2;
194
+ dx = ex;
195
+ dz = ez;
196
+ }
197
+ }
198
+ if (near !== Infinity && near > 36)
199
+ best = 0;
200
+ }
201
+ if (best === 81) {
202
+ // Nothing on screen: drift at half speed (players wait for the action, they do not sprint away from it).
203
+ if (tick - this.lastTurn > 60) {
204
+ this.lastTurn = tick;
205
+ this.heading += (this.rnd() - 0.5) * 1.2;
206
+ }
207
+ dx = Math.cos(this.heading) * 0.5;
208
+ dz = Math.sin(this.heading) * 0.5;
209
+ drift = true;
210
+ }
211
+ }
212
+ // Walls: turn back toward the middle before running into one.
213
+ const lim = now.half - 10;
214
+ if (Math.abs(now.x) > lim)
215
+ dx += (-Math.sign(now.x) * (Math.abs(now.x) - lim)) / 3;
216
+ if (Math.abs(now.z) > lim)
217
+ dz += (-Math.sign(now.z) * (Math.abs(now.z) - lim)) / 3;
218
+ // Pillars: slide around them.
219
+ for (const p of now.pillars) {
220
+ const ox = now.x - p.x;
221
+ const oz = now.z - p.z;
222
+ const d = Math.hypot(ox, oz);
223
+ if (d < p.radius + 2 && d > 1e-6) {
224
+ dx += (ox / d) * 1.5;
225
+ dz += (oz / d) * 1.5;
226
+ }
227
+ }
228
+ // Telegraphs: step out of anything about to land under you, and dash if it is close.
229
+ let inDanger = false;
230
+ for (const g of late.dangers ?? []) {
231
+ const ox = now.x - g.x;
232
+ const oz = now.z - g.z;
233
+ if (g.dx !== undefined && g.dz !== undefined) {
234
+ const along = ox * g.dx + oz * g.dz;
235
+ const side = ox * -g.dz + oz * g.dx;
236
+ if (along > -1 && along < (g.len ?? 0) + 1 && Math.abs(side) < g.r + 0.8) {
237
+ inDanger = true;
238
+ const s = side >= 0 ? 1 : -1;
239
+ dx += -g.dz * s * 4;
240
+ dz += g.dx * s * 4;
241
+ }
242
+ }
243
+ else if (ox * ox + oz * oz < (g.r + 0.8) * (g.r + 0.8)) {
244
+ inDanger = true;
245
+ const d = Math.hypot(ox, oz) || 1;
246
+ dx += (ox / d) * 4;
247
+ dz += (oz / d) * 4;
248
+ }
249
+ }
250
+ if ((inDanger || nearest < 1.1 || close >= 5) && tick - this.lastDash > 40) {
251
+ buttons |= Button.Sprint;
252
+ this.lastDash = tick;
253
+ }
254
+ const len = Math.hypot(dx, dz) || 1;
255
+ this.heading = Math.atan2(dz, dx);
256
+ const speed = drift ? 0.5 : 1;
257
+ return { seq: 0, tick, moveX: (dx / len) * speed, moveZ: (-dz / len) * speed, yaw: 0, buttons };
258
+ }
259
+ choose(choices, weapons) {
260
+ const offers = [(choices & 0xff) - 1, ((choices >>> 8) & 0xff) - 1, ((choices >>> 16) & 0xff) - 1];
261
+ const valid = [];
262
+ offers.forEach((u, k) => {
263
+ if (u >= 0)
264
+ valid.push([u, k]);
265
+ });
266
+ if (valid.length === 0)
267
+ return -1;
268
+ if (this.picks === "random")
269
+ return valid[Math.floor(this.rnd() * valid.length)][1];
270
+ const owned = new Set();
271
+ for (let s = 0; s < 4; s++) {
272
+ const wi = ((weapons >>> (s * 8)) & 0x0f) - 1;
273
+ if (wi >= 0)
274
+ owned.add(wi);
275
+ }
276
+ // A build with a goal: evolutions first, then the passive an owned weapon needs, then owned weapons, new weapons, other passives.
277
+ const needed = new Set(this.pairs.filter(([wi]) => owned.has(wi)).map(([, u]) => u));
278
+ const score = (u) => (this.evolved.has(u) ? 5 : needed.has(u) ? 3.5 : u < this.nWeapons ? (owned.has(u) ? 3 : 2) : 1);
279
+ let best = valid[0];
280
+ for (const v of valid)
281
+ if (score(v[0]) > score(best[0]))
282
+ best = v;
283
+ return best[1];
284
+ }
285
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Compile a game manifest (what the model declared through the API) into
3
+ * engine-sim content: enemy types, weapons, passives and progression. This
4
+ * is how prefabs and weapons stop being validated-only and start running in
5
+ * the sim.
6
+ */
7
+ import type { SimConfig, WeaponLevelStats } from "@netforge/engine-sim";
8
+ import type { GameManifest } from "../api/registry.js";
9
+ import type { PrefabDef, WeaponDef as ApiWeaponDef } from "../api/schemas.js";
10
+ import type { CompiledPlatformer } from "./platformer.js";
11
+ import type { CompiledRacer } from "./racer.js";
12
+ import type { CameraView } from "./view.js";
13
+ /** Templates that run on the survivor machinery (co-op, auto-firing weapons, downs and revives, keepers). */
14
+ export declare const COOP_TEMPLATES: ReadonlySet<string>;
15
+ /**
16
+ * Survivor-machinery defaults every game gets unless it says otherwise
17
+ * (GAME-DESIGN.md): a generous magnet, half a second of hit i-frames, a
18
+ * solo pause on level-up cards and a shove plus a second of i-frames when
19
+ * co-op cards open.
20
+ */
21
+ export declare const SURVIVOR_DEFAULTS: {
22
+ readonly magnet: 3.75;
23
+ readonly hitIframes: 0.5;
24
+ readonly choicePulseRadius: 2.5;
25
+ readonly choicePulseIframes: 1;
26
+ /** Co-op XP: half of each gem to teammates within 20 m. */
27
+ readonly share: {
28
+ readonly mode: "nearby";
29
+ readonly fraction: 0.5;
30
+ readonly radius: 20;
31
+ };
32
+ /** The reviver's buff: 5 s of x1.25 damage and x1.15 speed. */
33
+ readonly reviveBuff: {
34
+ readonly seconds: 5;
35
+ readonly damage: 1.25;
36
+ readonly speed: 1.15;
37
+ };
38
+ /** Going down is short: 10 s downed (crawling), one free get-up a run, then back in 5 s beside a teammate. */
39
+ readonly downed: {
40
+ readonly seconds: 10;
41
+ readonly crawl: 0.35;
42
+ readonly selfRevives: 1;
43
+ readonly respawn: 5;
44
+ };
45
+ /** Getting up shoves enemies within this radius, m. */
46
+ readonly risePulseRadius: 4;
47
+ };
48
+ export interface CompiledGame {
49
+ readonly manifest: GameManifest;
50
+ /** Pass to engine-sim's createWorld (add a seed). Identical on the server and the client. */
51
+ readonly sim: SimConfig;
52
+ /** Enemy prefab ids by engine type index. */
53
+ readonly enemyPrefabs: readonly string[];
54
+ /** Engine type index by enemy prefab id. */
55
+ readonly enemyType: ReadonlyMap<string, number>;
56
+ /** Pickup prefab ids by gem tier (0 small, 1 medium, 2 large); several tiers may share a prefab. */
57
+ readonly pickupPrefabs: readonly string[];
58
+ /** Special pickup prefab ids (heals, vacuums, bombs, chests): entity type PICKUP_SPECIAL + index. */
59
+ readonly specialPickups: readonly string[];
60
+ /** Weapon ids by engine weapon index, then upgrade ids: the order of level-up offer indices. */
61
+ readonly weaponIds: readonly string[];
62
+ readonly upgradeIds: readonly string[];
63
+ readonly tickRate: number;
64
+ /** "arena": the shooter machinery (loadouts, respawn, rounds, seat-filling bots); "platformer": the race-to-the-flag machinery (platformer.ts); else the survivor machinery. */
65
+ readonly mode: "survivor" | "arena" | "platformer" | "racer";
66
+ /** Platformer: the courses, feel and upgrade stats (null for other templates). */
67
+ readonly platformer?: CompiledPlatformer;
68
+ /** Racer: the circuits, handling and upgrade stats (null for other templates). */
69
+ readonly racer?: CompiledRacer;
70
+ /** Arena: weapon indices in loadout slots 1..4. */
71
+ readonly loadout: readonly number[];
72
+ /** Arena: seats bots fill up to (0: none). */
73
+ readonly bots: number;
74
+ /** Arena: round length and frag limit; null for survivor runs. */
75
+ readonly round: {
76
+ readonly seconds: number;
77
+ readonly frags: number | null;
78
+ } | null;
79
+ /** What a player sees: screen-edge distances by heading (the director spawns past them; the playtest counts inside them as on screen). */
80
+ readonly view: readonly number[];
81
+ }
82
+ /** Arena defaults: a 3 minute round, a 3 s respawn, 2 s spawn protection, heal cells last 20 s. */
83
+ export declare const ARENA_DEFAULTS: {
84
+ readonly roundSeconds: 180;
85
+ readonly respawnSeconds: 3;
86
+ readonly spawnProtectSeconds: 2;
87
+ readonly pickupSeconds: 20;
88
+ readonly pillarHeight: 2.4;
89
+ };
90
+ /** Every level's numbers: level 1 is the weapon's own, each declared level applies its changes to the one before. */
91
+ export declare function levelTable(w: ApiWeaponDef, tickRate: number): WeaponLevelStats[];
92
+ /** "enemy" or "pickup": explicit, else a prefab something drops is a pickup. */
93
+ export declare function prefabKind(m: GameManifest, id: string, p: PrefabDef): "enemy" | "pickup";
94
+ export declare function compileGame(m: GameManifest, opts?: {
95
+ maxEntities?: number;
96
+ views?: readonly CameraView[];
97
+ }): CompiledGame;