@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,107 @@
1
+ import { AnimationClip, Bone, CylinderGeometry, Float32BufferAttribute, Quaternion, QuaternionKeyframeTrack, Skeleton, SkinnedMesh, Uint16BufferAttribute, Vector3, VectorKeyframeTrack, } from "three/webgpu";
2
+ const q = (x, y, z) => {
3
+ const v = new Quaternion().setFromAxisAngle(new Vector3(x, y, z).normalize(), Math.hypot(x, y, z));
4
+ return [v.x, v.y, v.z, v.w];
5
+ };
6
+ /** A looping rotation track: samples of [x, y, z] axis-angle rotations evenly over `seconds`. */
7
+ function rotTrack(bone, seconds, rots) {
8
+ const times = [];
9
+ const values = [];
10
+ rots.forEach((r, i) => {
11
+ times.push((seconds * i) / (rots.length - 1));
12
+ values.push(...(Math.hypot(...r) < 1e-9 ? [0, 0, 0, 1] : q(...r)));
13
+ });
14
+ return new QuaternionKeyframeTrack(`${bone}.quaternion`, times, values);
15
+ }
16
+ function posTrack(bone, seconds, base, ys) {
17
+ const times = [];
18
+ const values = [];
19
+ ys.forEach((y, i) => {
20
+ times.push((seconds * i) / (ys.length - 1));
21
+ values.push(0, base + y, 0);
22
+ });
23
+ return new VectorKeyframeTrack(`${bone}.position`, times, values);
24
+ }
25
+ /**
26
+ * A three-bone procedural character (hip, spine, head) skinned to a
27
+ * cylinder or any upright geometry, with a full animation set built in
28
+ * code: rest, idle, walk, run, hit, attack, die. Templates with procedural
29
+ * art get real skinned animation (and VAT crowds via bakeVertexAnimation)
30
+ * without any asset files.
31
+ */
32
+ export function buildSegmentRig(material, opts = {}) {
33
+ const h = opts.height ?? 1.2;
34
+ const r = opts.radius ?? 0.35;
35
+ const geometry = opts.geometry ??
36
+ (() => {
37
+ const g = new CylinderGeometry(opts.topRadius ?? r * 0.75, r, h, opts.radialSegments ?? 10, opts.heightSegments ?? 6);
38
+ g.translate(0, h / 2, 0);
39
+ return g;
40
+ })();
41
+ const pos = geometry.getAttribute("position");
42
+ const idx = [];
43
+ const wts = [];
44
+ for (let i = 0; i < pos.count; i++) {
45
+ const y = Math.min(1, Math.max(0, pos.getY(i) / h));
46
+ // Hip owns the bottom half, spine the middle, head the top third.
47
+ if (y < 0.5) {
48
+ const w = y / 0.5;
49
+ idx.push(0, 1, 0, 0);
50
+ wts.push(1 - w, w, 0, 0);
51
+ }
52
+ else {
53
+ const w = Math.min(1, (y - 0.5) / 0.35);
54
+ idx.push(1, 2, 0, 0);
55
+ wts.push(1 - w, w, 0, 0);
56
+ }
57
+ }
58
+ geometry.setAttribute("skinIndex", new Uint16BufferAttribute(idx, 4));
59
+ geometry.setAttribute("skinWeight", new Float32BufferAttribute(wts, 4));
60
+ const hip = new Bone();
61
+ hip.name = "hip";
62
+ const spine = new Bone();
63
+ spine.name = "spine";
64
+ spine.position.y = h * 0.5;
65
+ const head = new Bone();
66
+ head.name = "head";
67
+ head.position.y = h * 0.35;
68
+ hip.add(spine);
69
+ spine.add(head);
70
+ const mesh = new SkinnedMesh(geometry, material);
71
+ mesh.add(hip);
72
+ mesh.bind(new Skeleton([hip, spine, head]));
73
+ const hb = 0;
74
+ const clips = [
75
+ new AnimationClip("rest", 1, [rotTrack("hip", 1, [[0, 0, 0], [0, 0, 0]])]),
76
+ new AnimationClip("idle", 2, [
77
+ posTrack("hip", 2, hb, [0, -0.02 * h, 0]),
78
+ rotTrack("spine", 2, [[0.02, 0, 0], [-0.03, 0, 0.02], [0.02, 0, 0]]),
79
+ rotTrack("head", 2, [[0, 0.05, 0], [0, -0.05, 0], [0, 0.05, 0]]),
80
+ ]),
81
+ new AnimationClip("walk", 0.8, [
82
+ posTrack("hip", 0.8, hb, [0, 0.05 * h, 0, 0.05 * h, 0]),
83
+ rotTrack("hip", 0.8, [[-0.08, 0, 0.12], [-0.08, 0, 0], [-0.08, 0, -0.12], [-0.08, 0, 0], [-0.08, 0, 0.12]]),
84
+ rotTrack("spine", 0.8, [[0, 0.12, -0.06], [0, 0, 0], [0, -0.12, 0.06], [0, 0, 0], [0, 0.12, -0.06]]),
85
+ rotTrack("head", 0.8, [[0.05, -0.08, 0], [0.05, 0, 0], [0.05, 0.08, 0], [0.05, 0, 0], [0.05, -0.08, 0]]),
86
+ ]),
87
+ new AnimationClip("run", 0.5, [
88
+ posTrack("hip", 0.5, hb, [0, 0.12 * h, 0, 0.12 * h, 0]),
89
+ rotTrack("hip", 0.5, [[-0.22, 0, 0.18], [-0.22, 0, 0], [-0.22, 0, -0.18], [-0.22, 0, 0], [-0.22, 0, 0.18]]),
90
+ rotTrack("spine", 0.5, [[-0.1, 0.2, -0.1], [-0.1, 0, 0], [-0.1, -0.2, 0.1], [-0.1, 0, 0], [-0.1, 0.2, -0.1]]),
91
+ rotTrack("head", 0.5, [[0.15, 0, 0], [0.1, 0, 0], [0.15, 0, 0], [0.1, 0, 0], [0.15, 0, 0]]),
92
+ ]),
93
+ new AnimationClip("hit", 0.3, [
94
+ rotTrack("spine", 0.3, [[0, 0, 0], [0.45, 0, 0.1], [0.1, 0, 0], [0, 0, 0]]),
95
+ rotTrack("head", 0.3, [[0, 0, 0], [0.3, 0, -0.1], [0, 0, 0], [0, 0, 0]]),
96
+ ]),
97
+ new AnimationClip("attack", 0.45, [
98
+ rotTrack("spine", 0.45, [[0, 0, 0], [0.15, -0.6, 0], [-0.3, 0.7, 0], [0, 0, 0]]),
99
+ posTrack("hip", 0.45, hb, [0, -0.04 * h, 0.02 * h, 0]),
100
+ ]),
101
+ new AnimationClip("die", 0.7, [
102
+ rotTrack("hip", 0.7, [[0, 0, 0], [0.4, 0, 0], [1.4, 0, 0.2]]),
103
+ new VectorKeyframeTrack("head.scale", [0, 0.7], [1, 1, 1, 1, 0.8, 1]),
104
+ ]),
105
+ ];
106
+ return { mesh, bones: [hip, spine, head], clips };
107
+ }
@@ -0,0 +1,34 @@
1
+ import type { Node } from "three/webgpu";
2
+ /** Metres one source pixel covers at `spriteScale` 1. */
3
+ export declare const SPRITE_TEXEL: number;
4
+ /** The pixel style's resize for every billboard sprite (1 elsewhere). */
5
+ export declare const spriteScale: import("three/webgpu").UniformNode<"float", number>;
6
+ /**
7
+ * World position of a sprite vertex: `center` is the feet in world space,
8
+ * `x`/`y` the vertex in metres on the plane (x across, y up from the feet),
9
+ * `flip` -1 to mirror. The plane turns to the camera (its up is the camera's
10
+ * up, so a sprite under a high camera shows its full height).
11
+ */
12
+ export declare function billboardWorld(center: Node<"vec3">, x: Node<"float">, y: Node<"float">, flip: Node<"float">): Node<"vec3">;
13
+ /**
14
+ * positionNode for a single sprite mesh (a player, a boss): the plane
15
+ * faces the camera whatever the mesh's rotation, and mirrors when the
16
+ * mesh's forward (-z) points to screen left.
17
+ */
18
+ export declare function spriteBillboardNode(): Node<"vec3">;
19
+ /** Flag a sprite plane so AnimatedCrowd draws it as billboards (geometry.userData). */
20
+ export declare function markSprite(geometry: {
21
+ userData: Record<string, unknown>;
22
+ }): void;
23
+ /** Whether a geometry is a sprite plane (markSprite). */
24
+ export declare function isSprite(geometry: {
25
+ userData: Record<string, unknown>;
26
+ }): boolean;
27
+ /**
28
+ * A sprite material's extra output: the pixel style keeps sprites in their
29
+ * own palette colours and steps only the 3D world into light bands. Safe
30
+ * everywhere: a target without a "sprite" texture just takes the colour.
31
+ */
32
+ export declare function markSpriteMaterial(material: {
33
+ mrtNode: unknown;
34
+ }): void;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 2D sprites in the 3D scene (the pixel art style).
3
+ * A sprite is an upright plane with its feet at y = 0, built in metres at
4
+ * SPRITE_TEXEL metres per source pixel. Drawn as a billboard it always
5
+ * faces the camera, mirrors to face where it walks, and is resized by
6
+ * `spriteScale`, which the pixel style sets each frame so one sprite texel
7
+ * covers exactly one of its low-resolution pixels (one pixel scale for the
8
+ * whole picture). Without the pixel style `spriteScale` stays 1.
9
+ */
10
+ import { Vector3 } from "three/webgpu";
11
+ import { float, modelWorldMatrix, mrt, output, modelWorldMatrixInverse, positionGeometry, select, uniform, vec4 } from "three/tsl";
12
+ /** Metres one source pixel covers at `spriteScale` 1. */
13
+ export const SPRITE_TEXEL = 1 / 16;
14
+ /** The pixel style's resize for every billboard sprite (1 elsewhere). */
15
+ export const spriteScale = uniform(1);
16
+ const camRight = uniform(new Vector3(1, 0, 0)).onObjectUpdate(({ camera }, self) => {
17
+ return camera ? self.value.setFromMatrixColumn(camera.matrixWorld, 0) : undefined;
18
+ });
19
+ const camUp = uniform(new Vector3(0, 1, 0)).onObjectUpdate(({ camera }, self) => {
20
+ return camera ? self.value.setFromMatrixColumn(camera.matrixWorld, 1) : undefined;
21
+ });
22
+ /**
23
+ * World position of a sprite vertex: `center` is the feet in world space,
24
+ * `x`/`y` the vertex in metres on the plane (x across, y up from the feet),
25
+ * `flip` -1 to mirror. The plane turns to the camera (its up is the camera's
26
+ * up, so a sprite under a high camera shows its full height).
27
+ */
28
+ export function billboardWorld(center, x, y, flip) {
29
+ return center.add(camRight.mul(x.mul(flip).mul(spriteScale))).add(camUp.mul(y.mul(spriteScale)));
30
+ }
31
+ /**
32
+ * positionNode for a single sprite mesh (a player, a boss): the plane
33
+ * faces the camera whatever the mesh's rotation, and mirrors when the
34
+ * mesh's forward (-z) points to screen left.
35
+ */
36
+ export function spriteBillboardNode() {
37
+ const center = modelWorldMatrix.mul(vec4(0, 0, 0, 1)).xyz;
38
+ const forward = modelWorldMatrix.mul(vec4(0, 0, -1, 0)).xyz;
39
+ const flip = select(forward.dot(camRight).lessThan(-0.05), float(-1), float(1));
40
+ const world = billboardWorld(center, positionGeometry.x, positionGeometry.y, flip);
41
+ return modelWorldMatrixInverse.mul(vec4(world, 1)).xyz;
42
+ }
43
+ /** Flag a sprite plane so AnimatedCrowd draws it as billboards (geometry.userData). */
44
+ export function markSprite(geometry) {
45
+ geometry.userData.sprite = true;
46
+ }
47
+ /** Whether a geometry is a sprite plane (markSprite). */
48
+ export function isSprite(geometry) {
49
+ return geometry.userData.sprite === true;
50
+ }
51
+ /**
52
+ * A sprite material's extra output: the pixel style keeps sprites in their
53
+ * own palette colours and steps only the 3D world into light bands. Safe
54
+ * everywhere: a target without a "sprite" texture just takes the colour.
55
+ */
56
+ export function markSpriteMaterial(material) {
57
+ material.mrtNode = mrt({ output, sprite: float(1) });
58
+ }
@@ -0,0 +1,74 @@
1
+ import type { Camera, Scene } from "three/webgpu";
2
+ import type { RendererHandle } from "../renderer.js";
3
+ import { Atmosphere } from "./atmosphere.js";
4
+ import { GroundDecals, GroundRings } from "./decals.js";
5
+ import { LightingRig } from "./lighting.js";
6
+ import { GpuParticles } from "./particles.js";
7
+ import { PostFX } from "./post.js";
8
+ import type { GradePreset, LightingPreset, AtmospherePreset, LookPreset } from "./presets.js";
9
+ import { QualityManager } from "./quality.js";
10
+ import type { QualityTier } from "./quality.js";
11
+ import type { StyleKit, StyleSession } from "../styles/kit.js";
12
+ export interface RenderStackOptions {
13
+ /** Look preset: lighting, fog, sky and grade together. Default "night". */
14
+ readonly preset?: LookPreset;
15
+ /** The game's art style (game({ style }), or a template's ?style=). Its kit (runtime/styles) brings its look, post stages and session; low-poly and unknown names draw the stack's own picture. */
16
+ readonly style?: string;
17
+ /** "auto" detects from the device. Default "auto". */
18
+ readonly quality?: QualityTier | "auto";
19
+ readonly bloom?: boolean;
20
+ /** Toon outline (medium and high tiers only). Default false. */
21
+ readonly outline?: boolean;
22
+ /** Dynamic resolution (default true). */
23
+ readonly dynamicResolution?: boolean;
24
+ /** Half-size of the shadowed square around the focus, m. Default 26. */
25
+ readonly shadowExtent?: number;
26
+ readonly lighting?: Partial<LightingPreset>;
27
+ readonly atmosphere?: Partial<AtmospherePreset>;
28
+ readonly grade?: Partial<GradePreset>;
29
+ /** GPU particle slots (default from the tier). */
30
+ readonly particles?: number;
31
+ /** Build the particle system (an art style swaps in its own, e.g. the voxel style's cubes). Default GpuParticles. */
32
+ readonly makeParticles?: (scene: Scene, capacity: number) => GpuParticles;
33
+ readonly decals?: number;
34
+ /** Library id to URL, for an art style's own textures (the template's LibraryClient.url). */
35
+ readonly resolve?: (id: string) => string | null;
36
+ }
37
+ export interface RenderStack {
38
+ readonly lighting: LightingRig;
39
+ readonly atmosphere: Atmosphere;
40
+ readonly post: PostFX;
41
+ readonly particles: GpuParticles;
42
+ readonly decals: GroundDecals;
43
+ readonly rings: GroundRings;
44
+ readonly quality: QualityManager;
45
+ /** The art style drawing the frame (its kit is registered in runtime/styles), or null for the runtime's own low-poly look. */
46
+ readonly style: string | null;
47
+ /** That style's kit (world builders for the template), or null. */
48
+ readonly kit: StyleKit | null;
49
+ /** The style attached to this renderer (its surfaces, its state), or null. */
50
+ readonly session: StyleSession | null;
51
+ /** Seconds of render time accumulated by `frame` (drives particles and decals). */
52
+ readonly time: number;
53
+ /**
54
+ * Per frame: advance effect time by `dt` (already hit-stop scaled), move
55
+ * the shadow to the focus, step dynamic resolution, draw. Returns true
56
+ * when the pixel ratio changed (call your resize).
57
+ */
58
+ frame(dt: number, focus: {
59
+ x: number;
60
+ y: number;
61
+ z: number;
62
+ }, realNowMs?: number): boolean;
63
+ resize(width: number, height: number): void;
64
+ setPreset(preset: LookPreset): void;
65
+ setQuality(tier: QualityTier): void;
66
+ dispose(): void;
67
+ }
68
+ /**
69
+ * Everything a template needs for a good-looking frame, in one call:
70
+ * lighting with a following shadow, fog and sky, bloom and grade, GPU
71
+ * particles, decals and shockwave rings, quality tiers and dynamic
72
+ * resolution. Replace `renderer.render(scene, camera)` with `stack.frame(...)`.
73
+ */
74
+ export declare function createRenderStack(handle: RendererHandle, scene: Scene, camera: Camera, opts?: RenderStackOptions): RenderStack;
@@ -0,0 +1,132 @@
1
+ import { Atmosphere } from "./atmosphere.js";
2
+ import { GroundDecals, GroundRings } from "./decals.js";
3
+ import { LightingRig } from "./lighting.js";
4
+ import { GpuParticles } from "./particles.js";
5
+ import { PostFX } from "./post.js";
6
+ import { browserHints, detectTier, QualityManager } from "./quality.js";
7
+ import { styleKit } from "../styles/index.js";
8
+ /**
9
+ * Everything a template needs for a good-looking frame, in one call:
10
+ * lighting with a following shadow, fog and sky, bloom and grade, GPU
11
+ * particles, decals and shockwave rings, quality tiers and dynamic
12
+ * resolution. Replace `renderer.render(scene, camera)` with `stack.frame(...)`.
13
+ */
14
+ export function createRenderStack(handle, scene, camera, opts = {}) {
15
+ const { renderer } = handle;
16
+ const preset = opts.preset ?? "night";
17
+ const tier = opts.quality === undefined || opts.quality === "auto" ? detectTier(browserHints(handle.backend)) : opts.quality;
18
+ const dpr = globalThis.devicePixelRatio ?? 1;
19
+ const quality = new QualityManager(renderer, tier, { devicePixelRatio: dpr, dynamic: opts.dynamicResolution ?? true });
20
+ const s = quality.settings;
21
+ // The art style: the one place the stack looks a style up. Its look and stack overrides sit under the template's own options.
22
+ const kit = styleKit(opts.style);
23
+ const overrides = (p) => {
24
+ const look = kit?.look?.(p);
25
+ const st = kit?.stack?.(p);
26
+ const merge = (a, b, c) => a || b || c ? { ...a, ...b, ...c } : undefined;
27
+ return {
28
+ lighting: merge(look?.lighting, st?.lighting, opts.lighting),
29
+ atmosphere: merge(look?.atmosphere, st?.atmosphere, opts.atmosphere),
30
+ grade: merge(look?.grade, st?.grade, opts.grade),
31
+ };
32
+ };
33
+ const o = overrides(preset);
34
+ const lighting = new LightingRig(renderer, scene, {
35
+ preset,
36
+ shadowMapSize: s.shadowMapSize,
37
+ ...(o.lighting ? { overrides: o.lighting } : {}),
38
+ ...(opts.shadowExtent !== undefined ? { shadowExtent: opts.shadowExtent } : {}),
39
+ });
40
+ const atmosphere = new Atmosphere(scene, { preset, ...(o.atmosphere ? { overrides: o.atmosphere } : {}) });
41
+ const outlineWanted = (opts.outline ?? false) && kit?.outline !== false;
42
+ // A style whose look lives in post keeps post on at the low tier, without bloom.
43
+ const styledLow = (postOn) => kit?.needsPost === true && !postOn;
44
+ const bloomWanted = opts.bloom ?? kit?.bloom ?? true;
45
+ const post = new PostFX(renderer, scene, camera, {
46
+ preset,
47
+ bloom: bloomWanted && !styledLow(s.post),
48
+ outline: outlineWanted && s.outline,
49
+ enabled: s.post || styledLow(s.post),
50
+ bloomScale: s.bloomScale,
51
+ ...(o.grade ? { overrides: o.grade } : {}),
52
+ });
53
+ const session = kit?.attach?.({ handle, renderer, scene, camera, tier, preset, post, atmosphere, ...(opts.resolve ? { resolve: opts.resolve } : {}) }) ?? null;
54
+ if (session?.post)
55
+ post.setStyle(session.post);
56
+ const particleCap = opts.particles ?? s.particles;
57
+ const makeParticles = opts.makeParticles ?? kit?.stack?.(preset).makeParticles;
58
+ const particles = makeParticles
59
+ ? makeParticles(scene, particleCap)
60
+ : new GpuParticles(scene, { capacity: particleCap, ...(session?.particles ? { look: session.particles } : {}) });
61
+ const decals = new GroundDecals(scene, opts.decals ?? 256, Math.random, session?.decals);
62
+ const rings = new GroundRings(scene, 32, session?.rings);
63
+ quality.onChange((q) => {
64
+ lighting.setShadowMapSize(q.shadowMapSize);
65
+ post.setQuality(q.tier, outlineWanted && q.outline);
66
+ if (kit?.needsPost)
67
+ post.configure(styledLow(q.post) ? { enabled: true, bloom: false } : { bloom: bloomWanted });
68
+ session?.setQuality?.(q.tier);
69
+ });
70
+ let time = 0;
71
+ let w = 1;
72
+ let h = 1;
73
+ const stack = {
74
+ lighting,
75
+ atmosphere,
76
+ post,
77
+ particles,
78
+ decals,
79
+ rings,
80
+ quality,
81
+ style: kit?.id ?? null,
82
+ kit,
83
+ session,
84
+ get time() {
85
+ return time;
86
+ },
87
+ frame(dt, focus, realNowMs) {
88
+ time += Math.max(0, dt);
89
+ session?.frame?.((realNowMs ?? performance.now()) / 1000);
90
+ lighting.follow(focus.x, focus.y, focus.z);
91
+ particles.update(time);
92
+ decals.update(time);
93
+ rings.update(time);
94
+ if (session?.render)
95
+ session.render(() => post.render());
96
+ else
97
+ post.render();
98
+ const changed = quality.frame(realNowMs ?? performance.now());
99
+ if (changed)
100
+ stack.resize(w, h);
101
+ return changed;
102
+ },
103
+ resize(width, height) {
104
+ w = Math.max(1, width);
105
+ h = Math.max(1, height);
106
+ renderer.setSize(w, h, false);
107
+ post.setSize(w, h);
108
+ const pr = renderer.getPixelRatio();
109
+ session?.setSize?.(w * pr, h * pr);
110
+ },
111
+ setPreset(p) {
112
+ const n = overrides(p);
113
+ lighting.setPreset(p, n.lighting);
114
+ atmosphere.setPreset(p, n.atmosphere);
115
+ post.setPreset(p, n.grade);
116
+ session?.setPreset?.(p);
117
+ },
118
+ setQuality(t) {
119
+ quality.setTier(t);
120
+ },
121
+ dispose() {
122
+ session?.dispose();
123
+ particles.dispose();
124
+ decals.dispose();
125
+ rings.dispose();
126
+ post.dispose();
127
+ atmosphere.dispose();
128
+ lighting.dispose();
129
+ },
130
+ };
131
+ return stack;
132
+ }
@@ -0,0 +1,78 @@
1
+ import { BufferGeometry, DataTexture, Mesh } from "three/webgpu";
2
+ import type { AnimationClip, Material, Node, Object3D, SkinnedMesh } from "three/webgpu";
3
+ export interface VatClip {
4
+ readonly name: string;
5
+ /** First frame of this clip in the texture. */
6
+ readonly start: number;
7
+ readonly frames: number;
8
+ }
9
+ /** A vertex animation texture: every vertex's skinned position (and normal) per frame. */
10
+ export interface VertexAnimation {
11
+ readonly positions: DataTexture;
12
+ readonly normals: DataTexture | null;
13
+ readonly vertexCount: number;
14
+ /** Texture width (vertices per row) and rows per frame. */
15
+ readonly width: number;
16
+ readonly rowsPerFrame: number;
17
+ readonly totalFrames: number;
18
+ readonly fps: number;
19
+ readonly clips: readonly VatClip[];
20
+ /** The source geometry (index, uv) the crowd draws. */
21
+ readonly geometry: BufferGeometry;
22
+ }
23
+ export interface BakeOptions {
24
+ /** Sample rate, frames per second. Default 30. */
25
+ readonly fps?: number;
26
+ /** Frames per clip (default ceil(duration x fps), at least 1). */
27
+ readonly frames?: number;
28
+ /** Also bake per-frame normals (smooth-shaded crowds). Default true. */
29
+ readonly normals?: boolean;
30
+ /** Max texture width. Default 2048. */
31
+ readonly maxWidth?: number;
32
+ }
33
+ /**
34
+ * Bake one or more clips of a skinned mesh into a vertex animation texture
35
+ * (CPU, at load). Frames are laid out clip after clip; a crowd then plays
36
+ * any clip on thousands of instances in one draw (`VatCrowd`).
37
+ */
38
+ export declare function bakeVertexAnimation(mesh: SkinnedMesh, clips: AnimationClip | readonly AnimationClip[], opts?: BakeOptions): VertexAnimation;
39
+ export interface VatCrowdOptions {
40
+ /** Use baked normals (smooth shading). Off for flat-shaded materials. Default true when baked. */
41
+ readonly bakedNormals?: boolean;
42
+ }
43
+ /**
44
+ * Thousands of animated characters in one draw: every instance samples the
45
+ * VAT at its own clip, phase and rate, then is rotated, scaled and placed
46
+ * from per-instance attributes. CPU work per instance is one 12-float write.
47
+ */
48
+ export declare class VatCrowd {
49
+ readonly vat: VertexAnimation;
50
+ readonly capacity: number;
51
+ readonly mesh: Mesh;
52
+ readonly time: import("three/webgpu").UniformNode<"float", number>;
53
+ count: number;
54
+ private readonly xform;
55
+ private readonly anim;
56
+ private readonly clip;
57
+ private readonly clipIndex;
58
+ constructor(vat: VertexAnimation, material: Material & {
59
+ positionNode: Node | null;
60
+ normalNode: Node | null;
61
+ emissiveNode?: Node | null;
62
+ flatShading?: boolean;
63
+ }, capacity: number, opts?: VatCrowdOptions);
64
+ clipNamed(name: string): VatClip | undefined;
65
+ begin(): void;
66
+ /** Add one instance this frame. `phase` offsets the clip in seconds; `rate` scales playback. */
67
+ push(x: number, y: number, z: number, yaw: number, opts?: {
68
+ clip?: string;
69
+ phase?: number;
70
+ rate?: number;
71
+ scale?: number;
72
+ flash?: number;
73
+ }): void;
74
+ /** Upload the instances written since `begin` and set the time (seconds). */
75
+ end(timeSeconds: number): void;
76
+ addTo(parent: Object3D): this;
77
+ dispose(): void;
78
+ }