@netforge/engine 0.0.0-stage → 0.1.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 (244) hide show
  1. package/GAME-DESIGN.md +195 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +30 -0
  4. package/README.md +147 -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 +208 -0
  76. package/dist/net/protocol.js +8 -0
  77. package/dist/net/room-client.d.ts +57 -0
  78. package/dist/net/room-client.js +144 -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.txt +183 -0
  244. package/package.json +82 -3
@@ -0,0 +1,48 @@
1
+ export interface TraumaOptions {
2
+ /** Trauma lost per second (1 = a full hit fades in one second). Default 1.4. */
3
+ readonly decay?: number;
4
+ /** shake = trauma ^ exponent. 2 or 3 reads best. Default 2. */
5
+ readonly exponent?: number;
6
+ /** Offset at full shake, in world units (m). Default 0.6. */
7
+ readonly maxOffset?: number;
8
+ /** Roll at full shake, radians. Default 0.04. */
9
+ readonly maxRoll?: number;
10
+ /** Noise frequency, Hz. Default 16. */
11
+ readonly frequency?: number;
12
+ /** Noise seed (same seed, same shake). Default 1. */
13
+ readonly seed?: number;
14
+ }
15
+ /**
16
+ * Trauma screen shake: hits add trauma 0..1, it decays linearly, and the
17
+ * shake is trauma squared, so small hits barely move the camera and big
18
+ * ones kick hard. Offsets come from smooth seeded noise, so the shake is
19
+ * fluid rather than jittery and never uses Math.random.
20
+ */
21
+ export declare class Trauma {
22
+ /** Current trauma 0..1. */
23
+ value: number;
24
+ /** This frame's offsets (m) and roll (rad); zero when calm. */
25
+ x: number;
26
+ y: number;
27
+ roll: number;
28
+ /** Trauma adds so far (playtest counters). */
29
+ adds: number;
30
+ private time;
31
+ private hold;
32
+ private readonly o;
33
+ constructor(opts?: TraumaOptions);
34
+ get options(): Readonly<Required<TraumaOptions>>;
35
+ /** Add trauma (the total is clamped to 0..1). */
36
+ add(amount: number): void;
37
+ /** Same as `add`: lets a bare Trauma stand in for a CameraRig as a TraumaTarget. */
38
+ addTrauma(amount: number): void;
39
+ /** Keep the current trauma from decaying for `seconds` (the classic shake(amplitude, seconds) maps here). */
40
+ sustain(seconds: number): void;
41
+ /** The shake factor, trauma ^ exponent. */
42
+ get shake(): number;
43
+ update(dt: number): void;
44
+ /** Back to calm. */
45
+ reset(): void;
46
+ /** Trauma that gives a shake of about `amplitude` world units. */
47
+ traumaFor(amplitude: number): number;
48
+ }
@@ -0,0 +1,76 @@
1
+ import { valueNoise1 } from "./noise.js";
2
+ /**
3
+ * Trauma screen shake: hits add trauma 0..1, it decays linearly, and the
4
+ * shake is trauma squared, so small hits barely move the camera and big
5
+ * ones kick hard. Offsets come from smooth seeded noise, so the shake is
6
+ * fluid rather than jittery and never uses Math.random.
7
+ */
8
+ export class Trauma {
9
+ /** Current trauma 0..1. */
10
+ value = 0;
11
+ /** This frame's offsets (m) and roll (rad); zero when calm. */
12
+ x = 0;
13
+ y = 0;
14
+ roll = 0;
15
+ /** Trauma adds so far (playtest counters). */
16
+ adds = 0;
17
+ time = 0;
18
+ hold = 0;
19
+ o;
20
+ constructor(opts = {}) {
21
+ this.o = { decay: 1.4, exponent: 2, maxOffset: 0.6, maxRoll: 0.04, frequency: 16, seed: 1, ...opts };
22
+ }
23
+ get options() {
24
+ return this.o;
25
+ }
26
+ /** Add trauma (the total is clamped to 0..1). */
27
+ add(amount) {
28
+ if (!(amount > 0))
29
+ return;
30
+ this.adds++;
31
+ this.value = Math.min(1, this.value + amount);
32
+ }
33
+ /** Same as `add`: lets a bare Trauma stand in for a CameraRig as a TraumaTarget. */
34
+ addTrauma(amount) {
35
+ this.add(amount);
36
+ }
37
+ /** Keep the current trauma from decaying for `seconds` (the classic shake(amplitude, seconds) maps here). */
38
+ sustain(seconds) {
39
+ this.hold = Math.max(this.hold, seconds);
40
+ }
41
+ /** The shake factor, trauma ^ exponent. */
42
+ get shake() {
43
+ return Math.pow(this.value, this.o.exponent);
44
+ }
45
+ update(dt) {
46
+ if (dt > 0) {
47
+ this.time += dt;
48
+ if (this.hold > 0)
49
+ this.hold = Math.max(0, this.hold - dt);
50
+ else
51
+ this.value = Math.max(0, this.value - this.o.decay * dt);
52
+ }
53
+ const s = this.shake;
54
+ if (s === 0) {
55
+ this.x = this.y = this.roll = 0;
56
+ return;
57
+ }
58
+ const t = this.time * this.o.frequency;
59
+ const seed = this.o.seed;
60
+ this.x = this.o.maxOffset * s * valueNoise1(t, seed);
61
+ this.y = this.o.maxOffset * s * valueNoise1(t, seed + 101);
62
+ this.roll = this.o.maxRoll * s * valueNoise1(t, seed + 202);
63
+ }
64
+ /** Back to calm. */
65
+ reset() {
66
+ this.value = 0;
67
+ this.hold = 0;
68
+ this.x = this.y = this.roll = 0;
69
+ }
70
+ /** Trauma that gives a shake of about `amplitude` world units. */
71
+ traumaFor(amplitude) {
72
+ if (!(amplitude > 0))
73
+ return 0;
74
+ return Math.min(1, Math.pow(amplitude / this.o.maxOffset, 1 / this.o.exponent));
75
+ }
76
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Juicy UI tweens: a tiny tween manager with an easing table, and DOM
3
+ * helpers (pop, shake, count up) built on it. The helpers use the CSS
4
+ * `scale` and `translate` properties, so they never fight a transform the
5
+ * page already sets.
6
+ */
7
+ export type EaseFn = (t: number) => number;
8
+ export declare const Ease: {
9
+ readonly linear: (t: number) => number;
10
+ readonly quadIn: (t: number) => number;
11
+ readonly quadOut: (t: number) => number;
12
+ readonly quadInOut: (t: number) => number;
13
+ readonly cubicOut: (t: number) => number;
14
+ readonly cubicInOut: (t: number) => number;
15
+ readonly sineInOut: (t: number) => number;
16
+ readonly expoOut: (t: number) => number;
17
+ readonly backOut: (t: number) => number;
18
+ readonly elasticOut: (t: number) => number;
19
+ readonly bounceOut: (t: number) => number;
20
+ };
21
+ export type EaseName = keyof typeof Ease;
22
+ export interface TweenOptions {
23
+ /** Seconds. */
24
+ readonly duration: number;
25
+ readonly ease?: EaseName | EaseFn;
26
+ /** Seconds before it starts (start values are read when it starts). */
27
+ readonly delay?: number;
28
+ /** Play back to the start after each forward play. */
29
+ readonly yoyo?: boolean;
30
+ /** Extra plays after the first (-1 forever). Default 0. */
31
+ readonly repeat?: number;
32
+ readonly onUpdate?: () => void;
33
+ readonly onDone?: () => void;
34
+ }
35
+ type NumericKeys<T> = {
36
+ [K in keyof T]: T[K] extends number ? K : never;
37
+ }[keyof T];
38
+ export type TweenProps<T> = Partial<Record<NumericKeys<T>, number>>;
39
+ export declare class Tweens {
40
+ private list;
41
+ private nextId;
42
+ /** Tween numeric properties of `target` to `props`. Returns a handle for `cancel`. */
43
+ to<T extends object>(target: T, props: TweenProps<T>, opts: TweenOptions): number;
44
+ /** Stop a tween by handle, or every tween on a target object. Values stay where they are. */
45
+ cancel(handleOrTarget: number | object): void;
46
+ /** Tweens running or waiting. */
47
+ get active(): number;
48
+ tick(dt: number): void;
49
+ }
50
+ /** The shared UI tween manager. It drives itself on requestAnimationFrame in a browser; call `uiTweens.tick(dt)` elsewhere. */
51
+ export declare const uiTweens: Tweens;
52
+ /** A minimal element: anything with a style and text (real elements, or fakes in tests). */
53
+ export interface FeelElement {
54
+ readonly style: {
55
+ scale: string;
56
+ translate: string;
57
+ };
58
+ textContent: string | null;
59
+ }
60
+ /** Punch an element's scale up to `scale` and spring it back (level-up, coin gain). */
61
+ export declare function pop(el: FeelElement, scale?: number, ms?: number, tweens?: Tweens): void;
62
+ /** Shake an element sideways by up to `px`, decaying over `ms` (denied action, damage taken). */
63
+ export declare function shakeEl(el: FeelElement, px?: number, ms?: number, tweens?: Tweens): void;
64
+ /** Count a number up in an element's text (scores, balances). */
65
+ export declare function countUp(el: FeelElement, from: number, to: number, ms?: number, format?: (n: number) => string, tweens?: Tweens): void;
66
+ export {};
@@ -0,0 +1,180 @@
1
+ const c1 = 1.70158;
2
+ const c3 = c1 + 1;
3
+ const bounce = (t) => {
4
+ const n = 7.5625;
5
+ const d = 2.75;
6
+ if (t < 1 / d)
7
+ return n * t * t;
8
+ if (t < 2 / d)
9
+ return n * (t -= 1.5 / d) * t + 0.75;
10
+ if (t < 2.5 / d)
11
+ return n * (t -= 2.25 / d) * t + 0.9375;
12
+ return n * (t -= 2.625 / d) * t + 0.984375;
13
+ };
14
+ export const Ease = {
15
+ linear: (t) => t,
16
+ quadIn: (t) => t * t,
17
+ quadOut: (t) => 1 - (1 - t) * (1 - t),
18
+ quadInOut: (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2),
19
+ cubicOut: (t) => 1 - Math.pow(1 - t, 3),
20
+ cubicInOut: (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2),
21
+ sineInOut: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
22
+ expoOut: (t) => (t >= 1 ? 1 : 1 - Math.pow(2, -10 * t)),
23
+ backOut: (t) => 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2),
24
+ elasticOut: (t) => t <= 0 ? 0 : t >= 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * ((2 * Math.PI) / 3)) + 1,
25
+ bounceOut: bounce,
26
+ };
27
+ export class Tweens {
28
+ list = [];
29
+ nextId = 1;
30
+ /** Tween numeric properties of `target` to `props`. Returns a handle for `cancel`. */
31
+ to(target, props, opts) {
32
+ const keys = Object.keys(props);
33
+ const rec = target;
34
+ const to = keys.map((k) => Number(props[k]));
35
+ const ease = typeof opts.ease === "function" ? opts.ease : Ease[opts.ease ?? "cubicOut"];
36
+ const id = this.nextId++;
37
+ this.list.push({
38
+ id,
39
+ target: rec,
40
+ keys,
41
+ from: keys.map(() => 0),
42
+ to,
43
+ started: false,
44
+ delay: Math.max(0, opts.delay ?? 0),
45
+ elapsed: 0,
46
+ duration: Math.max(1e-4, opts.duration),
47
+ ease,
48
+ yoyo: opts.yoyo === true,
49
+ repeatsLeft: opts.repeat ?? 0,
50
+ forward: true,
51
+ onUpdate: opts.onUpdate,
52
+ onDone: opts.onDone,
53
+ });
54
+ return id;
55
+ }
56
+ /** Stop a tween by handle, or every tween on a target object. Values stay where they are. */
57
+ cancel(handleOrTarget) {
58
+ this.list = this.list.filter((r) => (typeof handleOrTarget === "number" ? r.id !== handleOrTarget : r.target !== handleOrTarget));
59
+ }
60
+ /** Tweens running or waiting. */
61
+ get active() {
62
+ return this.list.length;
63
+ }
64
+ tick(dt) {
65
+ if (dt <= 0 || this.list.length === 0)
66
+ return;
67
+ const done = [];
68
+ for (const r of this.list) {
69
+ let left = dt;
70
+ if (r.delay > 0) {
71
+ const d = Math.min(r.delay, left);
72
+ r.delay -= d;
73
+ left -= d;
74
+ if (r.delay > 0)
75
+ continue;
76
+ }
77
+ if (!r.started) {
78
+ r.started = true;
79
+ r.from = r.keys.map((k) => Number(r.target[k] ?? 0));
80
+ }
81
+ r.elapsed += left;
82
+ while (r.elapsed >= r.duration) {
83
+ // A play finished: decide the next one.
84
+ if (r.repeatsLeft === 0 && (!r.yoyo || !r.forward))
85
+ break;
86
+ if (r.yoyo && r.forward) {
87
+ r.forward = false;
88
+ }
89
+ else {
90
+ if (r.repeatsLeft > 0)
91
+ r.repeatsLeft--;
92
+ r.forward = true;
93
+ }
94
+ r.elapsed -= r.duration;
95
+ }
96
+ const finished = r.elapsed >= r.duration;
97
+ const p = Math.min(1, r.elapsed / r.duration);
98
+ const e = r.ease(r.forward ? p : 1 - p);
99
+ for (let i = 0; i < r.keys.length; i++) {
100
+ const a = r.from[i] ?? 0;
101
+ const b = r.to[i] ?? 0;
102
+ r.target[r.keys[i]] = finished ? (r.forward ? b : a) : a + (b - a) * e;
103
+ }
104
+ r.onUpdate?.();
105
+ if (finished)
106
+ done.push(r);
107
+ }
108
+ if (done.length > 0) {
109
+ this.list = this.list.filter((r) => !done.includes(r));
110
+ for (const r of done)
111
+ r.onDone?.();
112
+ }
113
+ }
114
+ }
115
+ /** The shared UI tween manager. It drives itself on requestAnimationFrame in a browser; call `uiTweens.tick(dt)` elsewhere. */
116
+ export const uiTweens = new Tweens();
117
+ let rafOn = false;
118
+ let rafLast = 0;
119
+ function ensureLoop() {
120
+ if (rafOn || typeof requestAnimationFrame === "undefined")
121
+ return;
122
+ rafOn = true;
123
+ rafLast = 0;
124
+ const loop = (t) => {
125
+ const dt = rafLast === 0 ? 1 / 60 : Math.min(0.1, (t - rafLast) / 1000);
126
+ rafLast = t;
127
+ uiTweens.tick(dt);
128
+ if (uiTweens.active > 0)
129
+ requestAnimationFrame(loop);
130
+ else
131
+ rafOn = false;
132
+ };
133
+ requestAnimationFrame(loop);
134
+ }
135
+ const states = new WeakMap();
136
+ function stateOf(el) {
137
+ let st = states.get(el);
138
+ if (!st) {
139
+ st = { s: 1, dx: 0 };
140
+ states.set(el, st);
141
+ }
142
+ return st;
143
+ }
144
+ /** Punch an element's scale up to `scale` and spring it back (level-up, coin gain). */
145
+ export function pop(el, scale = 1.25, ms = 260, tweens = uiTweens) {
146
+ const st = stateOf(el);
147
+ tweens.cancel(st);
148
+ st.s = scale;
149
+ el.style.scale = String(scale);
150
+ tweens.to(st, { s: 1 }, { duration: ms / 1000, ease: "backOut", onUpdate: () => (el.style.scale = st.s.toFixed(4)) });
151
+ if (tweens === uiTweens)
152
+ ensureLoop();
153
+ }
154
+ /** Shake an element sideways by up to `px`, decaying over `ms` (denied action, damage taken). */
155
+ export function shakeEl(el, px = 6, ms = 280, tweens = uiTweens) {
156
+ const st = stateOf(el);
157
+ const prog = { t: 0 };
158
+ tweens.to(prog, { t: 1 }, {
159
+ duration: ms / 1000,
160
+ ease: "linear",
161
+ onUpdate: () => {
162
+ st.dx = Math.sin(prog.t * Math.PI * 9) * px * (1 - prog.t);
163
+ el.style.translate = `${st.dx.toFixed(2)}px 0`;
164
+ },
165
+ onDone: () => {
166
+ st.dx = 0;
167
+ el.style.translate = "";
168
+ },
169
+ });
170
+ if (tweens === uiTweens)
171
+ ensureLoop();
172
+ }
173
+ /** Count a number up in an element's text (scores, balances). */
174
+ export function countUp(el, from, to, ms = 600, format = (n) => String(Math.round(n)), tweens = uiTweens) {
175
+ const v = { n: from };
176
+ el.textContent = format(from);
177
+ tweens.to(v, { n: to }, { duration: ms / 1000, ease: "expoOut", onUpdate: () => (el.textContent = format(v.n)) });
178
+ if (tweens === uiTweens)
179
+ ensureLoop();
180
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared shapes of the game-feel kit. Kept free of
3
+ * three.js and the DOM so the kit imports anywhere, including node tests.
4
+ */
5
+ /** Something that can play a pooled particle effect (the GPU particle system implements it). */
6
+ export interface ParticleSink {
7
+ burst(preset: string, x: number, y: number, z: number, opts?: ParticleBurstOptions): void;
8
+ }
9
+ export interface ParticleBurstOptions {
10
+ /** Particle count override. */
11
+ readonly count?: number;
12
+ /** Tint as 0xRRGGBB. */
13
+ readonly color?: number;
14
+ /** Size multiplier. */
15
+ readonly scale?: number;
16
+ /** Bias direction in the ground plane (e.g. the hit direction). */
17
+ readonly dirX?: number;
18
+ readonly dirZ?: number;
19
+ }
20
+ /** World position to CSS pixels on the canvas. */
21
+ export type Projector = (x: number, y: number, z: number) => {
22
+ x: number;
23
+ y: number;
24
+ onScreen: boolean;
25
+ };
26
+ /** Anything that takes trauma and an optional FOV punch: a CameraRig, or a bare Trauma. */
27
+ export interface TraumaTarget {
28
+ addTrauma(amount: number): void;
29
+ punch?(fovKickDegrees: number): void;
30
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared shapes of the game-feel kit. Kept free of
3
+ * three.js and the DOM so the kit imports anywhere, including node tests.
4
+ */
5
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @netforge/engine/runtime: the client runtime. Used by templates and
3
+ * the Launcher shell, never imported by generated game code (which sees
4
+ * only the declarative API at the package root).
5
+ */
6
+ export { createRenderer } from "./renderer.js";
7
+ export type { Backend, RendererHandle, RendererOptions } from "./renderer.js";
8
+ export { InstancedCrowd } from "./crowd.js";
9
+ export { boundsShift, CameraRig, fieldBounds, groundFootprint } from "./camera.js";
10
+ export type { CameraRigOptions, GroundBounds } from "./camera.js";
11
+ export { InputController } from "./input.js";
12
+ export type { InputOptions, LookTarget } from "./input.js";
13
+ export { AssetLoader } from "./assets.js";
14
+ export type { AssetLoaderOptions } from "./assets.js";
15
+ export { FrameStats } from "./stats.js";
16
+ export { lerp, lerpAngle, damp } from "./interpolate.js";
17
+ export { ArenaClient } from "./arena.js";
18
+ export type { ArenaOptions, ArenaStats } from "./arena.js";
19
+ export { collectManifest, resetRegistry } from "../api/registry.js";
20
+ export { SystemScheduler } from "../api/scheduler.js";
21
+ export type { SystemHost } from "../api/scheduler.js";
22
+ export * from "./feel/index.js";
23
+ export * from "./render/index.js";
24
+ export * from "./styles/index.js";
25
+ export { createStyledView } from "./styles/view.js";
26
+ export type { StyledView, StyledViewOptions } from "./styles/view.js";
27
+ export { createPixelStyle, PIXEL_PALETTE, pixelBlock } from "./styles/pixel/index.js";
28
+ export type { PixelStyleOptions } from "./styles/pixel/index.js";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @netforge/engine/runtime: the client runtime. Used by templates and
3
+ * the Launcher shell, never imported by generated game code (which sees
4
+ * only the declarative API at the package root).
5
+ */
6
+ export { createRenderer } from "./renderer.js";
7
+ export { InstancedCrowd } from "./crowd.js";
8
+ export { boundsShift, CameraRig, fieldBounds, groundFootprint } from "./camera.js";
9
+ export { InputController } from "./input.js";
10
+ export { AssetLoader } from "./assets.js";
11
+ export { FrameStats } from "./stats.js";
12
+ export { lerp, lerpAngle, damp } from "./interpolate.js";
13
+ export { ArenaClient } from "./arena.js";
14
+ export { collectManifest, resetRegistry } from "../api/registry.js";
15
+ export { SystemScheduler } from "../api/scheduler.js";
16
+ // Game feel kit (§13): trauma shake, hit-stop clock, hit flash, knockback offsets,
17
+ // damage numbers, tweens, audio hooks, rumble and the FeelKit facade.
18
+ export * from "./feel/index.js";
19
+ // Render pipeline (§5): lighting and shadows, post, atmosphere, quality tiers,
20
+ // GPU particles, decals, animated characters and crowds, createRenderStack.
21
+ export * from "./render/index.js";
22
+ // Art styles (§10): each style's kit (look, world, crowds, particles), by style id.
23
+ export * from "./styles/index.js";
24
+ // A template that draws without the render stack applies a style with createStyledView.
25
+ export { createStyledView } from "./styles/view.js";
26
+ export { createPixelStyle, PIXEL_PALETTE, pixelBlock } from "./styles/pixel/index.js";
@@ -0,0 +1,38 @@
1
+ import type { InputCommand } from "@netforge/engine-sim";
2
+ export interface InputOptions {
3
+ /** Radians of yaw/pitch per pixel of mouse movement. */
4
+ readonly mouseSensitivity?: number;
5
+ /** Radians per pixel of touch-drag look. */
6
+ readonly touchSensitivity?: number;
7
+ /** Force the touch UI on (default: when the device reports coarse pointers / touch). */
8
+ readonly touch?: boolean;
9
+ }
10
+ export interface LookTarget {
11
+ look(dYaw: number, dPitch: number): void;
12
+ readonly yaw: number;
13
+ }
14
+ /**
15
+ * Keyboard + mouse (pointer lock) and touch (nipplejs virtual stick on the
16
+ * left half, drag-to-look and action buttons on the right). `sample()`
17
+ * produces one sequence-numbered InputCommand per fixed tick.
18
+ */
19
+ export declare class InputController {
20
+ private readonly element;
21
+ private readonly look;
22
+ private readonly keys;
23
+ private mouseButtons;
24
+ private stickX;
25
+ private stickY;
26
+ private touchButtons;
27
+ private seq;
28
+ private readonly cleanup;
29
+ private readonly o;
30
+ readonly touch: boolean;
31
+ constructor(element: HTMLElement, look: LookTarget, opts?: InputOptions);
32
+ private on;
33
+ private bindDesktop;
34
+ private bindTouch;
35
+ /** One command for the next fixed tick. */
36
+ sample(tick: number): InputCommand;
37
+ dispose(): void;
38
+ }
@@ -0,0 +1,159 @@
1
+ import { Button } from "@netforge/engine-sim";
2
+ const KEY_BUTTONS = {
3
+ Space: Button.Jump,
4
+ ShiftLeft: Button.Sprint,
5
+ ShiftRight: Button.Sprint,
6
+ KeyE: Button.Use,
7
+ KeyF: Button.Fire,
8
+ KeyQ: Button.Alt,
9
+ };
10
+ /**
11
+ * Keyboard + mouse (pointer lock) and touch (nipplejs virtual stick on the
12
+ * left half, drag-to-look and action buttons on the right). `sample()`
13
+ * produces one sequence-numbered InputCommand per fixed tick.
14
+ */
15
+ export class InputController {
16
+ element;
17
+ look;
18
+ keys = new Set();
19
+ mouseButtons = 0;
20
+ stickX = 0;
21
+ stickY = 0;
22
+ touchButtons = 0;
23
+ seq = 0;
24
+ cleanup = [];
25
+ o;
26
+ touch;
27
+ constructor(element, look, opts = {}) {
28
+ this.element = element;
29
+ this.look = look;
30
+ this.o = {
31
+ mouseSensitivity: 0.0025,
32
+ touchSensitivity: 0.006,
33
+ touch: opts.touch ?? (globalThis.matchMedia?.("(pointer: coarse)").matches || "ontouchstart" in globalThis),
34
+ ...opts,
35
+ };
36
+ this.touch = this.o.touch;
37
+ this.bindDesktop();
38
+ if (this.touch)
39
+ void this.bindTouch();
40
+ }
41
+ on(target, type, fn) {
42
+ target.addEventListener(type, fn, { passive: false });
43
+ this.cleanup.push(() => target.removeEventListener(type, fn));
44
+ }
45
+ bindDesktop() {
46
+ const el = this.element;
47
+ this.on(window, "keydown", (e) => {
48
+ if (e.code === "Space")
49
+ e.preventDefault();
50
+ this.keys.add(e.code);
51
+ });
52
+ this.on(window, "keyup", (e) => this.keys.delete(e.code));
53
+ this.on(window, "blur", () => {
54
+ this.keys.clear();
55
+ this.mouseButtons = 0;
56
+ });
57
+ this.on(el, "mousedown", (e) => {
58
+ if (this.touch)
59
+ return;
60
+ if (document.pointerLockElement !== el)
61
+ void el.requestPointerLock?.();
62
+ this.mouseButtons |= e.button === 0 ? Button.Fire : e.button === 2 ? Button.Alt : 0;
63
+ });
64
+ this.on(window, "mouseup", (e) => {
65
+ this.mouseButtons &= ~(e.button === 0 ? Button.Fire : e.button === 2 ? Button.Alt : 0);
66
+ });
67
+ this.on(el, "contextmenu", (e) => e.preventDefault());
68
+ this.on(window, "mousemove", (e) => {
69
+ if (document.pointerLockElement !== el)
70
+ return;
71
+ const s = this.o.mouseSensitivity;
72
+ this.look.look(-e.movementX * s, e.movementY * s);
73
+ });
74
+ }
75
+ async bindTouch() {
76
+ // nipplejs is only downloaded on touch devices.
77
+ const nipple = await import("nipplejs");
78
+ const left = document.createElement("div");
79
+ left.style.cssText = "position:absolute;left:0;top:0;bottom:0;width:50%;touch-action:none;z-index:2";
80
+ const right = document.createElement("div");
81
+ right.style.cssText = "position:absolute;right:0;top:0;bottom:0;width:50%;touch-action:none;z-index:2";
82
+ this.element.append(left, right);
83
+ this.cleanup.push(() => {
84
+ left.remove();
85
+ right.remove();
86
+ });
87
+ const sticks = nipple.create({ zone: left, mode: "dynamic", color: "white", size: 110 });
88
+ sticks.on("move", (evt) => {
89
+ this.stickX = evt.data.vector.x;
90
+ this.stickY = evt.data.vector.y;
91
+ });
92
+ sticks.on("end", () => {
93
+ this.stickX = 0;
94
+ this.stickY = 0;
95
+ });
96
+ this.cleanup.push(() => sticks.destroy());
97
+ // Right half: drag to look; two buttons for jump and fire.
98
+ let lastX = 0;
99
+ let lastY = 0;
100
+ let lookId = null;
101
+ this.on(right, "pointerdown", (e) => {
102
+ if (e.target !== right)
103
+ return;
104
+ lookId = e.pointerId;
105
+ lastX = e.clientX;
106
+ lastY = e.clientY;
107
+ });
108
+ this.on(right, "pointermove", (e) => {
109
+ if (e.pointerId !== lookId)
110
+ return;
111
+ const s = this.o.touchSensitivity;
112
+ this.look.look(-(e.clientX - lastX) * s, (e.clientY - lastY) * s);
113
+ lastX = e.clientX;
114
+ lastY = e.clientY;
115
+ });
116
+ const endLook = (e) => {
117
+ if (e.pointerId === lookId)
118
+ lookId = null;
119
+ };
120
+ this.on(right, "pointerup", endLook);
121
+ this.on(right, "pointercancel", endLook);
122
+ const mk = (label, bit, bottom, rightPx) => {
123
+ const b = document.createElement("button");
124
+ b.textContent = label;
125
+ b.style.cssText = `position:absolute;bottom:${bottom}px;right:${rightPx}px;width:76px;height:76px;border-radius:50%;border:2px solid rgba(255,255,255,.6);background:rgba(255,255,255,.12);color:#fff;font:600 13px system-ui;touch-action:none`;
126
+ b.addEventListener("pointerdown", (e) => {
127
+ e.preventDefault();
128
+ this.touchButtons |= bit;
129
+ });
130
+ const up = () => {
131
+ this.touchButtons &= ~bit;
132
+ };
133
+ b.addEventListener("pointerup", up);
134
+ b.addEventListener("pointercancel", up);
135
+ b.addEventListener("pointerleave", up);
136
+ right.append(b);
137
+ };
138
+ mk("JUMP", Button.Jump, 32, 120);
139
+ mk("PULSE", Button.Fire, 110, 32);
140
+ }
141
+ /** One command for the next fixed tick. */
142
+ sample(tick) {
143
+ const k = this.keys;
144
+ let mx = (k.has("KeyD") || k.has("ArrowRight") ? 1 : 0) - (k.has("KeyA") || k.has("ArrowLeft") ? 1 : 0);
145
+ let mz = (k.has("KeyW") || k.has("ArrowUp") ? 1 : 0) - (k.has("KeyS") || k.has("ArrowDown") ? 1 : 0);
146
+ if (this.stickX !== 0 || this.stickY !== 0) {
147
+ mx = this.stickX;
148
+ mz = this.stickY;
149
+ }
150
+ let buttons = this.mouseButtons | this.touchButtons;
151
+ for (const code of k)
152
+ buttons |= KEY_BUTTONS[code] ?? 0;
153
+ return { seq: ++this.seq, tick, moveX: mx, moveZ: mz, yaw: this.look.yaw, buttons };
154
+ }
155
+ dispose() {
156
+ for (const fn of this.cleanup.splice(0))
157
+ fn();
158
+ }
159
+ }
@@ -0,0 +1,6 @@
1
+ /** Linear interpolation. */
2
+ export declare const lerp: (a: number, b: number, t: number) => number;
3
+ /** Interpolate angles along the shortest arc (radians). */
4
+ export declare function lerpAngle(a: number, b: number, t: number): number;
5
+ /** Frame-rate independent exponential smoothing factor for a rate in 1/s. */
6
+ export declare const damp: (rate: number, dt: number) => number;