@rune-kit/rune 2.1.1
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.
- package/LICENSE +21 -0
- package/README.md +357 -0
- package/agents/.gitkeep +0 -0
- package/agents/architect.md +29 -0
- package/agents/asset-creator.md +11 -0
- package/agents/audit.md +11 -0
- package/agents/autopsy.md +11 -0
- package/agents/brainstorm.md +11 -0
- package/agents/browser-pilot.md +11 -0
- package/agents/coder.md +29 -0
- package/agents/completion-gate.md +11 -0
- package/agents/constraint-check.md +11 -0
- package/agents/context-engine.md +11 -0
- package/agents/cook.md +11 -0
- package/agents/db.md +11 -0
- package/agents/debug.md +11 -0
- package/agents/dependency-doctor.md +11 -0
- package/agents/deploy.md +11 -0
- package/agents/design.md +11 -0
- package/agents/docs-seeker.md +11 -0
- package/agents/fix.md +11 -0
- package/agents/hallucination-guard.md +11 -0
- package/agents/incident.md +11 -0
- package/agents/integrity-check.md +11 -0
- package/agents/journal.md +11 -0
- package/agents/launch.md +11 -0
- package/agents/logic-guardian.md +11 -0
- package/agents/marketing.md +11 -0
- package/agents/onboard.md +11 -0
- package/agents/perf.md +11 -0
- package/agents/plan.md +11 -0
- package/agents/preflight.md +11 -0
- package/agents/problem-solver.md +11 -0
- package/agents/rescue.md +11 -0
- package/agents/research.md +11 -0
- package/agents/researcher.md +29 -0
- package/agents/review-intake.md +11 -0
- package/agents/review.md +11 -0
- package/agents/reviewer.md +28 -0
- package/agents/safeguard.md +11 -0
- package/agents/sast.md +11 -0
- package/agents/scanner.md +28 -0
- package/agents/scope-guard.md +11 -0
- package/agents/scout.md +11 -0
- package/agents/sentinel.md +11 -0
- package/agents/sequential-thinking.md +11 -0
- package/agents/session-bridge.md +11 -0
- package/agents/skill-forge.md +11 -0
- package/agents/skill-router.md +11 -0
- package/agents/surgeon.md +11 -0
- package/agents/team.md +11 -0
- package/agents/test.md +11 -0
- package/agents/trend-scout.md +11 -0
- package/agents/verification.md +11 -0
- package/agents/video-creator.md +11 -0
- package/agents/watchdog.md +11 -0
- package/agents/worktree.md +11 -0
- package/commands/.gitkeep +0 -0
- package/commands/rune.md +168 -0
- package/compiler/__tests__/openclaw-adapter.test.js +140 -0
- package/compiler/__tests__/parser.test.js +55 -0
- package/compiler/adapters/antigravity.js +59 -0
- package/compiler/adapters/claude.js +37 -0
- package/compiler/adapters/cursor.js +67 -0
- package/compiler/adapters/generic.js +60 -0
- package/compiler/adapters/index.js +45 -0
- package/compiler/adapters/openclaw.js +150 -0
- package/compiler/adapters/windsurf.js +60 -0
- package/compiler/bin/rune.js +288 -0
- package/compiler/doctor.js +153 -0
- package/compiler/emitter.js +240 -0
- package/compiler/parser.js +208 -0
- package/compiler/transformer.js +69 -0
- package/compiler/transforms/branding.js +27 -0
- package/compiler/transforms/cross-references.js +29 -0
- package/compiler/transforms/frontmatter.js +38 -0
- package/compiler/transforms/hooks.js +68 -0
- package/compiler/transforms/subagents.js +36 -0
- package/compiler/transforms/tool-names.js +60 -0
- package/contexts/dev.md +34 -0
- package/contexts/research.md +43 -0
- package/contexts/review.md +55 -0
- package/extensions/ai-ml/PACK.md +517 -0
- package/extensions/analytics/PACK.md +557 -0
- package/extensions/backend/PACK.md +678 -0
- package/extensions/chrome-ext/PACK.md +995 -0
- package/extensions/content/PACK.md +381 -0
- package/extensions/devops/PACK.md +520 -0
- package/extensions/ecommerce/PACK.md +280 -0
- package/extensions/gamedev/PACK.md +393 -0
- package/extensions/mobile/PACK.md +273 -0
- package/extensions/saas/PACK.md +805 -0
- package/extensions/security/PACK.md +536 -0
- package/extensions/trading/PACK.md +597 -0
- package/extensions/ui/PACK.md +947 -0
- package/package.json +47 -0
- package/skills/.gitkeep +0 -0
- package/skills/adversary/SKILL.md +271 -0
- package/skills/asset-creator/SKILL.md +157 -0
- package/skills/audit/SKILL.md +466 -0
- package/skills/autopsy/SKILL.md +200 -0
- package/skills/ba/SKILL.md +279 -0
- package/skills/brainstorm/SKILL.md +266 -0
- package/skills/browser-pilot/SKILL.md +168 -0
- package/skills/completion-gate/SKILL.md +151 -0
- package/skills/constraint-check/SKILL.md +165 -0
- package/skills/context-engine/SKILL.md +176 -0
- package/skills/cook/SKILL.md +636 -0
- package/skills/db/SKILL.md +256 -0
- package/skills/debug/SKILL.md +240 -0
- package/skills/dependency-doctor/SKILL.md +235 -0
- package/skills/deploy/SKILL.md +174 -0
- package/skills/design/DESIGN-REFERENCE.md +365 -0
- package/skills/design/SKILL.md +462 -0
- package/skills/doc-processor/SKILL.md +254 -0
- package/skills/docs/SKILL.md +336 -0
- package/skills/docs-seeker/SKILL.md +166 -0
- package/skills/fix/SKILL.md +192 -0
- package/skills/git/SKILL.md +285 -0
- package/skills/hallucination-guard/SKILL.md +204 -0
- package/skills/incident/SKILL.md +241 -0
- package/skills/integrity-check/SKILL.md +169 -0
- package/skills/journal/SKILL.md +190 -0
- package/skills/launch/SKILL.md +330 -0
- package/skills/logic-guardian/SKILL.md +240 -0
- package/skills/marketing/SKILL.md +229 -0
- package/skills/mcp-builder/SKILL.md +311 -0
- package/skills/onboard/SKILL.md +298 -0
- package/skills/perf/SKILL.md +297 -0
- package/skills/plan/SKILL.md +520 -0
- package/skills/preflight/SKILL.md +231 -0
- package/skills/problem-solver/SKILL.md +284 -0
- package/skills/rescue/SKILL.md +434 -0
- package/skills/research/SKILL.md +122 -0
- package/skills/review/SKILL.md +354 -0
- package/skills/review-intake/SKILL.md +222 -0
- package/skills/safeguard/SKILL.md +188 -0
- package/skills/sast/SKILL.md +190 -0
- package/skills/scaffold/SKILL.md +276 -0
- package/skills/scope-guard/SKILL.md +150 -0
- package/skills/scout/SKILL.md +232 -0
- package/skills/sentinel/SKILL.md +320 -0
- package/skills/sentinel-env/SKILL.md +226 -0
- package/skills/sequential-thinking/SKILL.md +234 -0
- package/skills/session-bridge/SKILL.md +287 -0
- package/skills/skill-forge/SKILL.md +317 -0
- package/skills/skill-router/SKILL.md +267 -0
- package/skills/surgeon/SKILL.md +203 -0
- package/skills/team/SKILL.md +397 -0
- package/skills/test/SKILL.md +271 -0
- package/skills/trend-scout/SKILL.md +145 -0
- package/skills/verification/SKILL.md +201 -0
- package/skills/video-creator/SKILL.md +201 -0
- package/skills/watchdog/SKILL.md +166 -0
- package/skills/worktree/SKILL.md +140 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "@rune/ecommerce"
|
|
3
|
+
description: E-commerce patterns — Shopify development, payment integration, shopping cart, and inventory management.
|
|
4
|
+
metadata:
|
|
5
|
+
author: runedev
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
layer: L4
|
|
8
|
+
price: "$12"
|
|
9
|
+
target: E-commerce developers
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# @rune/ecommerce
|
|
13
|
+
|
|
14
|
+
## Purpose
|
|
15
|
+
|
|
16
|
+
E-commerce codebases fail at the seams between systems: payment intents that succeed but order records that don't get created, inventory counts that go negative during flash sales, carts that lose items when users sign in, and Shopify themes that break on metafield updates. This pack addresses the full order lifecycle — storefront to payment to fulfillment — with patterns that handle the race conditions, state machines, and distributed system problems that every commerce platform eventually hits.
|
|
17
|
+
|
|
18
|
+
## Triggers
|
|
19
|
+
|
|
20
|
+
- Auto-trigger: when `shopify.app.toml`, `*.liquid`, `cart`, `checkout`, `stripe` in payment context, `inventory` schema detected
|
|
21
|
+
- `/rune shopify-dev` — audit Shopify theme or app architecture
|
|
22
|
+
- `/rune payment-integration` — set up or audit payment flows
|
|
23
|
+
- `/rune cart-system` — build or audit cart architecture
|
|
24
|
+
- `/rune inventory-mgmt` — audit inventory tracking and stock management
|
|
25
|
+
- Called by `cook` (L1) when e-commerce project detected
|
|
26
|
+
- Called by `launch` (L1) when preparing storefront for production
|
|
27
|
+
|
|
28
|
+
## Skills Included
|
|
29
|
+
|
|
30
|
+
### shopify-dev
|
|
31
|
+
|
|
32
|
+
Shopify development patterns — Liquid templates, Shopify API, Hydrogen/Remix storefronts, metafields, theme architecture.
|
|
33
|
+
|
|
34
|
+
#### Workflow
|
|
35
|
+
|
|
36
|
+
**Step 1 — Detect Shopify architecture**
|
|
37
|
+
Use Glob to find `shopify.app.toml`, `*.liquid`, `remix.config.*`, `hydrogen.config.*`. Use Grep to find Storefront API queries (`#graphql`), Admin API calls, and metafield references. Classify: theme app extension, custom app, or Hydrogen storefront.
|
|
38
|
+
|
|
39
|
+
**Step 2 — Audit theme and API usage**
|
|
40
|
+
Check for: Liquid templates without `| escape` filter (XSS), Storefront API queries without pagination, hardcoded product IDs, missing metafield type validation, theme sections without `schema` blocks, and deprecated API version usage.
|
|
41
|
+
|
|
42
|
+
**Step 3 — Emit optimized patterns**
|
|
43
|
+
For Hydrogen: emit typed Storefront API loader with proper caching. For theme: emit section schema with metafield integration. For apps: emit webhook handler with HMAC verification.
|
|
44
|
+
|
|
45
|
+
#### Example
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Hydrogen — typed Storefront API loader with caching
|
|
49
|
+
import { json, type LoaderFunctionArgs } from '@shopify/remix-oxygen';
|
|
50
|
+
|
|
51
|
+
const PRODUCT_QUERY = `#graphql
|
|
52
|
+
query Product($handle: String!) {
|
|
53
|
+
product(handle: $handle) {
|
|
54
|
+
id title description
|
|
55
|
+
variants(first: 10) {
|
|
56
|
+
nodes { id title price { amount currencyCode } availableForSale }
|
|
57
|
+
}
|
|
58
|
+
metafield(namespace: "custom", key: "care_instructions") { value type }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
` as const;
|
|
62
|
+
|
|
63
|
+
export async function loader({ params, context }: LoaderFunctionArgs) {
|
|
64
|
+
const { product } = await context.storefront.query(PRODUCT_QUERY, {
|
|
65
|
+
variables: { handle: params.handle! },
|
|
66
|
+
cache: context.storefront.CacheLong(),
|
|
67
|
+
});
|
|
68
|
+
if (!product) throw new Response('Not Found', { status: 404 });
|
|
69
|
+
return json({ product });
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### payment-integration
|
|
76
|
+
|
|
77
|
+
Payment integration — Stripe Payment Intents, 3D Secure, webhook handling, refunds, PCI compliance.
|
|
78
|
+
|
|
79
|
+
#### Workflow
|
|
80
|
+
|
|
81
|
+
**Step 1 — Detect payment setup**
|
|
82
|
+
Use Grep to find `stripe`, `paypal`, `@stripe/stripe-js`, payment-related endpoints. Read checkout handlers and webhook processors to understand: payment flow type, webhook events handled, and error recovery.
|
|
83
|
+
|
|
84
|
+
**Step 2 — Audit payment security**
|
|
85
|
+
Check for: missing idempotency keys on payment creation, webhook signature not verified, payment amount calculated client-side (manipulation risk), no 3D Secure handling, secret keys in client bundle, and missing failed payment recovery flow.
|
|
86
|
+
|
|
87
|
+
**Step 3 — Emit robust payment flow**
|
|
88
|
+
Emit: server-side Payment Intent creation with idempotency, 3D Secure handling, comprehensive webhook handler, and refund flow with audit trail.
|
|
89
|
+
|
|
90
|
+
#### Example
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Stripe Payment Intent — server-side, idempotent, 3DS-ready
|
|
94
|
+
import Stripe from 'stripe';
|
|
95
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
96
|
+
|
|
97
|
+
app.post('/api/checkout', async (req, res) => {
|
|
98
|
+
const { cartId, paymentMethodId } = req.body;
|
|
99
|
+
const cart = await cartService.getVerified(cartId); // server-side price calculation
|
|
100
|
+
|
|
101
|
+
const intent = await stripe.paymentIntents.create({
|
|
102
|
+
amount: cart.totalInCents, // ALWAYS server-calculated
|
|
103
|
+
currency: cart.currency,
|
|
104
|
+
payment_method: paymentMethodId,
|
|
105
|
+
confirm: true,
|
|
106
|
+
return_url: `${process.env.APP_URL}/checkout/complete`,
|
|
107
|
+
metadata: { cartId, userId: req.user.id },
|
|
108
|
+
idempotencyKey: `checkout-${cartId}-${Date.now()}`,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (intent.status === 'requires_action') {
|
|
112
|
+
return res.json({ requiresAction: true, clientSecret: intent.client_secret });
|
|
113
|
+
}
|
|
114
|
+
if (intent.status === 'succeeded') {
|
|
115
|
+
await orderService.create(cart, intent.id);
|
|
116
|
+
return res.json({ success: true, orderId: intent.metadata.orderId });
|
|
117
|
+
}
|
|
118
|
+
res.status(400).json({ error: { code: 'PAYMENT_FAILED', message: 'Payment could not be processed' } });
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### cart-system
|
|
125
|
+
|
|
126
|
+
Shopping cart architecture — state management, persistent carts, guest checkout, coupon/discount engine, tax calculation.
|
|
127
|
+
|
|
128
|
+
#### Workflow
|
|
129
|
+
|
|
130
|
+
**Step 1 — Detect cart architecture**
|
|
131
|
+
Use Grep to find cart state: `cartStore`, `useCart`, `addToCart`, `localStorage.*cart`, `session.*cart`. Read cart-related components and API routes to understand: client vs server cart, persistence strategy, and discount handling.
|
|
132
|
+
|
|
133
|
+
**Step 2 — Audit cart integrity**
|
|
134
|
+
Check for: cart total calculated client-side only (price manipulation), no cart TTL (stale carts), missing guest-to-authenticated cart merge, race conditions on concurrent cart updates, coupons validated client-side, and no stock check at add-to-cart time.
|
|
135
|
+
|
|
136
|
+
**Step 3 — Emit cart patterns**
|
|
137
|
+
Emit: server-authoritative cart with client cache, guest-to-auth merge flow, coupon validation middleware, and optimistic UI with server reconciliation.
|
|
138
|
+
|
|
139
|
+
#### Example
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// Server-authoritative cart with Zustand client cache
|
|
143
|
+
import { create } from 'zustand';
|
|
144
|
+
import { persist } from 'zustand/middleware';
|
|
145
|
+
|
|
146
|
+
interface CartStore {
|
|
147
|
+
items: CartItem[];
|
|
148
|
+
cartId: string | null;
|
|
149
|
+
addItem: (productId: string, variantId: string, qty: number) => Promise<void>;
|
|
150
|
+
mergeGuestCart: (userId: string) => Promise<void>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const useCart = create<CartStore>()(persist((set, get) => ({
|
|
154
|
+
items: [], cartId: null,
|
|
155
|
+
|
|
156
|
+
addItem: async (productId, variantId, qty) => {
|
|
157
|
+
// Optimistic update
|
|
158
|
+
set(state => ({ items: [...state.items, { productId, variantId, qty, pending: true }] }));
|
|
159
|
+
// Server reconciliation (validates stock, calculates price)
|
|
160
|
+
const cart = await fetch('/api/cart/add', {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
body: JSON.stringify({ cartId: get().cartId, productId, variantId, qty }),
|
|
163
|
+
}).then(r => r.json());
|
|
164
|
+
set({ items: cart.items, cartId: cart.id }); // server is source of truth
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
mergeGuestCart: async (userId) => {
|
|
168
|
+
const { cartId } = get();
|
|
169
|
+
if (!cartId) return;
|
|
170
|
+
const merged = await fetch('/api/cart/merge', {
|
|
171
|
+
method: 'POST', body: JSON.stringify({ guestCartId: cartId, userId }),
|
|
172
|
+
}).then(r => r.json());
|
|
173
|
+
set({ items: merged.items, cartId: merged.id });
|
|
174
|
+
},
|
|
175
|
+
}), { name: 'cart-storage' }));
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### inventory-mgmt
|
|
181
|
+
|
|
182
|
+
Inventory management — stock tracking with optimistic locking, variant management, low stock alerts, backorder handling.
|
|
183
|
+
|
|
184
|
+
#### Workflow
|
|
185
|
+
|
|
186
|
+
**Step 1 — Detect inventory model**
|
|
187
|
+
Use Grep to find stock-related code: `stock`, `inventory`, `quantity`, `variant`, `warehouse`, `sku`. Read schema files to understand: single vs multi-warehouse, variant structure, and reservation model.
|
|
188
|
+
|
|
189
|
+
**Step 2 — Audit stock integrity**
|
|
190
|
+
Check for: stock decremented without transaction (oversell risk), no optimistic locking on concurrent updates, inventory checked at cart-add but not at checkout (stale check), missing low-stock alerts, and no backorder handling for out-of-stock items.
|
|
191
|
+
|
|
192
|
+
**Step 3 — Emit inventory patterns**
|
|
193
|
+
Emit: atomic stock reservation with optimistic locking (version field), reservation expiry for abandoned checkouts, low-stock alert trigger, and backorder queue.
|
|
194
|
+
|
|
195
|
+
#### Example
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// Atomic stock reservation with optimistic locking (Prisma)
|
|
199
|
+
async function reserveStock(variantId: string, qty: number, orderId: string) {
|
|
200
|
+
const MAX_RETRIES = 3;
|
|
201
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
202
|
+
const variant = await prisma.variant.findUniqueOrThrow({ where: { id: variantId } });
|
|
203
|
+
|
|
204
|
+
if (variant.stock < qty && !variant.allowBackorder) {
|
|
205
|
+
throw new Error(`Insufficient stock: ${variant.stock} available, ${qty} requested`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const updated = await prisma.variant.update({
|
|
210
|
+
where: { id: variantId, version: variant.version }, // optimistic lock
|
|
211
|
+
data: {
|
|
212
|
+
stock: { decrement: qty },
|
|
213
|
+
version: { increment: 1 },
|
|
214
|
+
reservations: { create: { orderId, qty, expiresAt: addMinutes(new Date(), 15) } },
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (updated.stock <= updated.lowStockThreshold) {
|
|
219
|
+
await alertService.trigger('LOW_STOCK', { variantId, currentStock: updated.stock });
|
|
220
|
+
}
|
|
221
|
+
return updated;
|
|
222
|
+
} catch (e) {
|
|
223
|
+
if (attempt === MAX_RETRIES - 1) throw new Error('Stock reservation failed: concurrent modification');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Connections
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
Calls → sentinel (L2): PCI compliance audit on payment code
|
|
235
|
+
Calls → db (L2): schema design for orders, inventory, carts
|
|
236
|
+
Called By ← cook (L1): when e-commerce project detected
|
|
237
|
+
Called By ← launch (L1): pre-launch checkout verification
|
|
238
|
+
Called By ← review (L2): when payment or cart code under review
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Tech Stack Support
|
|
242
|
+
|
|
243
|
+
| Platform | Framework | Payment | Notes |
|
|
244
|
+
|----------|-----------|---------|-------|
|
|
245
|
+
| Shopify | Hydrogen 2.x (Remix) | Shopify Payments | Storefront + Admin API |
|
|
246
|
+
| Custom | Next.js 16 / SvelteKit | Stripe | Most flexible |
|
|
247
|
+
| Headless | Any frontend | Stripe / PayPal | API-first commerce |
|
|
248
|
+
| Medusa.js | Next.js | Stripe / PayPal | Open-source alternative |
|
|
249
|
+
|
|
250
|
+
## Constraints
|
|
251
|
+
|
|
252
|
+
1. MUST calculate all prices server-side — never trust client-submitted amounts for payment.
|
|
253
|
+
2. MUST use idempotency keys on all payment creation API calls — prevent double charges.
|
|
254
|
+
3. MUST use optimistic locking or database transactions for inventory updates — prevent overselling.
|
|
255
|
+
4. MUST verify webhook signatures before processing any payment events.
|
|
256
|
+
5. MUST validate coupons and discounts server-side — client-side validation is bypassable.
|
|
257
|
+
|
|
258
|
+
## Sharp Edges
|
|
259
|
+
|
|
260
|
+
| Failure Mode | Severity | Mitigation |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| Double charge from retried Payment Intent without idempotency key | CRITICAL | Always pass idempotencyKey derived from cartId; check for existing successful intent before creating new one |
|
|
263
|
+
| Overselling during flash sale (stock goes negative) | CRITICAL | Use optimistic locking with version field; serializable isolation for high-contention items |
|
|
264
|
+
| Webhook processes `payment_intent.succeeded` but order creation fails (payment taken, no order) | HIGH | Wrap order creation in transaction; implement reconciliation job that matches intents to orders |
|
|
265
|
+
| Guest cart items lost on login (separate cart created for auth user) | HIGH | Implement cart merge in auth callback; prefer server cart state over local |
|
|
266
|
+
| Tax calculated at cart time but rate changed by checkout (wrong amount charged) | MEDIUM | Recalculate tax at payment creation time, not at cart add time |
|
|
267
|
+
| Liquid template outputs unescaped metafield content (XSS in Shopify theme) | HIGH | Always use `| escape` filter on user-generated metafield values |
|
|
268
|
+
|
|
269
|
+
## Done When
|
|
270
|
+
|
|
271
|
+
- Checkout flow completes end-to-end: cart → payment → order confirmation
|
|
272
|
+
- Inventory accurately tracks stock with no overselling under concurrent load
|
|
273
|
+
- Webhooks are idempotent and handle all payment lifecycle events
|
|
274
|
+
- Guest-to-authenticated cart merge works without data loss
|
|
275
|
+
- All prices and discounts validated server-side
|
|
276
|
+
- Structured report emitted for each skill invoked
|
|
277
|
+
|
|
278
|
+
## Cost Profile
|
|
279
|
+
|
|
280
|
+
~8,000–14,000 tokens per full pack run (all 4 skills). Individual skill: ~2,000–4,000 tokens. Sonnet default. Use haiku for detection scans; escalate to sonnet for payment flow and inventory pattern generation.
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "@rune/gamedev"
|
|
3
|
+
description: Game development patterns — Three.js, WebGL, game loops, physics engines, and asset pipelines.
|
|
4
|
+
metadata:
|
|
5
|
+
author: runedev
|
|
6
|
+
version: "0.1.0"
|
|
7
|
+
layer: L4
|
|
8
|
+
price: "$12"
|
|
9
|
+
target: Game developers
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# @rune/gamedev
|
|
13
|
+
|
|
14
|
+
## Purpose
|
|
15
|
+
|
|
16
|
+
Web game development hits performance walls that traditional web apps never encounter: 60fps render loops that stutter on garbage collection, physics simulations that diverge between clients, shaders that work on desktop but fail on mobile GPUs, and asset loading that blocks the first frame for 10 seconds. This pack provides patterns for the full web game stack — rendering, simulation, physics, and assets — each optimized for the unique constraints of real-time interactive applications running in a browser.
|
|
17
|
+
|
|
18
|
+
## Triggers
|
|
19
|
+
|
|
20
|
+
- Auto-trigger: when `three`, `@react-three/fiber`, `pixi.js`, `phaser`, `cannon`, `rapier`, `*.glsl`, `*.wgsl` detected
|
|
21
|
+
- `/rune threejs-patterns` — audit or optimize Three.js scene
|
|
22
|
+
- `/rune webgl` — raw WebGL/shader development
|
|
23
|
+
- `/rune game-loops` — implement or audit game loop architecture
|
|
24
|
+
- `/rune physics-engine` — set up or optimize physics simulation
|
|
25
|
+
- `/rune asset-pipeline` — optimize asset loading and management
|
|
26
|
+
- Called by `cook` (L1) when game development task detected
|
|
27
|
+
|
|
28
|
+
## Skills Included
|
|
29
|
+
|
|
30
|
+
### threejs-patterns
|
|
31
|
+
|
|
32
|
+
Three.js patterns — scene setup, React Three Fiber integration, PBR materials, post-processing, performance optimization.
|
|
33
|
+
|
|
34
|
+
#### Workflow
|
|
35
|
+
|
|
36
|
+
**Step 1 — Detect Three.js setup**
|
|
37
|
+
Use Grep to find Three.js usage: `THREE.`, `useThree`, `useFrame`, `Canvas`, `@react-three/fiber`, `@react-three/drei`. Read the main scene file to understand: renderer setup, scene graph structure, camera type, and lighting model.
|
|
38
|
+
|
|
39
|
+
**Step 2 — Audit performance**
|
|
40
|
+
Check for: objects created inside `useFrame` (GC pressure), missing `dispose()` on unmount (memory leak), no frustum culling on large scenes, textures without power-of-two dimensions, unoptimized geometry (too many draw calls), and missing LOD for distant objects.
|
|
41
|
+
|
|
42
|
+
**Step 3 — Emit optimized scene**
|
|
43
|
+
Emit: properly structured R3F scene with declarative lights, memoized geometries, disposal on unmount, instanced meshes for repeated objects, and post-processing pipeline.
|
|
44
|
+
|
|
45
|
+
#### Example
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// React Three Fiber — optimized scene with instancing and post-processing
|
|
49
|
+
import { Canvas, useFrame } from '@react-three/fiber';
|
|
50
|
+
import { OrbitControls, Environment, useGLTF, Instances, Instance } from '@react-three/drei';
|
|
51
|
+
import { EffectComposer, Bloom, Vignette } from '@react-three/postprocessing';
|
|
52
|
+
import { useRef, useMemo } from 'react';
|
|
53
|
+
|
|
54
|
+
function InstancedTrees({ count = 500 }) {
|
|
55
|
+
const positions = useMemo(() =>
|
|
56
|
+
Array.from({ length: count }, () => [
|
|
57
|
+
(Math.random() - 0.5) * 100,
|
|
58
|
+
0,
|
|
59
|
+
(Math.random() - 0.5) * 100,
|
|
60
|
+
] as [number, number, number]),
|
|
61
|
+
[count]);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Instances limit={count}>
|
|
65
|
+
<cylinderGeometry args={[0.2, 0.4, 3]} />
|
|
66
|
+
<meshStandardMaterial color="#4a7c59" />
|
|
67
|
+
{positions.map((pos, i) => <Instance key={i} position={pos} />)}
|
|
68
|
+
</Instances>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function GameScene() {
|
|
73
|
+
return (
|
|
74
|
+
<Canvas camera={{ position: [0, 10, 20], fov: 60 }} gl={{ antialias: true }}>
|
|
75
|
+
<ambientLight intensity={0.3} />
|
|
76
|
+
<directionalLight position={[10, 20, 10]} intensity={1} castShadow />
|
|
77
|
+
<Environment preset="sunset" />
|
|
78
|
+
<InstancedTrees count={500} />
|
|
79
|
+
<OrbitControls maxPolarAngle={Math.PI / 2.2} />
|
|
80
|
+
<EffectComposer>
|
|
81
|
+
<Bloom intensity={0.3} luminanceThreshold={0.8} />
|
|
82
|
+
<Vignette offset={0.3} darkness={0.6} />
|
|
83
|
+
</EffectComposer>
|
|
84
|
+
</Canvas>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### webgl
|
|
92
|
+
|
|
93
|
+
WebGL patterns — shader programming, GLSL, buffer management, texture handling, instanced rendering.
|
|
94
|
+
|
|
95
|
+
#### Workflow
|
|
96
|
+
|
|
97
|
+
**Step 1 — Detect WebGL usage**
|
|
98
|
+
Use Grep to find WebGL code: `getContext('webgl`, `gl.createShader`, `gl.createProgram`, `*.glsl`, `*.vert`, `*.frag`. Read shader files and GL initialization to understand: WebGL version, shader complexity, and buffer strategy.
|
|
99
|
+
|
|
100
|
+
**Step 2 — Audit shader and buffer efficiency**
|
|
101
|
+
Check for: uniforms set every frame that don't change (use UBO), separate draw calls for identical geometry (use instancing), textures not using mipmaps, missing `gl.deleteBuffer`/`gl.deleteTexture` cleanup, and shaders with expensive per-fragment branching.
|
|
102
|
+
|
|
103
|
+
**Step 3 — Emit optimized WebGL code**
|
|
104
|
+
Emit: WebGL2 setup with proper context attributes, VAO-based buffer management, instanced rendering for repeated geometry, and GLSL shaders with documented inputs/outputs.
|
|
105
|
+
|
|
106
|
+
#### Example
|
|
107
|
+
|
|
108
|
+
```glsl
|
|
109
|
+
// Vertex shader — instanced rendering with per-instance transform
|
|
110
|
+
#version 300 es
|
|
111
|
+
layout(location = 0) in vec3 aPosition;
|
|
112
|
+
layout(location = 1) in vec3 aNormal;
|
|
113
|
+
layout(location = 2) in mat4 aInstanceMatrix; // per-instance (locations 2-5)
|
|
114
|
+
|
|
115
|
+
uniform mat4 uViewProjection;
|
|
116
|
+
|
|
117
|
+
out vec3 vNormal;
|
|
118
|
+
out vec3 vWorldPos;
|
|
119
|
+
|
|
120
|
+
void main() {
|
|
121
|
+
vec4 worldPos = aInstanceMatrix * vec4(aPosition, 1.0);
|
|
122
|
+
vWorldPos = worldPos.xyz;
|
|
123
|
+
vNormal = mat3(transpose(inverse(aInstanceMatrix))) * aNormal;
|
|
124
|
+
gl_Position = uViewProjection * worldPos;
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```glsl
|
|
129
|
+
// Fragment shader — PBR-lite with single directional light
|
|
130
|
+
#version 300 es
|
|
131
|
+
precision highp float;
|
|
132
|
+
|
|
133
|
+
in vec3 vNormal;
|
|
134
|
+
in vec3 vWorldPos;
|
|
135
|
+
out vec4 fragColor;
|
|
136
|
+
|
|
137
|
+
uniform vec3 uLightDir;
|
|
138
|
+
uniform vec3 uCameraPos;
|
|
139
|
+
uniform vec3 uBaseColor;
|
|
140
|
+
|
|
141
|
+
void main() {
|
|
142
|
+
vec3 N = normalize(vNormal);
|
|
143
|
+
vec3 L = normalize(uLightDir);
|
|
144
|
+
vec3 V = normalize(uCameraPos - vWorldPos);
|
|
145
|
+
vec3 H = normalize(L + V);
|
|
146
|
+
|
|
147
|
+
float diffuse = max(dot(N, L), 0.0);
|
|
148
|
+
float specular = pow(max(dot(N, H), 0.0), 32.0);
|
|
149
|
+
vec3 ambient = uBaseColor * 0.15;
|
|
150
|
+
|
|
151
|
+
fragColor = vec4(ambient + uBaseColor * diffuse + vec3(specular * 0.5), 1.0);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### game-loops
|
|
158
|
+
|
|
159
|
+
Game loop architecture — fixed timestep, interpolation, input handling, state machines, ECS.
|
|
160
|
+
|
|
161
|
+
#### Workflow
|
|
162
|
+
|
|
163
|
+
**Step 1 — Detect game loop pattern**
|
|
164
|
+
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.
|
|
165
|
+
|
|
166
|
+
**Step 2 — Audit loop correctness**
|
|
167
|
+
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.
|
|
168
|
+
|
|
169
|
+
**Step 3 — Emit fixed timestep loop**
|
|
170
|
+
Emit: fixed timestep (60Hz) with accumulator, interpolation for smooth rendering, decoupled input handler, and frame budget monitoring.
|
|
171
|
+
|
|
172
|
+
#### Example
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// Fixed timestep game loop with interpolation
|
|
176
|
+
const TICK_RATE = 60;
|
|
177
|
+
const TICK_DURATION = 1000 / TICK_RATE;
|
|
178
|
+
|
|
179
|
+
class GameLoop {
|
|
180
|
+
private accumulator = 0;
|
|
181
|
+
private previousTime = 0;
|
|
182
|
+
private running = false;
|
|
183
|
+
|
|
184
|
+
constructor(
|
|
185
|
+
private update: (dt: number) => void, // fixed timestep logic
|
|
186
|
+
private render: (alpha: number) => void, // interpolated rendering
|
|
187
|
+
) {}
|
|
188
|
+
|
|
189
|
+
start() {
|
|
190
|
+
this.running = true;
|
|
191
|
+
this.previousTime = performance.now();
|
|
192
|
+
requestAnimationFrame(this.tick);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private tick = (currentTime: number) => {
|
|
196
|
+
if (!this.running) return;
|
|
197
|
+
const elapsed = Math.min(currentTime - this.previousTime, 250); // cap spiral of death
|
|
198
|
+
this.previousTime = currentTime;
|
|
199
|
+
this.accumulator += elapsed;
|
|
200
|
+
|
|
201
|
+
while (this.accumulator >= TICK_DURATION) {
|
|
202
|
+
this.update(TICK_DURATION / 1000); // dt in seconds
|
|
203
|
+
this.accumulator -= TICK_DURATION;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const alpha = this.accumulator / TICK_DURATION; // interpolation factor [0, 1)
|
|
207
|
+
this.render(alpha);
|
|
208
|
+
requestAnimationFrame(this.tick);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
stop() { this.running = false; }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Usage
|
|
215
|
+
const loop = new GameLoop(
|
|
216
|
+
(dt) => { world.step(dt); entities.forEach(e => e.update(dt)); },
|
|
217
|
+
(alpha) => { renderer.render(scene, camera, alpha); },
|
|
218
|
+
);
|
|
219
|
+
loop.start();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### physics-engine
|
|
225
|
+
|
|
226
|
+
Physics integration — Rapier.js, rigid bodies, constraints, raycasting, collision callbacks, deterministic simulation.
|
|
227
|
+
|
|
228
|
+
#### Workflow
|
|
229
|
+
|
|
230
|
+
**Step 1 — Detect physics setup**
|
|
231
|
+
Use Grep to find physics libraries: `rapier`, `cannon`, `ammo`, `@dimforge/rapier3d`, `RigidBody`, `Collider`. Read physics initialization and body creation to understand: engine choice, world configuration, and collision handling.
|
|
232
|
+
|
|
233
|
+
**Step 2 — Audit physics configuration**
|
|
234
|
+
Check for: physics step tied to render frame (non-deterministic), missing collision groups (everything collides with everything), no sleep threshold (wasted CPU on static objects), raycasts without max distance (expensive), and missing body cleanup on entity destroy.
|
|
235
|
+
|
|
236
|
+
**Step 3 — Emit optimized physics**
|
|
237
|
+
Emit: Rapier.js (WASM, deterministic) setup with proper collision groups, sleep thresholds, event-driven collision callbacks, and raycasting utility.
|
|
238
|
+
|
|
239
|
+
#### Example
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// Rapier.js (WASM) — setup with collision groups and raycasting
|
|
243
|
+
import RAPIER from '@dimforge/rapier3d-compat';
|
|
244
|
+
|
|
245
|
+
await RAPIER.init();
|
|
246
|
+
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
|
|
247
|
+
|
|
248
|
+
// Collision groups: player=0x0001, enemy=0x0002, ground=0x0004, projectile=0x0008
|
|
249
|
+
const GROUPS = { PLAYER: 0x0001, ENEMY: 0x0002, GROUND: 0x0004, PROJECTILE: 0x0008 };
|
|
250
|
+
|
|
251
|
+
// Ground — static, collides with everything
|
|
252
|
+
const groundBody = world.createRigidBody(RAPIER.RigidBodyDesc.fixed().setTranslation(0, 0, 0));
|
|
253
|
+
world.createCollider(
|
|
254
|
+
RAPIER.ColliderDesc.cuboid(50, 0.1, 50)
|
|
255
|
+
.setCollisionGroups((GROUPS.GROUND << 16) | 0xFFFF),
|
|
256
|
+
groundBody,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Player — dynamic, collides with ground + enemy (not own projectiles)
|
|
260
|
+
const playerBody = world.createRigidBody(RAPIER.RigidBodyDesc.dynamic().setTranslation(0, 5, 0));
|
|
261
|
+
world.createCollider(
|
|
262
|
+
RAPIER.ColliderDesc.capsule(0.5, 0.3)
|
|
263
|
+
.setCollisionGroups((GROUPS.PLAYER << 16) | (GROUPS.GROUND | GROUPS.ENEMY))
|
|
264
|
+
.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS),
|
|
265
|
+
playerBody,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Raycast utility
|
|
269
|
+
function raycast(origin: RAPIER.Vector3, direction: RAPIER.Vector3, maxDist = 100) {
|
|
270
|
+
const ray = new RAPIER.Ray(origin, direction);
|
|
271
|
+
const hit = world.castRay(ray, maxDist, true);
|
|
272
|
+
if (hit) {
|
|
273
|
+
const point = ray.pointAt(hit.timeOfImpact);
|
|
274
|
+
return { point, normal: hit.normal, collider: hit.collider };
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### asset-pipeline
|
|
283
|
+
|
|
284
|
+
Game asset pipeline — glTF loading, texture compression, audio management, asset manifest, preloading.
|
|
285
|
+
|
|
286
|
+
#### Workflow
|
|
287
|
+
|
|
288
|
+
**Step 1 — Detect asset strategy**
|
|
289
|
+
Use Glob to find asset files: `*.gltf`, `*.glb`, `*.ktx2`, `*.basis`, `*.png` in `assets/` or `public/`. Use Grep to find loaders: `GLTFLoader`, `TextureLoader`, `KTX2Loader`, `Howler`, `Audio`. Read the loading code to understand: preloading strategy, compression, and caching.
|
|
290
|
+
|
|
291
|
+
**Step 2 — Audit asset efficiency**
|
|
292
|
+
Check for: uncompressed textures (PNG/JPG instead of KTX2/Basis), glTF without Draco compression, no asset manifest (scattered inline paths), missing preloader (assets load mid-gameplay causing stutters), audio files in WAV format (use OGG/MP3), and no LOD variants for 3D models.
|
|
293
|
+
|
|
294
|
+
**Step 3 — Emit asset pipeline**
|
|
295
|
+
Emit: asset manifest with typed entries, preloader with progress tracking, glTF loader with Draco decoder, KTX2 texture loader, and audio manager with Howler.js.
|
|
296
|
+
|
|
297
|
+
#### Example
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// Asset manifest + preloader with progress tracking
|
|
301
|
+
interface AssetManifest {
|
|
302
|
+
models: Record<string, { url: string; draco?: boolean }>;
|
|
303
|
+
textures: Record<string, { url: string; format: 'ktx2' | 'png' }>;
|
|
304
|
+
audio: Record<string, { url: string; volume?: number; loop?: boolean }>;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const MANIFEST: AssetManifest = {
|
|
308
|
+
models: {
|
|
309
|
+
player: { url: '/assets/player.glb', draco: true },
|
|
310
|
+
level1: { url: '/assets/level1.glb', draco: true },
|
|
311
|
+
},
|
|
312
|
+
textures: {
|
|
313
|
+
terrain: { url: '/assets/terrain.ktx2', format: 'ktx2' },
|
|
314
|
+
},
|
|
315
|
+
audio: {
|
|
316
|
+
bgm: { url: '/assets/bgm.ogg', volume: 0.5, loop: true },
|
|
317
|
+
jump: { url: '/assets/jump.ogg', volume: 0.8 },
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
class AssetLoader {
|
|
322
|
+
private loaded = 0;
|
|
323
|
+
private total = 0;
|
|
324
|
+
|
|
325
|
+
async loadAll(manifest: AssetManifest, onProgress: (pct: number) => void) {
|
|
326
|
+
const entries = [
|
|
327
|
+
...Object.values(manifest.models),
|
|
328
|
+
...Object.values(manifest.textures),
|
|
329
|
+
...Object.values(manifest.audio),
|
|
330
|
+
];
|
|
331
|
+
this.total = entries.length;
|
|
332
|
+
|
|
333
|
+
await Promise.all(entries.map(async (entry) => {
|
|
334
|
+
await fetch(entry.url); // preload into browser cache
|
|
335
|
+
this.loaded++;
|
|
336
|
+
onProgress(this.loaded / this.total);
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Connections
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
Calls → perf (L2): frame budget and rendering performance audit
|
|
348
|
+
Calls → asset-creator (L3): generate placeholder assets and sprites
|
|
349
|
+
Called By ← cook (L1): when game development task detected
|
|
350
|
+
Called By ← review (L2): when game code under review
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Tech Stack Support
|
|
354
|
+
|
|
355
|
+
| Engine | Rendering | Physics | ECS |
|
|
356
|
+
|--------|-----------|---------|-----|
|
|
357
|
+
| Three.js | WebGL2 / WebGPU | Rapier.js (WASM) | bitECS |
|
|
358
|
+
| React Three Fiber | Three.js (declarative) | @react-three/rapier | Custom |
|
|
359
|
+
| PixiJS | WebGL2 (2D) | Matter.js | Custom |
|
|
360
|
+
| Phaser 3 | WebGL / Canvas | Arcade / Matter | Built-in |
|
|
361
|
+
| Babylon.js | WebGL2 / WebGPU | Havok (WASM) | Built-in |
|
|
362
|
+
|
|
363
|
+
## Constraints
|
|
364
|
+
|
|
365
|
+
1. MUST use fixed timestep for physics — variable timestep causes non-deterministic simulation.
|
|
366
|
+
2. MUST dispose all GPU resources (geometries, textures, materials) on scene teardown — GPU memory leaks crash tabs.
|
|
367
|
+
3. MUST NOT create objects inside the render loop — allocate outside, reuse inside.
|
|
368
|
+
4. MUST test on target minimum hardware (mobile GPU) not just development machine.
|
|
369
|
+
5. MUST use compressed asset formats (Draco for geometry, KTX2/Basis for textures) — raw assets cause unacceptable load times.
|
|
370
|
+
|
|
371
|
+
## Sharp Edges
|
|
372
|
+
|
|
373
|
+
| Failure Mode | Severity | Mitigation |
|
|
374
|
+
|---|---|---|
|
|
375
|
+
| Objects created in useFrame/render loop cause GC stutters at 60fps | CRITICAL | Pre-allocate all vectors, quaternions, matrices outside the loop; reuse with `.set()` |
|
|
376
|
+
| GPU memory leak from undisposed textures/geometries (tab crashes after 5 minutes) | CRITICAL | Implement disposal manager; call `.dispose()` on every Three.js resource on unmount |
|
|
377
|
+
| Physics spiral of death: update takes longer than frame, accumulator grows unbounded | HIGH | Cap accumulator at 250ms (skip frames); reduce physics complexity if consistent |
|
|
378
|
+
| Shader compiles on first use causing frame drop (shader cache miss) | MEDIUM | Pre-warm shaders during loading screen; use `renderer.compile(scene, camera)` |
|
|
379
|
+
| Asset loading blocks first frame (white screen for 5+ seconds) | HIGH | Implement progressive loading with preloader UI; prioritize visible assets |
|
|
380
|
+
| Mobile GPU fails on desktop-quality shaders (WebGL context lost) | HIGH | Detect GPU tier with `detect-gpu`; provide shader LOD variants |
|
|
381
|
+
|
|
382
|
+
## Done When
|
|
383
|
+
|
|
384
|
+
- Scene renders at stable 60fps on target hardware
|
|
385
|
+
- Physics simulation is deterministic with fixed timestep
|
|
386
|
+
- All GPU resources properly disposed on cleanup
|
|
387
|
+
- Assets compressed and preloaded with progress indicator
|
|
388
|
+
- Game loop decouples update from render with interpolation
|
|
389
|
+
- Structured report emitted for each skill invoked
|
|
390
|
+
|
|
391
|
+
## Cost Profile
|
|
392
|
+
|
|
393
|
+
~10,000–20,000 tokens per full pack run (all 5 skills). Individual skill: ~2,000–4,000 tokens. Sonnet default. Use haiku for asset detection scans; escalate to sonnet for shader optimization and physics configuration.
|