@guinetik/gcanvas 1.0.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 (349) hide show
  1. package/.github/workflows/release.yaml +70 -0
  2. package/.jshintrc +4 -0
  3. package/.vscode/settings.json +22 -0
  4. package/CLAUDE.md +310 -0
  5. package/blackhole.jpg +0 -0
  6. package/demo.png +0 -0
  7. package/demos/CNAME +1 -0
  8. package/demos/animations.html +31 -0
  9. package/demos/basic.html +38 -0
  10. package/demos/baskara.html +31 -0
  11. package/demos/bezier.html +35 -0
  12. package/demos/beziersignature.html +29 -0
  13. package/demos/blackhole.html +28 -0
  14. package/demos/blob.html +35 -0
  15. package/demos/demos.css +289 -0
  16. package/demos/easing.html +28 -0
  17. package/demos/events.html +195 -0
  18. package/demos/fluent.html +647 -0
  19. package/demos/fractals.html +36 -0
  20. package/demos/genart.html +26 -0
  21. package/demos/gendream.html +26 -0
  22. package/demos/group.html +36 -0
  23. package/demos/home.html +587 -0
  24. package/demos/index.html +364 -0
  25. package/demos/isometric.html +34 -0
  26. package/demos/js/animations.js +452 -0
  27. package/demos/js/basic.js +204 -0
  28. package/demos/js/baskara.js +751 -0
  29. package/demos/js/bezier.js +692 -0
  30. package/demos/js/beziersignature.js +241 -0
  31. package/demos/js/blackhole/accretiondisk.obj.js +379 -0
  32. package/demos/js/blackhole/blackhole.obj.js +318 -0
  33. package/demos/js/blackhole/index.js +409 -0
  34. package/demos/js/blackhole/particle.js +56 -0
  35. package/demos/js/blackhole/starfield.obj.js +218 -0
  36. package/demos/js/blob.js +2263 -0
  37. package/demos/js/easing.js +477 -0
  38. package/demos/js/fluent.js +183 -0
  39. package/demos/js/fractals.js +931 -0
  40. package/demos/js/fractalworker.js +93 -0
  41. package/demos/js/genart.js +268 -0
  42. package/demos/js/gendream.js +209 -0
  43. package/demos/js/group.js +140 -0
  44. package/demos/js/info-toggle.js +25 -0
  45. package/demos/js/isometric.js +863 -0
  46. package/demos/js/kerr.js +1556 -0
  47. package/demos/js/lavalamp.js +590 -0
  48. package/demos/js/layout.js +354 -0
  49. package/demos/js/mondrian.js +285 -0
  50. package/demos/js/opacity.js +275 -0
  51. package/demos/js/painter.js +484 -0
  52. package/demos/js/particles-showcase.js +514 -0
  53. package/demos/js/particles.js +299 -0
  54. package/demos/js/patterns.js +397 -0
  55. package/demos/js/penrose/artifact.js +69 -0
  56. package/demos/js/penrose/blackhole.js +121 -0
  57. package/demos/js/penrose/constants.js +73 -0
  58. package/demos/js/penrose/game.js +943 -0
  59. package/demos/js/penrose/lore.js +278 -0
  60. package/demos/js/penrose/penrosescene.js +892 -0
  61. package/demos/js/penrose/ship.js +216 -0
  62. package/demos/js/penrose/sounds.js +211 -0
  63. package/demos/js/penrose/voidparticle.js +55 -0
  64. package/demos/js/penrose/voidscene.js +258 -0
  65. package/demos/js/penrose/voidship.js +144 -0
  66. package/demos/js/penrose/wormhole.js +46 -0
  67. package/demos/js/pipeline.js +555 -0
  68. package/demos/js/scene.js +304 -0
  69. package/demos/js/scenes.js +320 -0
  70. package/demos/js/schrodinger.js +410 -0
  71. package/demos/js/schwarzschild.js +1023 -0
  72. package/demos/js/shapes.js +628 -0
  73. package/demos/js/space/alien.js +171 -0
  74. package/demos/js/space/boom.js +98 -0
  75. package/demos/js/space/boss.js +353 -0
  76. package/demos/js/space/buff.js +73 -0
  77. package/demos/js/space/bullet.js +102 -0
  78. package/demos/js/space/constants.js +85 -0
  79. package/demos/js/space/game.js +1884 -0
  80. package/demos/js/space/hud.js +112 -0
  81. package/demos/js/space/laserbeam.js +179 -0
  82. package/demos/js/space/lightning.js +277 -0
  83. package/demos/js/space/minion.js +192 -0
  84. package/demos/js/space/missile.js +212 -0
  85. package/demos/js/space/player.js +430 -0
  86. package/demos/js/space/powerup.js +90 -0
  87. package/demos/js/space/starfield.js +58 -0
  88. package/demos/js/space/starpower.js +90 -0
  89. package/demos/js/spacetime.js +559 -0
  90. package/demos/js/svgtween.js +204 -0
  91. package/demos/js/tde/accretiondisk.js +418 -0
  92. package/demos/js/tde/blackhole.js +219 -0
  93. package/demos/js/tde/blackholescene.js +209 -0
  94. package/demos/js/tde/config.js +59 -0
  95. package/demos/js/tde/index.js +695 -0
  96. package/demos/js/tde/jets.js +290 -0
  97. package/demos/js/tde/lensedstarfield.js +147 -0
  98. package/demos/js/tde/tdestar.js +317 -0
  99. package/demos/js/tde/tidalstream.js +356 -0
  100. package/demos/js/tde_old/blackhole.obj.js +354 -0
  101. package/demos/js/tde_old/debris.obj.js +791 -0
  102. package/demos/js/tde_old/flare.obj.js +239 -0
  103. package/demos/js/tde_old/index.js +448 -0
  104. package/demos/js/tde_old/star.obj.js +812 -0
  105. package/demos/js/tiles.js +312 -0
  106. package/demos/js/tweendemo.js +79 -0
  107. package/demos/js/visibility.js +102 -0
  108. package/demos/kerr.html +28 -0
  109. package/demos/lavalamp.html +27 -0
  110. package/demos/layouts.html +37 -0
  111. package/demos/logo.svg +4 -0
  112. package/demos/loop.html +84 -0
  113. package/demos/mondrian.html +32 -0
  114. package/demos/og_image.png +0 -0
  115. package/demos/opacity.html +36 -0
  116. package/demos/painter.html +39 -0
  117. package/demos/particles-showcase.html +28 -0
  118. package/demos/particles.html +24 -0
  119. package/demos/patterns.html +33 -0
  120. package/demos/penrose-game.html +31 -0
  121. package/demos/pipeline.html +737 -0
  122. package/demos/scene.html +33 -0
  123. package/demos/scenes.html +96 -0
  124. package/demos/schrodinger.html +27 -0
  125. package/demos/schwarzschild.html +27 -0
  126. package/demos/shapes.html +16 -0
  127. package/demos/space.html +85 -0
  128. package/demos/spacetime.html +27 -0
  129. package/demos/svgtween.html +29 -0
  130. package/demos/tde.html +28 -0
  131. package/demos/tiles.html +28 -0
  132. package/demos/transforms.html +400 -0
  133. package/demos/tween.html +45 -0
  134. package/demos/visibility.html +33 -0
  135. package/disk_example.png +0 -0
  136. package/docs/README.md +222 -0
  137. package/docs/concepts/architecture-overview.md +204 -0
  138. package/docs/concepts/lifecycle.md +255 -0
  139. package/docs/concepts/rendering-pipeline.md +279 -0
  140. package/docs/concepts/tde-zorder.md +106 -0
  141. package/docs/concepts/two-layer-architecture.md +229 -0
  142. package/docs/getting-started/first-game.md +354 -0
  143. package/docs/getting-started/hello-world.md +269 -0
  144. package/docs/getting-started/installation.md +157 -0
  145. package/docs/modules/collision/README.md +453 -0
  146. package/docs/modules/fluent/README.md +1075 -0
  147. package/docs/modules/game/README.md +303 -0
  148. package/docs/modules/isometric-camera.md +210 -0
  149. package/docs/modules/isometric.md +275 -0
  150. package/docs/modules/painter/README.md +328 -0
  151. package/docs/modules/particle/README.md +559 -0
  152. package/docs/modules/shapes/README.md +221 -0
  153. package/docs/modules/shapes/base/euclidian.md +123 -0
  154. package/docs/modules/shapes/base/geometry2d.md +204 -0
  155. package/docs/modules/shapes/base/renderable.md +215 -0
  156. package/docs/modules/shapes/base/shape.md +262 -0
  157. package/docs/modules/shapes/base/transformable.md +243 -0
  158. package/docs/modules/shapes/hierarchy.md +218 -0
  159. package/docs/modules/state/README.md +577 -0
  160. package/docs/modules/util/README.md +99 -0
  161. package/docs/modules/util/camera3d.md +412 -0
  162. package/docs/modules/util/scene3d.md +395 -0
  163. package/index.html +17 -0
  164. package/jsdoc.json +50 -0
  165. package/package.json +55 -0
  166. package/readme.md +599 -0
  167. package/scripts/build-demo.js +69 -0
  168. package/scripts/bundle4llm.js +276 -0
  169. package/scripts/clearconsole.js +48 -0
  170. package/src/collision/collision-system.js +332 -0
  171. package/src/collision/collision.js +303 -0
  172. package/src/collision/index.js +10 -0
  173. package/src/fluent/fluent-game.js +430 -0
  174. package/src/fluent/fluent-go.js +1060 -0
  175. package/src/fluent/fluent-layer.js +152 -0
  176. package/src/fluent/fluent-scene.js +291 -0
  177. package/src/fluent/index.js +98 -0
  178. package/src/fluent/sketch.js +380 -0
  179. package/src/game/game.js +467 -0
  180. package/src/game/index.js +49 -0
  181. package/src/game/objects/go.js +220 -0
  182. package/src/game/objects/imagego.js +30 -0
  183. package/src/game/objects/index.js +54 -0
  184. package/src/game/objects/isometric-scene.js +260 -0
  185. package/src/game/objects/layoutscene.js +549 -0
  186. package/src/game/objects/scene.js +175 -0
  187. package/src/game/objects/scene3d.js +118 -0
  188. package/src/game/objects/text.js +221 -0
  189. package/src/game/objects/wrapper.js +232 -0
  190. package/src/game/pipeline.js +243 -0
  191. package/src/game/ui/button.js +396 -0
  192. package/src/game/ui/cursor.js +93 -0
  193. package/src/game/ui/fps.js +91 -0
  194. package/src/game/ui/index.js +5 -0
  195. package/src/game/ui/togglebutton.js +93 -0
  196. package/src/game/ui/tooltip.js +249 -0
  197. package/src/index.js +25 -0
  198. package/src/io/events.js +20 -0
  199. package/src/io/index.js +86 -0
  200. package/src/io/input.js +70 -0
  201. package/src/io/keys.js +152 -0
  202. package/src/io/mouse.js +61 -0
  203. package/src/io/touch.js +39 -0
  204. package/src/logger/debugtab.js +138 -0
  205. package/src/logger/index.js +3 -0
  206. package/src/logger/loggable.js +47 -0
  207. package/src/logger/logger.js +113 -0
  208. package/src/math/complex.js +37 -0
  209. package/src/math/constants.js +1 -0
  210. package/src/math/fractal.js +1271 -0
  211. package/src/math/gr.js +201 -0
  212. package/src/math/heat.js +202 -0
  213. package/src/math/index.js +12 -0
  214. package/src/math/noise.js +433 -0
  215. package/src/math/orbital.js +191 -0
  216. package/src/math/patterns.js +1339 -0
  217. package/src/math/penrose.js +259 -0
  218. package/src/math/quantum.js +115 -0
  219. package/src/math/random.js +195 -0
  220. package/src/math/tensor.js +1009 -0
  221. package/src/mixins/anchor.js +131 -0
  222. package/src/mixins/draggable.js +72 -0
  223. package/src/mixins/index.js +2 -0
  224. package/src/motion/bezier.js +132 -0
  225. package/src/motion/bounce.js +58 -0
  226. package/src/motion/easing.js +349 -0
  227. package/src/motion/float.js +130 -0
  228. package/src/motion/follow.js +125 -0
  229. package/src/motion/hop.js +52 -0
  230. package/src/motion/index.js +82 -0
  231. package/src/motion/motion.js +1124 -0
  232. package/src/motion/orbit.js +49 -0
  233. package/src/motion/oscillate.js +39 -0
  234. package/src/motion/parabolic.js +141 -0
  235. package/src/motion/patrol.js +147 -0
  236. package/src/motion/pendulum.js +48 -0
  237. package/src/motion/pulse.js +88 -0
  238. package/src/motion/shake.js +83 -0
  239. package/src/motion/spiral.js +144 -0
  240. package/src/motion/spring.js +150 -0
  241. package/src/motion/swing.js +47 -0
  242. package/src/motion/tween.js +92 -0
  243. package/src/motion/tweenetik.js +139 -0
  244. package/src/motion/waypoint.js +210 -0
  245. package/src/painter/index.js +8 -0
  246. package/src/painter/painter.colors.js +331 -0
  247. package/src/painter/painter.effects.js +230 -0
  248. package/src/painter/painter.img.js +229 -0
  249. package/src/painter/painter.js +295 -0
  250. package/src/painter/painter.lines.js +189 -0
  251. package/src/painter/painter.opacity.js +41 -0
  252. package/src/painter/painter.shapes.js +277 -0
  253. package/src/painter/painter.text.js +273 -0
  254. package/src/particle/emitter.js +124 -0
  255. package/src/particle/index.js +11 -0
  256. package/src/particle/particle-system.js +322 -0
  257. package/src/particle/particle.js +71 -0
  258. package/src/particle/updaters.js +170 -0
  259. package/src/shapes/arc.js +43 -0
  260. package/src/shapes/arrow.js +33 -0
  261. package/src/shapes/bezier.js +42 -0
  262. package/src/shapes/circle.js +62 -0
  263. package/src/shapes/clouds.js +56 -0
  264. package/src/shapes/cone.js +219 -0
  265. package/src/shapes/cross.js +70 -0
  266. package/src/shapes/cube.js +244 -0
  267. package/src/shapes/cylinder.js +254 -0
  268. package/src/shapes/diamond.js +48 -0
  269. package/src/shapes/euclidian.js +111 -0
  270. package/src/shapes/figure.js +115 -0
  271. package/src/shapes/geometry.js +220 -0
  272. package/src/shapes/group.js +375 -0
  273. package/src/shapes/heart.js +42 -0
  274. package/src/shapes/hexagon.js +26 -0
  275. package/src/shapes/image.js +192 -0
  276. package/src/shapes/index.js +111 -0
  277. package/src/shapes/line.js +29 -0
  278. package/src/shapes/pattern.js +90 -0
  279. package/src/shapes/pin.js +44 -0
  280. package/src/shapes/poly.js +31 -0
  281. package/src/shapes/prism.js +226 -0
  282. package/src/shapes/rect.js +35 -0
  283. package/src/shapes/renderable.js +333 -0
  284. package/src/shapes/ring.js +26 -0
  285. package/src/shapes/roundrect.js +95 -0
  286. package/src/shapes/shape.js +117 -0
  287. package/src/shapes/slice.js +26 -0
  288. package/src/shapes/sphere.js +314 -0
  289. package/src/shapes/sphere3d.js +537 -0
  290. package/src/shapes/square.js +15 -0
  291. package/src/shapes/star.js +99 -0
  292. package/src/shapes/svg.js +408 -0
  293. package/src/shapes/text.js +553 -0
  294. package/src/shapes/traceable.js +83 -0
  295. package/src/shapes/transform.js +357 -0
  296. package/src/shapes/transformable.js +172 -0
  297. package/src/shapes/triangle.js +26 -0
  298. package/src/sound/index.js +17 -0
  299. package/src/sound/sound.js +473 -0
  300. package/src/sound/synth.analyzer.js +149 -0
  301. package/src/sound/synth.effects.js +207 -0
  302. package/src/sound/synth.envelope.js +59 -0
  303. package/src/sound/synth.js +229 -0
  304. package/src/sound/synth.musical.js +160 -0
  305. package/src/sound/synth.noise.js +85 -0
  306. package/src/sound/synth.oscillators.js +293 -0
  307. package/src/state/index.js +10 -0
  308. package/src/state/state-machine.js +371 -0
  309. package/src/util/camera3d.js +438 -0
  310. package/src/util/index.js +6 -0
  311. package/src/util/isometric-camera.js +235 -0
  312. package/src/util/layout.js +317 -0
  313. package/src/util/position.js +147 -0
  314. package/src/util/tasks.js +47 -0
  315. package/src/util/zindex.js +287 -0
  316. package/src/webgl/index.js +9 -0
  317. package/src/webgl/shaders/sphere-shaders.js +994 -0
  318. package/src/webgl/webgl-renderer.js +388 -0
  319. package/tde.png +0 -0
  320. package/test/math/orbital.test.js +61 -0
  321. package/test/math/tensor.test.js +114 -0
  322. package/test/particle/emitter.test.js +204 -0
  323. package/test/particle/particle-system.test.js +310 -0
  324. package/test/particle/particle.test.js +116 -0
  325. package/test/particle/updaters.test.js +386 -0
  326. package/test/setup.js +120 -0
  327. package/test/shapes/euclidian.test.js +44 -0
  328. package/test/shapes/geometry.test.js +86 -0
  329. package/test/shapes/group.test.js +86 -0
  330. package/test/shapes/rectangle.test.js +64 -0
  331. package/test/shapes/transform.test.js +379 -0
  332. package/test/util/camera3d.test.js +428 -0
  333. package/test/util/scene3d.test.js +352 -0
  334. package/types/collision.d.ts +249 -0
  335. package/types/common.d.ts +155 -0
  336. package/types/game.d.ts +497 -0
  337. package/types/index.d.ts +309 -0
  338. package/types/io.d.ts +188 -0
  339. package/types/logger.d.ts +127 -0
  340. package/types/math.d.ts +268 -0
  341. package/types/mixins.d.ts +92 -0
  342. package/types/motion.d.ts +678 -0
  343. package/types/painter.d.ts +378 -0
  344. package/types/shapes.d.ts +864 -0
  345. package/types/sound.d.ts +672 -0
  346. package/types/state.d.ts +251 -0
  347. package/types/util.d.ts +253 -0
  348. package/vite.config.js +50 -0
  349. package/vitest.config.js +13 -0
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * bundle4llm
5
+ * -------------
6
+ * Create a comprehensive, single-file, import/export-free JavaScript bundle
7
+ * ideal for feeding into LLMs without worrying about dependencies.
8
+ *
9
+ * Features:
10
+ * - Recursively resolves and includes all exported modules
11
+ * - Flattens directory structure
12
+ * - Orders files by export order in index files
13
+ * - Sanitizes code based on selected preset (defaults to ES2020)
14
+ * - Skips index.js files from output (used only for ordering)
15
+ * - Ensures comprehensive module inclusion
16
+ * - Supports verbose logging with --verbose or -v
17
+ * - Optional comment stripping with --strip-comments
18
+ * - Accurate token estimation for various LLM models
19
+ *
20
+ * Usage:
21
+ * bundle4llm --src ./src --out ./dist --file bundle.js --preset es2020 --strip-comments --model claude -v
22
+ */
23
+
24
+ import fs from 'fs';
25
+ import path from 'path';
26
+ import { argv } from 'process';
27
+
28
+ const args = Object.fromEntries(
29
+ argv.slice(2).map(arg => {
30
+ const [key, val] = arg.replace(/^--?/, '').split('=');
31
+ return [key, val ?? true];
32
+ })
33
+ );
34
+
35
+ const srcDir = path.resolve(args.src || './src');
36
+ const outDir = path.resolve(args.out || './dist');
37
+ const outFile = args.file || 'llm-bundle.js';
38
+ const verbose = args.v || args.verbose;
39
+ const stripComments = args['strip-comments'] || false;
40
+ const preset = args.preset || 'es2020';
41
+ const model = args.model || 'claude'; // Default to Claude model
42
+
43
+ // Deep file collection with recursive export resolution
44
+ function collectModules(indexPath, fileMap = {}, visited = new Set(), depth = 0) {
45
+ // Prevent infinite recursion
46
+ if (depth > 10) return fileMap;
47
+
48
+ // Prevent revisiting the same index file
49
+ if (visited.has(indexPath)) return fileMap;
50
+ visited.add(indexPath);
51
+
52
+ // Read index file contents
53
+ if (!fs.existsSync(indexPath)) return fileMap;
54
+ const content = fs.readFileSync(indexPath, 'utf-8');
55
+
56
+ // Find all export declarations
57
+ const exportMatches = [
58
+ ...content.matchAll(/export\s+(?:\*|{[^}]*})\s*from\s+['"](.+)['"]/g),
59
+ ...content.matchAll(/export\s+{[^}]*}\s*from\s+['"](.+)['"]/g)
60
+ ];
61
+
62
+ // Resolve and process each exported module
63
+ for (const match of exportMatches) {
64
+ const rawPath = match[1];
65
+ const modulePath = rawPath.startsWith('.')
66
+ ? path.resolve(path.dirname(indexPath), rawPath)
67
+ : path.resolve(srcDir, rawPath);
68
+
69
+ // Normalize path
70
+ const normalizedPath = modulePath.replace(/\.js$/, '');
71
+ const jsPath = normalizedPath + '.js';
72
+
73
+ // If it's a directory, look for its index file
74
+ const indexInDir = path.join(normalizedPath, 'index.js');
75
+
76
+ if (fs.existsSync(jsPath) && !fileMap[jsPath]) {
77
+ // Add the module file
78
+ fileMap[jsPath] = jsPath;
79
+
80
+ // If it's a directory with an index, recursively collect its exports
81
+ if (fs.existsSync(indexInDir)) {
82
+ collectModules(indexInDir, fileMap, visited, depth + 1);
83
+ }
84
+ } else if (fs.existsSync(indexInDir)) {
85
+ // Recursively collect exports from subdirectory index
86
+ collectModules(indexInDir, fileMap, visited, depth + 1);
87
+ }
88
+ }
89
+
90
+ return fileMap;
91
+ }
92
+
93
+ // Order modules based on index file export order
94
+ function orderModules(srcDir) {
95
+ const indexPath = path.join(srcDir, 'index.js');
96
+ const fileMap = collectModules(indexPath);
97
+
98
+ // Convert to array and sort
99
+ return Object.values(fileMap)
100
+ .filter(filePath => !filePath.endsWith('index.js'));
101
+ }
102
+
103
+ // Sanitization presets
104
+ const sanitizationPresets = {
105
+ // ES2020 sanitization - strips imports and optionally comments
106
+ es2020: (code, options = {}) => {
107
+ let result = code;
108
+
109
+ // Strip import statements
110
+ result = result
111
+ .split('\n')
112
+ .filter(line => !/^\s*import\b/.test(line))
113
+ .join('\n');
114
+
115
+ // Optionally strip comments
116
+ if (options.stripComments) {
117
+ // Strip block comments
118
+ result = result.replace(/\/\*[\s\S]*?\*\//g, '');
119
+ // Strip line comments
120
+ result = result.replace(/\/\/.*$/gm, '');
121
+ }
122
+
123
+ return result.trim();
124
+ },
125
+
126
+ // ES2015 sanitization - more conservative approach
127
+ es2015: (code, options = {}) => {
128
+ let result = code;
129
+
130
+ // Strip import statements
131
+ result = result
132
+ .split('\n')
133
+ .filter(line => !/^\s*import\b/.test(line))
134
+ .join('\n');
135
+
136
+ // Optionally strip comments
137
+ if (options.stripComments) {
138
+ // Strip block comments
139
+ result = result.replace(/\/\*[\s\S]*?\*\//g, '');
140
+ // Strip line comments
141
+ result = result.replace(/\/\/.*$/gm, '');
142
+ }
143
+
144
+ return result.trim();
145
+ },
146
+
147
+ // Minimal sanitization - only strips imports
148
+ minimal: (code, options = {}) => {
149
+ let result = code
150
+ .split('\n')
151
+ .filter(line => !/^\s*import\b/.test(line))
152
+ .join('\n');
153
+
154
+ return result.trim();
155
+ }
156
+ };
157
+
158
+ // Generic sanitize function that uses the selected preset
159
+ function sanitizeCode(code, presetName, options = {}) {
160
+ const sanitizer = sanitizationPresets[presetName] || sanitizationPresets.es2020;
161
+ return sanitizer(code, options);
162
+ }
163
+
164
+ /**
165
+ * Improved token estimation function supporting multiple LLM models
166
+ *
167
+ * @param {string} text - The text to estimate tokens for
168
+ * @param {string} model - Model identifier ('claude', 'gpt3', 'gpt4', etc.)
169
+ * @return {number} Estimated token count
170
+ */
171
+ function estimateTokens(text, model = 'claude') {
172
+ // Basic character counting
173
+ const charCount = text.length;
174
+ const wordCount = text.split(/\s+/).filter(Boolean).length;
175
+
176
+ // Model-specific token ratio estimations
177
+ const modelRatios = {
178
+ // Anthropic Claude models
179
+ 'claude': {
180
+ charRatio: 3.5, // ~3.5 chars per token for Claude
181
+ wordRatio: 0.75, // ~0.75 words per token for Claude
182
+ },
183
+ // OpenAI models
184
+ 'gpt3': {
185
+ charRatio: 4.0, // ~4 chars per token for GPT-3
186
+ wordRatio: 0.75, // ~0.75 words per token for GPT-3
187
+ },
188
+ 'gpt4': {
189
+ charRatio: 3.8, // ~3.8 chars per token for GPT-4
190
+ wordRatio: 0.7, // ~0.7 words per token for GPT-4
191
+ },
192
+ // Default fallback
193
+ 'default': {
194
+ charRatio: 4.0,
195
+ wordRatio: 0.75,
196
+ }
197
+ };
198
+
199
+ // Get the appropriate ratio for the model
200
+ const ratio = modelRatios[model] || modelRatios.default;
201
+
202
+ // Estimate based on character count and word count, using an average of both methods
203
+ const charBasedEstimate = Math.ceil(charCount / ratio.charRatio);
204
+ const wordBasedEstimate = Math.ceil(wordCount / ratio.wordRatio);
205
+
206
+ // Language features that tend to increase token count
207
+ const codeFeatures = {
208
+ symbols: (text.match(/[{}[\]()<>:;,."'`~!@#$%^&*+=|\\/?-]/g) || []).length,
209
+ indentation: (text.match(/^ +/gm) || []).length,
210
+ camelCase: (text.match(/[a-z][A-Z]/g) || []).length,
211
+ };
212
+
213
+ // Adjustment for code-specific features (symbols, indentation, camelCase)
214
+ const codeAdjustment = codeFeatures.symbols * 0.1 +
215
+ codeFeatures.indentation * 0.05 +
216
+ codeFeatures.camelCase * 0.2;
217
+
218
+ // Calculate weighted average with more weight on char-based for code
219
+ const weightedEstimate = (charBasedEstimate * 0.7) + (wordBasedEstimate * 0.3);
220
+
221
+ // Add code-specific adjustment
222
+ const finalEstimate = Math.ceil(weightedEstimate + codeAdjustment);
223
+
224
+ return finalEstimate;
225
+ }
226
+
227
+ // Main bundling function
228
+ function bundleLLMBuild(srcDir, outDir, outFile, options = {}) {
229
+ const orderedFiles = orderModules(srcDir);
230
+ const { verbose, stripComments, preset, model } = options;
231
+
232
+ let output = '/**\n * bundle4llm Output\n';
233
+ output += ` * Generated: ${new Date().toISOString()}\n`;
234
+ output += ` * Source: ${srcDir}\n`;
235
+ output += ` * Preset: ${preset}\n`;
236
+ if (stripComments) output += ` * Comments: Stripped\n`;
237
+ output += ` */\n\n`;
238
+
239
+ let currentDir = '';
240
+ for (const file of orderedFiles) {
241
+ const fileDir = path.dirname(file).replace(srcDir, '');
242
+ if (fileDir !== currentDir) {
243
+ currentDir = fileDir;
244
+ output += `\n// =========================================\n`;
245
+ output += `// DIRECTORY: ${currentDir || '/'}\n`;
246
+ output += `// =========================================\n\n`;
247
+ }
248
+
249
+ let contents = fs.readFileSync(file, 'utf-8');
250
+ contents = sanitizeCode(contents, preset, { stripComments });
251
+
252
+ if (verbose) console.log('📦 Including:', file);
253
+ output += contents + '\n\n';
254
+ }
255
+
256
+ // Ensure output directory exists
257
+ fs.mkdirSync(outDir, { recursive: true });
258
+
259
+ // Write the bundled file
260
+ const fullOutputPath = path.join(outDir, outFile);
261
+ fs.writeFileSync(fullOutputPath, output);
262
+
263
+ const totalTokens = estimateTokens(output, model).toLocaleString();
264
+ console.log(`✅ Created LLM build → ${fullOutputPath}`);
265
+ console.log(`📊 Total modules included: ${orderedFiles.length}`);
266
+ console.log(`🔧 Preset: ${preset}${stripComments ? ', comments stripped' : ''}`);
267
+ console.log(`🔍 Estimated tokens (${model}): ${totalTokens}`);
268
+ }
269
+
270
+ // Execute the bundle
271
+ bundleLLMBuild(srcDir, outDir, outFile, {
272
+ verbose,
273
+ stripComments,
274
+ preset,
275
+ model
276
+ });
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const targetDir = process.argv[2] || "./src"; // default to ./src
7
+
8
+ function isLineCommented(line) {
9
+ return /^\s*\/\/.*console\.log/.test(line);
10
+ }
11
+
12
+ function isInsideBlockComment(lines, index) {
13
+ for (let i = index; i >= 0; i--) {
14
+ if (lines[i].includes("*/")) break;
15
+ if (lines[i].includes("/*")) return true;
16
+ }
17
+ return false;
18
+ }
19
+
20
+ function processFile(filePath) {
21
+ const content = fs.readFileSync(filePath, "utf8");
22
+ const lines = content.split("\n");
23
+ const modified = lines.map((line, i) => {
24
+ if (
25
+ line.includes("console.log") &&
26
+ !isLineCommented(line) &&
27
+ !isInsideBlockComment(lines, i)
28
+ ) {
29
+ return line.replace(/(.*?)(console\.log.*)/, "$1// $2");
30
+ }
31
+ return line;
32
+ });
33
+ fs.writeFileSync(filePath, modified.join("\n"), "utf8");
34
+ console.log(`✔ Commented console.log in: ${filePath}`);
35
+ }
36
+
37
+ function walkDir(dir) {
38
+ fs.readdirSync(dir).forEach((file) => {
39
+ const fullPath = path.join(dir, file);
40
+ if (fs.statSync(fullPath).isDirectory()) {
41
+ walkDir(fullPath);
42
+ } else if (/\.(js|ts|jsx|tsx)$/.test(fullPath)) {
43
+ processFile(fullPath);
44
+ }
45
+ });
46
+ }
47
+
48
+ walkDir(targetDir);
@@ -0,0 +1,332 @@
1
+ import { Collision } from "./collision.js";
2
+
3
+ /**
4
+ * CollisionSystem - Manages collision groups and detection
5
+ *
6
+ * Provides an organized way to manage multiple groups of collidable objects
7
+ * and efficiently check collisions between them.
8
+ *
9
+ * @example
10
+ * const collisions = new CollisionSystem();
11
+ *
12
+ * // Register groups
13
+ * collisions.createGroup('players');
14
+ * collisions.createGroup('enemies');
15
+ * collisions.createGroup('bullets');
16
+ *
17
+ * // Add objects to groups
18
+ * collisions.add('players', player);
19
+ * collisions.add('enemies', alien);
20
+ * collisions.add('bullets', bullet);
21
+ *
22
+ * // Set up collision handlers
23
+ * collisions.onCollision('bullets', 'enemies', (bullet, enemy) => {
24
+ * bullet.destroy();
25
+ * enemy.takeDamage();
26
+ * });
27
+ *
28
+ * // Check collisions each frame
29
+ * collisions.update();
30
+ */
31
+ export class CollisionSystem {
32
+ constructor() {
33
+ /** @type {Map<string, Set<Object>>} */
34
+ this.groups = new Map();
35
+
36
+ /** @type {Array<Object>} Collision pair definitions */
37
+ this.pairs = [];
38
+
39
+ /** @type {boolean} Whether to use quadtree optimization */
40
+ this.useQuadtree = false;
41
+ }
42
+
43
+ /**
44
+ * Create a new collision group
45
+ *
46
+ * @param {string} name - Unique group name
47
+ * @returns {CollisionSystem} this for chaining
48
+ */
49
+ createGroup(name) {
50
+ if (!this.groups.has(name)) {
51
+ this.groups.set(name, new Set());
52
+ }
53
+ return this;
54
+ }
55
+
56
+ /**
57
+ * Add an object to a collision group
58
+ * Object must have either getBounds() method or bounds property
59
+ *
60
+ * @param {string} groupName - Group to add to
61
+ * @param {Object} obj - Object with getBounds() or bounds property
62
+ * @returns {CollisionSystem} this for chaining
63
+ */
64
+ add(groupName, obj) {
65
+ const group = this.groups.get(groupName);
66
+ if (!group) {
67
+ throw new Error(`Collision group '${groupName}' does not exist. Call createGroup('${groupName}') first.`);
68
+ }
69
+ group.add(obj);
70
+ return this;
71
+ }
72
+
73
+ /**
74
+ * Remove an object from a collision group
75
+ *
76
+ * @param {string} groupName - Group to remove from
77
+ * @param {Object} obj - Object to remove
78
+ * @returns {boolean} True if object was in the group
79
+ */
80
+ remove(groupName, obj) {
81
+ const group = this.groups.get(groupName);
82
+ if (!group) return false;
83
+ return group.delete(obj);
84
+ }
85
+
86
+ /**
87
+ * Remove an object from all groups
88
+ *
89
+ * @param {Object} obj - Object to remove
90
+ */
91
+ removeFromAll(obj) {
92
+ for (const group of this.groups.values()) {
93
+ group.delete(obj);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Clear all objects from a group
99
+ *
100
+ * @param {string} groupName - Group to clear
101
+ */
102
+ clearGroup(groupName) {
103
+ const group = this.groups.get(groupName);
104
+ if (group) {
105
+ group.clear();
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Clear all groups
111
+ */
112
+ clearAll() {
113
+ for (const group of this.groups.values()) {
114
+ group.clear();
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Get all objects in a group
120
+ *
121
+ * @param {string} groupName - Group name
122
+ * @returns {Array<Object>} Array of objects in the group
123
+ */
124
+ getGroup(groupName) {
125
+ const group = this.groups.get(groupName);
126
+ return group ? Array.from(group) : [];
127
+ }
128
+
129
+ /**
130
+ * Register a collision callback between two groups
131
+ *
132
+ * @param {string} groupA - First group name
133
+ * @param {string} groupB - Second group name
134
+ * @param {Function} callback - Called with (objA, objB) on collision
135
+ * @param {Object} [options={}] - Additional options
136
+ * @param {boolean} [options.once=false] - If true, only trigger once per pair per frame
137
+ * @returns {CollisionSystem} this for chaining
138
+ */
139
+ onCollision(groupA, groupB, callback, options = {}) {
140
+ this.pairs.push({
141
+ groupA,
142
+ groupB,
143
+ callback,
144
+ once: options.once ?? false,
145
+ });
146
+ return this;
147
+ }
148
+
149
+ /**
150
+ * Remove all collision callbacks for a pair of groups
151
+ *
152
+ * @param {string} groupA - First group name
153
+ * @param {string} groupB - Second group name
154
+ */
155
+ offCollision(groupA, groupB) {
156
+ this.pairs = this.pairs.filter(
157
+ (pair) => !(pair.groupA === groupA && pair.groupB === groupB)
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Check and handle all registered collision pairs
163
+ * Call this each frame in your update loop
164
+ */
165
+ update() {
166
+ for (const pair of this.pairs) {
167
+ this._checkPair(pair);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Check collisions between two specific groups (without callbacks)
173
+ * Returns array of colliding pairs
174
+ *
175
+ * @param {string} groupA - First group name
176
+ * @param {string} groupB - Second group name
177
+ * @returns {Array<Array>} Array of [objA, objB] colliding pairs
178
+ */
179
+ check(groupA, groupB) {
180
+ const setA = this.groups.get(groupA);
181
+ const setB = this.groups.get(groupB);
182
+ if (!setA || !setB) return [];
183
+
184
+ const collisions = [];
185
+
186
+ for (const objA of setA) {
187
+ if (!this._isActive(objA)) continue;
188
+ const boundsA = this._getBounds(objA);
189
+ if (!boundsA) continue;
190
+
191
+ for (const objB of setB) {
192
+ if (objA === objB) continue;
193
+ if (!this._isActive(objB)) continue;
194
+
195
+ const boundsB = this._getBounds(objB);
196
+ if (!boundsB) continue;
197
+
198
+ if (Collision.rectRect(boundsA, boundsB)) {
199
+ collisions.push([objA, objB]);
200
+ }
201
+ }
202
+ }
203
+
204
+ return collisions;
205
+ }
206
+
207
+ /**
208
+ * Check if an object collides with any object in a group
209
+ *
210
+ * @param {Object} obj - Object to test
211
+ * @param {string} groupName - Group to test against
212
+ * @returns {Object|null} First colliding object, or null
213
+ */
214
+ checkAgainstGroup(obj, groupName) {
215
+ const group = this.groups.get(groupName);
216
+ if (!group) return null;
217
+
218
+ const boundsA = this._getBounds(obj);
219
+ if (!boundsA) return null;
220
+
221
+ for (const other of group) {
222
+ if (obj === other) continue;
223
+ if (!this._isActive(other)) continue;
224
+
225
+ const boundsB = this._getBounds(other);
226
+ if (!boundsB) continue;
227
+
228
+ if (Collision.rectRect(boundsA, boundsB)) {
229
+ return other;
230
+ }
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Check if an object collides with any object in a group
238
+ * Returns all colliding objects
239
+ *
240
+ * @param {Object} obj - Object to test
241
+ * @param {string} groupName - Group to test against
242
+ * @returns {Array<Object>} All colliding objects
243
+ */
244
+ checkAllAgainstGroup(obj, groupName) {
245
+ const group = this.groups.get(groupName);
246
+ if (!group) return [];
247
+
248
+ const boundsA = this._getBounds(obj);
249
+ if (!boundsA) return [];
250
+
251
+ const collisions = [];
252
+
253
+ for (const other of group) {
254
+ if (obj === other) continue;
255
+ if (!this._isActive(other)) continue;
256
+
257
+ const boundsB = this._getBounds(other);
258
+ if (!boundsB) continue;
259
+
260
+ if (Collision.rectRect(boundsA, boundsB)) {
261
+ collisions.push(other);
262
+ }
263
+ }
264
+
265
+ return collisions;
266
+ }
267
+
268
+ /**
269
+ * @private
270
+ */
271
+ _checkPair(pair) {
272
+ const setA = this.groups.get(pair.groupA);
273
+ const setB = this.groups.get(pair.groupB);
274
+ if (!setA || !setB) return;
275
+
276
+ for (const objA of setA) {
277
+ if (!this._isActive(objA)) continue;
278
+ const boundsA = this._getBounds(objA);
279
+ if (!boundsA) continue;
280
+
281
+ for (const objB of setB) {
282
+ if (objA === objB) continue;
283
+ if (!this._isActive(objB)) continue;
284
+
285
+ const boundsB = this._getBounds(objB);
286
+ if (!boundsB) continue;
287
+
288
+ if (Collision.rectRect(boundsA, boundsB)) {
289
+ pair.callback(objA, objB);
290
+
291
+ // If 'once' option, skip remaining checks for this objA
292
+ if (pair.once) break;
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Get bounds from an object (supports getBounds() method or bounds property)
300
+ * @private
301
+ */
302
+ _getBounds(obj) {
303
+ if (typeof obj.getBounds === "function") {
304
+ return obj.getBounds();
305
+ }
306
+ if (obj.bounds) {
307
+ return obj.bounds;
308
+ }
309
+ // Fallback: try to construct bounds from x, y, width, height
310
+ if (obj.x !== undefined && obj.y !== undefined) {
311
+ return {
312
+ x: obj.x - (obj.width || 0) / 2,
313
+ y: obj.y - (obj.height || 0) / 2,
314
+ width: obj.width || 0,
315
+ height: obj.height || 0,
316
+ };
317
+ }
318
+ return null;
319
+ }
320
+
321
+ /**
322
+ * Check if an object is still active (for filtering dead objects)
323
+ * @private
324
+ */
325
+ _isActive(obj) {
326
+ // Support common patterns for "active" objects
327
+ if (obj.active === false) return false;
328
+ if (obj.destroyed === true) return false;
329
+ if (obj.alive === false) return false;
330
+ return true;
331
+ }
332
+ }