@rune-kit/rune 2.10.0 → 2.12.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 (240) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +65 -6
  3. package/commands/rune.md +168 -168
  4. package/compiler/__tests__/detect-invariants.test.js +136 -0
  5. package/compiler/__tests__/doctor-mesh.test.js +229 -0
  6. package/compiler/__tests__/hook-dispatch.test.js +91 -0
  7. package/compiler/__tests__/hooks-antigravity.test.js +118 -0
  8. package/compiler/__tests__/hooks-cursor.test.js +139 -0
  9. package/compiler/__tests__/hooks-install.test.js +305 -0
  10. package/compiler/__tests__/hooks-merge.test.js +204 -0
  11. package/compiler/__tests__/hooks-tiers.test.js +519 -0
  12. package/compiler/__tests__/hooks-windsurf.test.js +115 -0
  13. package/compiler/__tests__/inject-claude-md.test.js +152 -0
  14. package/compiler/__tests__/load-invariants.test.js +408 -0
  15. package/compiler/__tests__/onboard-invariants.test.js +240 -0
  16. package/compiler/adapters/hooks/antigravity.js +140 -0
  17. package/compiler/adapters/hooks/claude.js +166 -0
  18. package/compiler/adapters/hooks/cursor.js +191 -0
  19. package/compiler/adapters/hooks/index.js +82 -0
  20. package/compiler/adapters/hooks/tier-emitter.js +182 -0
  21. package/compiler/adapters/hooks/windsurf.js +202 -0
  22. package/compiler/bin/rune.js +196 -6
  23. package/compiler/commands/hook-dispatch.js +87 -0
  24. package/compiler/commands/hooks/install.js +120 -0
  25. package/compiler/commands/hooks/merge.js +211 -0
  26. package/compiler/commands/hooks/presets.js +116 -0
  27. package/compiler/commands/hooks/status.js +112 -0
  28. package/compiler/commands/hooks/tiers.js +221 -0
  29. package/compiler/commands/hooks/uninstall.js +94 -0
  30. package/compiler/doctor.js +236 -0
  31. package/contexts/dev.md +34 -34
  32. package/contexts/research.md +43 -43
  33. package/contexts/review.md +55 -55
  34. package/extensions/ai-ml/PACK.md +88 -88
  35. package/extensions/ai-ml/skills/ai-agents.md +172 -172
  36. package/extensions/ai-ml/skills/code-sandbox.md +187 -187
  37. package/extensions/ai-ml/skills/deep-research.md +146 -146
  38. package/extensions/ai-ml/skills/embedding-search.md +66 -66
  39. package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
  40. package/extensions/ai-ml/skills/llm-architect.md +125 -125
  41. package/extensions/ai-ml/skills/llm-integration.md +64 -64
  42. package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
  43. package/extensions/ai-ml/skills/rag-patterns.md +66 -66
  44. package/extensions/ai-ml/skills/web-extraction.md +114 -114
  45. package/extensions/analytics/PACK.md +92 -92
  46. package/extensions/analytics/skills/ab-testing.md +72 -72
  47. package/extensions/analytics/skills/dashboard-patterns.md +83 -83
  48. package/extensions/analytics/skills/data-validation.md +68 -68
  49. package/extensions/analytics/skills/funnel-analysis.md +81 -81
  50. package/extensions/analytics/skills/sql-patterns.md +57 -57
  51. package/extensions/analytics/skills/statistical-analysis.md +79 -79
  52. package/extensions/analytics/skills/tracking-setup.md +71 -71
  53. package/extensions/backend/PACK.md +104 -104
  54. package/extensions/backend/skills/api-patterns.md +84 -84
  55. package/extensions/backend/skills/async-pipeline.md +193 -193
  56. package/extensions/backend/skills/auth-patterns.md +97 -97
  57. package/extensions/backend/skills/background-jobs.md +133 -133
  58. package/extensions/backend/skills/caching-patterns.md +108 -108
  59. package/extensions/backend/skills/cli-generation.md +133 -133
  60. package/extensions/backend/skills/database-patterns.md +87 -87
  61. package/extensions/backend/skills/middleware-patterns.md +104 -104
  62. package/extensions/chrome-ext/PACK.md +93 -93
  63. package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
  64. package/extensions/chrome-ext/skills/cws-publish.md +104 -104
  65. package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
  66. package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
  67. package/extensions/chrome-ext/skills/ext-storage.md +133 -133
  68. package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
  69. package/extensions/content/PACK.md +96 -96
  70. package/extensions/content/skills/blog-patterns.md +88 -88
  71. package/extensions/content/skills/cms-integration.md +131 -131
  72. package/extensions/content/skills/content-scoring.md +107 -107
  73. package/extensions/content/skills/i18n.md +83 -83
  74. package/extensions/content/skills/mdx-authoring.md +137 -137
  75. package/extensions/content/skills/reference.md +1014 -1014
  76. package/extensions/content/skills/seo-patterns.md +67 -67
  77. package/extensions/content/skills/video-repurpose.md +153 -153
  78. package/extensions/devops/PACK.md +101 -101
  79. package/extensions/devops/skills/chaos-testing.md +67 -67
  80. package/extensions/devops/skills/ci-cd.md +75 -75
  81. package/extensions/devops/skills/docker.md +58 -58
  82. package/extensions/devops/skills/edge-serverless.md +163 -163
  83. package/extensions/devops/skills/infra-as-code.md +158 -158
  84. package/extensions/devops/skills/kubernetes.md +110 -110
  85. package/extensions/devops/skills/monitoring.md +57 -57
  86. package/extensions/devops/skills/server-setup.md +64 -64
  87. package/extensions/devops/skills/ssl-domain.md +42 -42
  88. package/extensions/ecommerce/PACK.md +116 -116
  89. package/extensions/ecommerce/skills/cart-system.md +79 -79
  90. package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
  91. package/extensions/ecommerce/skills/order-management.md +126 -126
  92. package/extensions/ecommerce/skills/payment-integration.md +472 -472
  93. package/extensions/ecommerce/skills/shopify-dev.md +69 -69
  94. package/extensions/ecommerce/skills/subscription-billing.md +93 -93
  95. package/extensions/ecommerce/skills/tax-compliance.md +117 -117
  96. package/extensions/gamedev/PACK.md +142 -142
  97. package/extensions/gamedev/skills/asset-pipeline.md +74 -74
  98. package/extensions/gamedev/skills/audio-system.md +129 -129
  99. package/extensions/gamedev/skills/camera-system.md +87 -87
  100. package/extensions/gamedev/skills/ecs.md +98 -98
  101. package/extensions/gamedev/skills/game-loops.md +72 -72
  102. package/extensions/gamedev/skills/input-system.md +199 -199
  103. package/extensions/gamedev/skills/multiplayer.md +180 -180
  104. package/extensions/gamedev/skills/particles.md +105 -105
  105. package/extensions/gamedev/skills/physics-engine.md +89 -89
  106. package/extensions/gamedev/skills/scene-management.md +146 -146
  107. package/extensions/gamedev/skills/threejs-patterns.md +90 -90
  108. package/extensions/gamedev/skills/webgl.md +71 -71
  109. package/extensions/mobile/PACK.md +106 -106
  110. package/extensions/mobile/skills/app-store-connect.md +152 -152
  111. package/extensions/mobile/skills/app-store-prep.md +66 -66
  112. package/extensions/mobile/skills/deep-linking.md +109 -109
  113. package/extensions/mobile/skills/flutter.md +60 -60
  114. package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
  115. package/extensions/mobile/skills/native-bridge.md +66 -66
  116. package/extensions/mobile/skills/ota-updates.md +97 -97
  117. package/extensions/mobile/skills/push-notifications.md +111 -111
  118. package/extensions/mobile/skills/react-native.md +82 -82
  119. package/extensions/saas/PACK.md +116 -116
  120. package/extensions/saas/skills/billing-integration.md +200 -200
  121. package/extensions/saas/skills/feature-flags.md +130 -130
  122. package/extensions/saas/skills/multi-tenant.md +103 -103
  123. package/extensions/saas/skills/onboarding-flow.md +139 -139
  124. package/extensions/saas/skills/subscription-flow.md +95 -95
  125. package/extensions/saas/skills/team-management.md +144 -144
  126. package/extensions/security/PACK.md +99 -99
  127. package/extensions/security/skills/api-security.md +140 -140
  128. package/extensions/security/skills/compliance.md +68 -68
  129. package/extensions/security/skills/owasp-audit.md +64 -64
  130. package/extensions/security/skills/pentest-patterns.md +77 -77
  131. package/extensions/security/skills/secret-mgmt.md +65 -65
  132. package/extensions/security/skills/supply-chain.md +65 -65
  133. package/extensions/trading/PACK.md +80 -80
  134. package/extensions/trading/skills/chart-components.md +55 -55
  135. package/extensions/trading/skills/experiment-loop.md +125 -125
  136. package/extensions/trading/skills/fintech-patterns.md +47 -47
  137. package/extensions/trading/skills/indicator-library.md +58 -58
  138. package/extensions/trading/skills/quant-analysis.md +111 -111
  139. package/extensions/trading/skills/realtime-data.md +58 -58
  140. package/extensions/trading/skills/trade-logic.md +104 -104
  141. package/extensions/ui/PACK.md +130 -130
  142. package/extensions/ui/skills/a11y-audit.md +91 -91
  143. package/extensions/ui/skills/animation-patterns.md +127 -127
  144. package/extensions/ui/skills/component-patterns.md +100 -100
  145. package/extensions/ui/skills/design-decision.md +108 -108
  146. package/extensions/ui/skills/design-system.md +68 -68
  147. package/extensions/ui/skills/landing-patterns.md +155 -155
  148. package/extensions/ui/skills/palette-picker.md +173 -173
  149. package/extensions/ui/skills/react-health.md +90 -90
  150. package/extensions/ui/skills/type-system.md +125 -125
  151. package/extensions/ui/skills/web-vitals.md +153 -153
  152. package/extensions/zalo/PACK.md +145 -145
  153. package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
  154. package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
  155. package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
  156. package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
  157. package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
  158. package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
  159. package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
  160. package/hooks/auto-format/index.cjs +48 -48
  161. package/hooks/hooks.json +111 -111
  162. package/hooks/post-session-reflect/index.cjs +189 -189
  163. package/hooks/pre-compact/index.cjs +95 -95
  164. package/hooks/run-hook.cmd +1 -1
  165. package/hooks/secrets-scan/index.cjs +100 -100
  166. package/hooks/session-start/index.cjs +71 -71
  167. package/hooks/typecheck/index.cjs +65 -65
  168. package/package.json +63 -63
  169. package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
  170. package/references/ui-pro-max-data/charts.csv +26 -26
  171. package/references/ui-pro-max-data/colors.csv +161 -161
  172. package/references/ui-pro-max-data/styles.csv +68 -68
  173. package/references/ui-pro-max-data/typography.csv +74 -74
  174. package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
  175. package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
  176. package/skills/adversary/SKILL.md +283 -283
  177. package/skills/asset-creator/SKILL.md +157 -157
  178. package/skills/audit/SKILL.md +147 -2
  179. package/skills/autopsy/SKILL.md +335 -335
  180. package/skills/ba/SKILL.md +85 -1
  181. package/skills/brainstorm/SKILL.md +380 -342
  182. package/skills/browser-pilot/SKILL.md +169 -168
  183. package/skills/constraint-check/SKILL.md +165 -165
  184. package/skills/context-engine/SKILL.md +408 -404
  185. package/skills/cook/SKILL.md +917 -863
  186. package/skills/db/SKILL.md +273 -273
  187. package/skills/debug/SKILL.md +465 -465
  188. package/skills/dependency-doctor/SKILL.md +265 -235
  189. package/skills/deploy/SKILL.md +274 -231
  190. package/skills/design/DESIGN-REFERENCE.md +365 -365
  191. package/skills/design/SKILL.md +590 -589
  192. package/skills/doc-processor/SKILL.md +254 -254
  193. package/skills/docs/SKILL.md +374 -374
  194. package/skills/docs-seeker/SKILL.md +178 -177
  195. package/skills/fix/SKILL.md +332 -330
  196. package/skills/git/SKILL.md +339 -339
  197. package/skills/hallucination-guard/SKILL.md +220 -219
  198. package/skills/incident/SKILL.md +254 -253
  199. package/skills/integrity-check/SKILL.md +169 -169
  200. package/skills/journal/SKILL.md +241 -240
  201. package/skills/launch/SKILL.md +344 -344
  202. package/skills/logic-guardian/SKILL.md +269 -251
  203. package/skills/marketing/SKILL.md +351 -289
  204. package/skills/mcp-builder/SKILL.md +425 -425
  205. package/skills/neural-memory/SKILL.md +359 -362
  206. package/skills/onboard/SKILL.md +432 -403
  207. package/skills/onboard/references/invariants-template.md +76 -0
  208. package/skills/onboard/scripts/detect-invariants.js +439 -0
  209. package/skills/onboard/scripts/inject-claude-md.js +150 -0
  210. package/skills/onboard/scripts/onboard-invariants.js +194 -0
  211. package/skills/perf/SKILL.md +347 -346
  212. package/skills/plan/SKILL.md +435 -428
  213. package/skills/preflight/SKILL.md +415 -415
  214. package/skills/problem-solver/SKILL.md +380 -284
  215. package/skills/rescue/SKILL.md +474 -474
  216. package/skills/research/SKILL.md +4 -0
  217. package/skills/retro/SKILL.md +3 -1
  218. package/skills/review/SKILL.md +614 -588
  219. package/skills/review-intake/SKILL.md +249 -249
  220. package/skills/safeguard/SKILL.md +200 -200
  221. package/skills/sast/SKILL.md +190 -190
  222. package/skills/scaffold/SKILL.md +328 -287
  223. package/skills/scope-guard/SKILL.md +183 -180
  224. package/skills/scout/SKILL.md +269 -263
  225. package/skills/sentinel/SKILL.md +384 -381
  226. package/skills/sentinel-env/SKILL.md +254 -254
  227. package/skills/sequential-thinking/SKILL.md +234 -234
  228. package/skills/session-bridge/SKILL.md +595 -543
  229. package/skills/session-bridge/scripts/load-invariants.js +397 -0
  230. package/skills/skill-forge/SKILL.md +581 -581
  231. package/skills/skill-router/SKILL.md +3 -0
  232. package/skills/slides/SKILL.md +19 -0
  233. package/skills/surgeon/SKILL.md +215 -215
  234. package/skills/team/SKILL.md +557 -537
  235. package/skills/test/SKILL.md +620 -614
  236. package/skills/trend-scout/SKILL.md +145 -145
  237. package/skills/verification/SKILL.md +334 -326
  238. package/skills/video-creator/SKILL.md +201 -201
  239. package/skills/watchdog/SKILL.md +168 -168
  240. package/skills/worktree/SKILL.md +140 -140
@@ -1,72 +1,72 @@
1
- ---
2
- name: "game-loops"
3
- pack: "@rune/gamedev"
4
- description: "Game loop architecture — fixed timestep, interpolation, input handling, state machines, ECS."
5
- model: sonnet
6
- tools: [Read, Edit, Write, Grep, Glob, Bash]
7
- ---
8
-
9
- # game-loops
10
-
11
- Game loop architecture — fixed timestep, interpolation, input handling, state machines, ECS.
12
-
13
- #### Workflow
14
-
15
- **Step 1 — Detect game loop pattern**
16
- Use Grep to find loop code: `requestAnimationFrame`, `setInterval.*16`, `update`, `fixedUpdate`, `deltaTime`, `gameLoop`. Read the main loop to understand: timestep strategy, update/render separation, and input handling.
17
-
18
- **Step 2 — Audit loop correctness**
19
- Check for: variable timestep physics (non-deterministic), no accumulator for fixed update (physics tied to framerate), input polled inside render (inconsistent), missing interpolation between fixed steps (visual stuttering), and no frame budget monitoring.
20
-
21
- **Step 3 — Emit fixed timestep loop**
22
- Emit: fixed timestep (60Hz) with accumulator, interpolation for smooth rendering, decoupled input handler, and frame budget monitoring.
23
-
24
- #### Example
25
-
26
- ```typescript
27
- // Fixed timestep game loop with interpolation
28
- const TICK_RATE = 60;
29
- const TICK_DURATION = 1000 / TICK_RATE;
30
-
31
- class GameLoop {
32
- private accumulator = 0;
33
- private previousTime = 0;
34
- private running = false;
35
-
36
- constructor(
37
- private update: (dt: number) => void, // fixed timestep logic
38
- private render: (alpha: number) => void, // interpolated rendering
39
- ) {}
40
-
41
- start() {
42
- this.running = true;
43
- this.previousTime = performance.now();
44
- requestAnimationFrame(this.tick);
45
- }
46
-
47
- private tick = (currentTime: number) => {
48
- if (!this.running) return;
49
- const elapsed = Math.min(currentTime - this.previousTime, 250); // cap spiral of death
50
- this.previousTime = currentTime;
51
- this.accumulator += elapsed;
52
-
53
- while (this.accumulator >= TICK_DURATION) {
54
- this.update(TICK_DURATION / 1000); // dt in seconds
55
- this.accumulator -= TICK_DURATION;
56
- }
57
-
58
- const alpha = this.accumulator / TICK_DURATION; // interpolation factor [0, 1)
59
- this.render(alpha);
60
- requestAnimationFrame(this.tick);
61
- };
62
-
63
- stop() { this.running = false; }
64
- }
65
-
66
- // Usage
67
- const loop = new GameLoop(
68
- (dt) => { world.step(dt); entities.forEach(e => e.update(dt)); },
69
- (alpha) => { renderer.render(scene, camera, alpha); },
70
- );
71
- loop.start();
72
- ```
1
+ ---
2
+ name: "game-loops"
3
+ pack: "@rune/gamedev"
4
+ description: "Game loop architecture — fixed timestep, interpolation, input handling, state machines, ECS."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # game-loops
10
+
11
+ Game loop architecture — fixed timestep, interpolation, input handling, state machines, ECS.
12
+
13
+ #### Workflow
14
+
15
+ **Step 1 — Detect game loop pattern**
16
+ Use Grep to find loop code: `requestAnimationFrame`, `setInterval.*16`, `update`, `fixedUpdate`, `deltaTime`, `gameLoop`. Read the main loop to understand: timestep strategy, update/render separation, and input handling.
17
+
18
+ **Step 2 — Audit loop correctness**
19
+ Check for: variable timestep physics (non-deterministic), no accumulator for fixed update (physics tied to framerate), input polled inside render (inconsistent), missing interpolation between fixed steps (visual stuttering), and no frame budget monitoring.
20
+
21
+ **Step 3 — Emit fixed timestep loop**
22
+ Emit: fixed timestep (60Hz) with accumulator, interpolation for smooth rendering, decoupled input handler, and frame budget monitoring.
23
+
24
+ #### Example
25
+
26
+ ```typescript
27
+ // Fixed timestep game loop with interpolation
28
+ const TICK_RATE = 60;
29
+ const TICK_DURATION = 1000 / TICK_RATE;
30
+
31
+ class GameLoop {
32
+ private accumulator = 0;
33
+ private previousTime = 0;
34
+ private running = false;
35
+
36
+ constructor(
37
+ private update: (dt: number) => void, // fixed timestep logic
38
+ private render: (alpha: number) => void, // interpolated rendering
39
+ ) {}
40
+
41
+ start() {
42
+ this.running = true;
43
+ this.previousTime = performance.now();
44
+ requestAnimationFrame(this.tick);
45
+ }
46
+
47
+ private tick = (currentTime: number) => {
48
+ if (!this.running) return;
49
+ const elapsed = Math.min(currentTime - this.previousTime, 250); // cap spiral of death
50
+ this.previousTime = currentTime;
51
+ this.accumulator += elapsed;
52
+
53
+ while (this.accumulator >= TICK_DURATION) {
54
+ this.update(TICK_DURATION / 1000); // dt in seconds
55
+ this.accumulator -= TICK_DURATION;
56
+ }
57
+
58
+ const alpha = this.accumulator / TICK_DURATION; // interpolation factor [0, 1)
59
+ this.render(alpha);
60
+ requestAnimationFrame(this.tick);
61
+ };
62
+
63
+ stop() { this.running = false; }
64
+ }
65
+
66
+ // Usage
67
+ const loop = new GameLoop(
68
+ (dt) => { world.step(dt); entities.forEach(e => e.update(dt)); },
69
+ (alpha) => { renderer.render(scene, camera, alpha); },
70
+ );
71
+ loop.start();
72
+ ```
@@ -1,199 +1,199 @@
1
- ---
2
- name: "input-system"
3
- pack: "@rune/gamedev"
4
- description: "Keyboard/mouse/gamepad/touch input handling — unified InputManager with action mapping, input buffering, coyote time, and touch virtual joystick."
5
- model: sonnet
6
- tools: [Read, Edit, Write, Grep, Glob, Bash]
7
- ---
8
-
9
- # input-system
10
-
11
- Keyboard/mouse/gamepad/touch input handling — unified InputManager with action mapping, input buffering, coyote time, and touch virtual joystick.
12
-
13
- #### Workflow
14
-
15
- **Step 1 — Detect input handling**
16
- Use Grep to find input code: `addEventListener.*keydown`, `addEventListener.*gamepad`, `onMouseMove`, `ontouchstart`. Read input handlers to understand: action mapping strategy, polling vs event-driven, and platform support.
17
-
18
- **Step 2 — Audit input correctness**
19
- Check for: input polled inside render loop (should be in fixed update), no gamepad support, missing touch controls for mobile, hardcoded key bindings, and no input buffering (missed jump inputs).
20
-
21
- **Step 3 — Emit InputManager**
22
- Emit: unified InputManager with keyboard/mouse/gamepad/touch, action-based API, justPressed/wasReleased one-frame flags, and runtime rebinding. Always call `flush()` at end of each fixed tick.
23
-
24
- #### Unified Input Handler — Keyboard, Mouse, Gamepad, Touch
25
-
26
- ```typescript
27
- // InputManager — supports keyboard, mouse, gamepad, touch with action mapping
28
- type ActionMap = Record<string, string[]>; // action → [key1, key2, ...]
29
-
30
- const DEFAULT_ACTIONS: ActionMap = {
31
- moveUp: ['KeyW', 'ArrowUp'],
32
- moveDown: ['KeyS', 'ArrowDown'],
33
- moveLeft: ['KeyA', 'ArrowLeft'],
34
- moveRight: ['KeyD', 'ArrowRight'],
35
- jump: ['Space'],
36
- attack: ['Mouse0'],
37
- pause: ['Escape'],
38
- };
39
-
40
- class InputManager {
41
- private held = new Set<string>();
42
- private justPressed = new Set<string>();
43
- private justReleased = new Set<string>();
44
- private axes = { x: 0, y: 0 };
45
- private gamepad: Gamepad | null = null;
46
- private bindings: ActionMap;
47
-
48
- constructor(bindings = DEFAULT_ACTIONS) {
49
- this.bindings = { ...bindings };
50
- window.addEventListener('keydown', e => { this.held.add(e.code); this.justPressed.add(e.code); });
51
- window.addEventListener('keyup', e => { this.held.delete(e.code); this.justReleased.add(e.code); });
52
- window.addEventListener('mousedown', e => this.held.add(`Mouse${e.button}`));
53
- window.addEventListener('mouseup', e => this.held.delete(`Mouse${e.button}`));
54
- window.addEventListener('gamepaddisconnected', () => { this.gamepad = null; });
55
- }
56
-
57
- // Call at the START of each fixed update tick, not inside render
58
- pollGamepad() {
59
- const pads = navigator.getGamepads();
60
- this.gamepad = pads[0] ?? null;
61
- if (this.gamepad) {
62
- this.axes.x = this.gamepad.axes[0];
63
- this.axes.y = this.gamepad.axes[1];
64
- }
65
- }
66
-
67
- // Call at END of each fixed update tick to clear one-frame flags
68
- flush() {
69
- this.justPressed.clear();
70
- this.justReleased.clear();
71
- }
72
-
73
- isDown(action: string): boolean {
74
- return (this.bindings[action] ?? []).some(key => this.held.has(key));
75
- }
76
-
77
- wasPressed(action: string): boolean {
78
- return (this.bindings[action] ?? []).some(key => this.justPressed.has(key));
79
- }
80
-
81
- wasReleased(action: string): boolean {
82
- return (this.bindings[action] ?? []).some(key => this.justReleased.has(key));
83
- }
84
-
85
- getAxes() { return { ...this.axes }; }
86
-
87
- // Rebind at runtime (save to localStorage)
88
- rebind(action: string, keys: string[]) {
89
- this.bindings[action] = keys;
90
- localStorage.setItem('inputBindings', JSON.stringify(this.bindings));
91
- }
92
-
93
- loadSavedBindings() {
94
- const saved = localStorage.getItem('inputBindings');
95
- if (saved) this.bindings = { ...this.bindings, ...JSON.parse(saved) };
96
- }
97
- }
98
- ```
99
-
100
- #### Input Buffering (Coyote Time + Jump Buffering)
101
-
102
- ```typescript
103
- // Input buffer — remember button press for N frames to forgive missed timing
104
- class InputBuffer {
105
- private buffer: Map<string, number> = new Map(); // action → frames remaining
106
-
107
- bufferAction(action: string, frames = 6) { this.buffer.set(action, frames); }
108
-
109
- consume(action: string): boolean {
110
- if ((this.buffer.get(action) ?? 0) > 0) {
111
- this.buffer.set(action, 0);
112
- return true;
113
- }
114
- return false;
115
- }
116
-
117
- tick() {
118
- this.buffer.forEach((frames, action) => {
119
- if (frames > 0) this.buffer.set(action, frames - 1);
120
- });
121
- }
122
- }
123
-
124
- // Coyote time — allow jump for N frames after walking off a ledge
125
- class CoyoteTime {
126
- private grounded = false;
127
- private coyoteFrames = 0;
128
- private readonly maxFrames = 6;
129
-
130
- update(isGrounded: boolean) {
131
- if (isGrounded) {
132
- this.grounded = true;
133
- this.coyoteFrames = this.maxFrames;
134
- } else {
135
- this.grounded = false;
136
- this.coyoteFrames = Math.max(0, this.coyoteFrames - 1);
137
- }
138
- }
139
-
140
- canJump(): boolean { return this.coyoteFrames > 0; }
141
- }
142
- ```
143
-
144
- #### Touch Virtual Joystick (Mobile)
145
-
146
- ```typescript
147
- // Canvas-rendered virtual joystick for mobile
148
- class VirtualJoystick {
149
- private origin: { x: number; y: number } | null = null;
150
- private current: { x: number; y: number } | null = null;
151
- private touchId: number | null = null;
152
- readonly radius = 60;
153
-
154
- constructor(private canvas: HTMLCanvasElement) {
155
- canvas.addEventListener('touchstart', this.onStart, { passive: false });
156
- canvas.addEventListener('touchmove', this.onMove, { passive: false });
157
- canvas.addEventListener('touchend', this.onEnd);
158
- }
159
-
160
- private onStart = (e: TouchEvent) => {
161
- e.preventDefault();
162
- if (this.touchId !== null) return;
163
- const t = e.changedTouches[0];
164
- this.touchId = t.identifier;
165
- this.origin = { x: t.clientX, y: t.clientY };
166
- this.current = { ...this.origin };
167
- };
168
-
169
- private onMove = (e: TouchEvent) => {
170
- e.preventDefault();
171
- const t = Array.from(e.changedTouches).find(t => t.identifier === this.touchId);
172
- if (!t || !this.origin) return;
173
- const dx = t.clientX - this.origin.x;
174
- const dy = t.clientY - this.origin.y;
175
- const len = Math.hypot(dx, dy);
176
- const clamped = Math.min(len, this.radius);
177
- const angle = Math.atan2(dy, dx);
178
- this.current = {
179
- x: this.origin.x + Math.cos(angle) * clamped,
180
- y: this.origin.y + Math.sin(angle) * clamped,
181
- };
182
- };
183
-
184
- private onEnd = (e: TouchEvent) => {
185
- if (Array.from(e.changedTouches).some(t => t.identifier === this.touchId)) {
186
- this.origin = this.current = null;
187
- this.touchId = null;
188
- }
189
- };
190
-
191
- getAxes(): { x: number; y: number } {
192
- if (!this.origin || !this.current) return { x: 0, y: 0 };
193
- return {
194
- x: (this.current.x - this.origin.x) / this.radius,
195
- y: (this.current.y - this.origin.y) / this.radius,
196
- };
197
- }
198
- }
199
- ```
1
+ ---
2
+ name: "input-system"
3
+ pack: "@rune/gamedev"
4
+ description: "Keyboard/mouse/gamepad/touch input handling — unified InputManager with action mapping, input buffering, coyote time, and touch virtual joystick."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # input-system
10
+
11
+ Keyboard/mouse/gamepad/touch input handling — unified InputManager with action mapping, input buffering, coyote time, and touch virtual joystick.
12
+
13
+ #### Workflow
14
+
15
+ **Step 1 — Detect input handling**
16
+ Use Grep to find input code: `addEventListener.*keydown`, `addEventListener.*gamepad`, `onMouseMove`, `ontouchstart`. Read input handlers to understand: action mapping strategy, polling vs event-driven, and platform support.
17
+
18
+ **Step 2 — Audit input correctness**
19
+ Check for: input polled inside render loop (should be in fixed update), no gamepad support, missing touch controls for mobile, hardcoded key bindings, and no input buffering (missed jump inputs).
20
+
21
+ **Step 3 — Emit InputManager**
22
+ Emit: unified InputManager with keyboard/mouse/gamepad/touch, action-based API, justPressed/wasReleased one-frame flags, and runtime rebinding. Always call `flush()` at end of each fixed tick.
23
+
24
+ #### Unified Input Handler — Keyboard, Mouse, Gamepad, Touch
25
+
26
+ ```typescript
27
+ // InputManager — supports keyboard, mouse, gamepad, touch with action mapping
28
+ type ActionMap = Record<string, string[]>; // action → [key1, key2, ...]
29
+
30
+ const DEFAULT_ACTIONS: ActionMap = {
31
+ moveUp: ['KeyW', 'ArrowUp'],
32
+ moveDown: ['KeyS', 'ArrowDown'],
33
+ moveLeft: ['KeyA', 'ArrowLeft'],
34
+ moveRight: ['KeyD', 'ArrowRight'],
35
+ jump: ['Space'],
36
+ attack: ['Mouse0'],
37
+ pause: ['Escape'],
38
+ };
39
+
40
+ class InputManager {
41
+ private held = new Set<string>();
42
+ private justPressed = new Set<string>();
43
+ private justReleased = new Set<string>();
44
+ private axes = { x: 0, y: 0 };
45
+ private gamepad: Gamepad | null = null;
46
+ private bindings: ActionMap;
47
+
48
+ constructor(bindings = DEFAULT_ACTIONS) {
49
+ this.bindings = { ...bindings };
50
+ window.addEventListener('keydown', e => { this.held.add(e.code); this.justPressed.add(e.code); });
51
+ window.addEventListener('keyup', e => { this.held.delete(e.code); this.justReleased.add(e.code); });
52
+ window.addEventListener('mousedown', e => this.held.add(`Mouse${e.button}`));
53
+ window.addEventListener('mouseup', e => this.held.delete(`Mouse${e.button}`));
54
+ window.addEventListener('gamepaddisconnected', () => { this.gamepad = null; });
55
+ }
56
+
57
+ // Call at the START of each fixed update tick, not inside render
58
+ pollGamepad() {
59
+ const pads = navigator.getGamepads();
60
+ this.gamepad = pads[0] ?? null;
61
+ if (this.gamepad) {
62
+ this.axes.x = this.gamepad.axes[0];
63
+ this.axes.y = this.gamepad.axes[1];
64
+ }
65
+ }
66
+
67
+ // Call at END of each fixed update tick to clear one-frame flags
68
+ flush() {
69
+ this.justPressed.clear();
70
+ this.justReleased.clear();
71
+ }
72
+
73
+ isDown(action: string): boolean {
74
+ return (this.bindings[action] ?? []).some(key => this.held.has(key));
75
+ }
76
+
77
+ wasPressed(action: string): boolean {
78
+ return (this.bindings[action] ?? []).some(key => this.justPressed.has(key));
79
+ }
80
+
81
+ wasReleased(action: string): boolean {
82
+ return (this.bindings[action] ?? []).some(key => this.justReleased.has(key));
83
+ }
84
+
85
+ getAxes() { return { ...this.axes }; }
86
+
87
+ // Rebind at runtime (save to localStorage)
88
+ rebind(action: string, keys: string[]) {
89
+ this.bindings[action] = keys;
90
+ localStorage.setItem('inputBindings', JSON.stringify(this.bindings));
91
+ }
92
+
93
+ loadSavedBindings() {
94
+ const saved = localStorage.getItem('inputBindings');
95
+ if (saved) this.bindings = { ...this.bindings, ...JSON.parse(saved) };
96
+ }
97
+ }
98
+ ```
99
+
100
+ #### Input Buffering (Coyote Time + Jump Buffering)
101
+
102
+ ```typescript
103
+ // Input buffer — remember button press for N frames to forgive missed timing
104
+ class InputBuffer {
105
+ private buffer: Map<string, number> = new Map(); // action → frames remaining
106
+
107
+ bufferAction(action: string, frames = 6) { this.buffer.set(action, frames); }
108
+
109
+ consume(action: string): boolean {
110
+ if ((this.buffer.get(action) ?? 0) > 0) {
111
+ this.buffer.set(action, 0);
112
+ return true;
113
+ }
114
+ return false;
115
+ }
116
+
117
+ tick() {
118
+ this.buffer.forEach((frames, action) => {
119
+ if (frames > 0) this.buffer.set(action, frames - 1);
120
+ });
121
+ }
122
+ }
123
+
124
+ // Coyote time — allow jump for N frames after walking off a ledge
125
+ class CoyoteTime {
126
+ private grounded = false;
127
+ private coyoteFrames = 0;
128
+ private readonly maxFrames = 6;
129
+
130
+ update(isGrounded: boolean) {
131
+ if (isGrounded) {
132
+ this.grounded = true;
133
+ this.coyoteFrames = this.maxFrames;
134
+ } else {
135
+ this.grounded = false;
136
+ this.coyoteFrames = Math.max(0, this.coyoteFrames - 1);
137
+ }
138
+ }
139
+
140
+ canJump(): boolean { return this.coyoteFrames > 0; }
141
+ }
142
+ ```
143
+
144
+ #### Touch Virtual Joystick (Mobile)
145
+
146
+ ```typescript
147
+ // Canvas-rendered virtual joystick for mobile
148
+ class VirtualJoystick {
149
+ private origin: { x: number; y: number } | null = null;
150
+ private current: { x: number; y: number } | null = null;
151
+ private touchId: number | null = null;
152
+ readonly radius = 60;
153
+
154
+ constructor(private canvas: HTMLCanvasElement) {
155
+ canvas.addEventListener('touchstart', this.onStart, { passive: false });
156
+ canvas.addEventListener('touchmove', this.onMove, { passive: false });
157
+ canvas.addEventListener('touchend', this.onEnd);
158
+ }
159
+
160
+ private onStart = (e: TouchEvent) => {
161
+ e.preventDefault();
162
+ if (this.touchId !== null) return;
163
+ const t = e.changedTouches[0];
164
+ this.touchId = t.identifier;
165
+ this.origin = { x: t.clientX, y: t.clientY };
166
+ this.current = { ...this.origin };
167
+ };
168
+
169
+ private onMove = (e: TouchEvent) => {
170
+ e.preventDefault();
171
+ const t = Array.from(e.changedTouches).find(t => t.identifier === this.touchId);
172
+ if (!t || !this.origin) return;
173
+ const dx = t.clientX - this.origin.x;
174
+ const dy = t.clientY - this.origin.y;
175
+ const len = Math.hypot(dx, dy);
176
+ const clamped = Math.min(len, this.radius);
177
+ const angle = Math.atan2(dy, dx);
178
+ this.current = {
179
+ x: this.origin.x + Math.cos(angle) * clamped,
180
+ y: this.origin.y + Math.sin(angle) * clamped,
181
+ };
182
+ };
183
+
184
+ private onEnd = (e: TouchEvent) => {
185
+ if (Array.from(e.changedTouches).some(t => t.identifier === this.touchId)) {
186
+ this.origin = this.current = null;
187
+ this.touchId = null;
188
+ }
189
+ };
190
+
191
+ getAxes(): { x: number; y: number } {
192
+ if (!this.origin || !this.current) return { x: 0, y: 0 };
193
+ return {
194
+ x: (this.current.x - this.origin.x) / this.radius,
195
+ y: (this.current.y - this.origin.y) / this.radius,
196
+ };
197
+ }
198
+ }
199
+ ```