synorch 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/application/doctor-service.d.ts +24 -0
- package/dist/application/doctor-service.d.ts.map +1 -0
- package/dist/application/doctor-service.js +508 -0
- package/dist/application/doctor-service.js.map +1 -0
- package/dist/application/project-discovery.d.ts +23 -0
- package/dist/application/project-discovery.d.ts.map +1 -0
- package/dist/application/project-discovery.js +741 -0
- package/dist/application/project-discovery.js.map +1 -0
- package/dist/application/skill-resolver.d.ts +25 -0
- package/dist/application/skill-resolver.d.ts.map +1 -0
- package/dist/application/skill-resolver.js +90 -0
- package/dist/application/skill-resolver.js.map +1 -0
- package/dist/application/structure-service.d.ts +11 -0
- package/dist/application/structure-service.d.ts.map +1 -0
- package/dist/application/structure-service.js +116 -0
- package/dist/application/structure-service.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +170 -0
- package/dist/cli.js.map +1 -0
- package/dist/domain/config.d.ts +210 -0
- package/dist/domain/config.d.ts.map +1 -0
- package/dist/domain/config.js +108 -0
- package/dist/domain/config.js.map +1 -0
- package/dist/domain/errors.d.ts +5 -0
- package/dist/domain/errors.d.ts.map +1 -0
- package/dist/domain/errors.js +9 -0
- package/dist/domain/errors.js.map +1 -0
- package/dist/domain/generation.d.ts +21 -0
- package/dist/domain/generation.d.ts.map +1 -0
- package/dist/domain/generation.js +2 -0
- package/dist/domain/generation.js.map +1 -0
- package/dist/domain/product.d.ts +3 -0
- package/dist/domain/product.d.ts.map +1 -0
- package/dist/domain/product.js +3 -0
- package/dist/domain/product.js.map +1 -0
- package/dist/domain/skill-packs.d.ts +39 -0
- package/dist/domain/skill-packs.d.ts.map +1 -0
- package/dist/domain/skill-packs.js +98 -0
- package/dist/domain/skill-packs.js.map +1 -0
- package/dist/domain/skill-sources.d.ts +20 -0
- package/dist/domain/skill-sources.d.ts.map +1 -0
- package/dist/domain/skill-sources.js +95 -0
- package/dist/domain/skill-sources.js.map +1 -0
- package/dist/infrastructure/bundled-skill-library.d.ts +10 -0
- package/dist/infrastructure/bundled-skill-library.d.ts.map +1 -0
- package/dist/infrastructure/bundled-skill-library.js +108 -0
- package/dist/infrastructure/bundled-skill-library.js.map +1 -0
- package/dist/infrastructure/file-system.d.ts +21 -0
- package/dist/infrastructure/file-system.d.ts.map +1 -0
- package/dist/infrastructure/file-system.js +72 -0
- package/dist/infrastructure/file-system.js.map +1 -0
- package/dist/infrastructure/serialization.d.ts +3 -0
- package/dist/infrastructure/serialization.d.ts.map +1 -0
- package/dist/infrastructure/serialization.js +11 -0
- package/dist/infrastructure/serialization.js.map +1 -0
- package/dist/templates/structure-templates.d.ts +4 -0
- package/dist/templates/structure-templates.d.ts.map +1 -0
- package/dist/templates/structure-templates.js +471 -0
- package/dist/templates/structure-templates.js.map +1 -0
- package/dist/templates/technology-skill-templates.d.ts +3 -0
- package/dist/templates/technology-skill-templates.d.ts.map +1 -0
- package/dist/templates/technology-skill-templates.js +73 -0
- package/dist/templates/technology-skill-templates.js.map +1 -0
- package/package.json +59 -0
- package/skill-sources/ingenium/NOTICE.md +11 -0
- package/skill-sources/ingenium/skills/db-schema-craft/SKILL.md +127 -0
- package/skill-sources/ingenium/skills/debug-detective/SKILL.md +67 -0
- package/skill-sources/ingenium/skills/design-system/SKILL.md +57 -0
- package/skill-sources/ingenium/skills/docs-sync/SKILL.md +68 -0
- package/skill-sources/ingenium/skills/dotnet-backend/SKILL.md +110 -0
- package/skill-sources/ingenium/skills/frontend-craft/SKILL.md +69 -0
- package/skill-sources/ingenium/skills/game-audio/SKILL.md +73 -0
- package/skill-sources/ingenium/skills/game-design/SKILL.md +92 -0
- package/skill-sources/ingenium/skills/godot-dev/SKILL.md +78 -0
- package/skill-sources/ingenium/skills/human-made-design/SKILL.md +73 -0
- package/skill-sources/ingenium/skills/java-backend/SKILL.md +98 -0
- package/skill-sources/ingenium/skills/jev/SKILL.md +150 -0
- package/skill-sources/ingenium/skills/motion-craft/SKILL.md +66 -0
- package/skill-sources/ingenium/skills/multiplayer-netcode/SKILL.md +63 -0
- package/skill-sources/ingenium/skills/node-backend/SKILL.md +113 -0
- package/skill-sources/ingenium/skills/node-backend/reference.md +144 -0
- package/skill-sources/ingenium/skills/perf-audit/SKILL.md +70 -0
- package/skill-sources/ingenium/skills/pixel-art-assets/SKILL.md +91 -0
- package/skill-sources/ingenium/skills/pixel-art-assets/scripts/px.py +169 -0
- package/skill-sources/ingenium/skills/pixel-game-dev/SKILL.md +79 -0
- package/skill-sources/ingenium/skills/project-onboard/SKILL.md +78 -0
- package/skill-sources/ingenium/skills/pwa-offline/SKILL.md +70 -0
- package/skill-sources/ingenium/skills/query-tuning/SKILL.md +137 -0
- package/skill-sources/ingenium/skills/react-modern/SKILL.md +92 -0
- package/skill-sources/ingenium/skills/refactor-safe/SKILL.md +63 -0
- package/skill-sources/ingenium/skills/release-prep/SKILL.md +57 -0
- package/skill-sources/ingenium/skills/safe-merge/SKILL.md +91 -0
- package/skill-sources/ingenium/skills/session-recap/SKILL.md +95 -0
- package/skill-sources/ingenium/skills/session-recap/scripts/extract_session.py +409 -0
- package/skill-sources/ingenium/skills/shader-vfx/SKILL.md +70 -0
- package/skill-sources/ingenium/skills/tailwind-v4-tokens/SKILL.md +165 -0
- package/skill-sources/ingenium/skills/task-conductor/SKILL.md +158 -0
- package/skill-sources/ingenium/skills/tauri-game-dev/SKILL.md +81 -0
- package/skill-sources/ingenium/skills/ui-ux-design/SKILL.md +96 -0
- package/skill-sources/ingenium/skills/vue-modern/SKILL.md +88 -0
- package/skill-sources/ingenium/skills/web-kickoff/SKILL.md +63 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: multiplayer-netcode
|
|
3
|
+
description: Multiplayer and netcode for web games - choosing the right model per genre (lockstep, snapshot interpolation, client prediction with server reconciliation, rollback), transport selection (WebSocket vs WebRTC DataChannel vs WebTransport), authoritative server design, state sync and delta compression, lag compensation, determinism traps in JavaScript, anti-cheat basics and latency simulation testing. Use when adding multiplayer or online play to a game, syncing game state over the network, fixing lag/desync/rubber-banding, or designing rooms and matchmaking. Türkçe tetikleyiciler - "multiplayer ekle", "çok oyunculu yap", "online oyun", "netcode", "lag var", "oyuncular birbirini görsün", "state senkronizasyonu", "desync oluyor", "rollback".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Multiplayer Netcode (Web Games)
|
|
7
|
+
|
|
8
|
+
You design netcode that fits the game, not the fanciest technique. First, the honest warning you always give: **multiplayer multiplies scope 3–5×** — servers, sync, edge cases, cheaters, testing. The MVP is two players in one room with the simplest sync model that fits the genre; everything else comes after that works.
|
|
9
|
+
|
|
10
|
+
Always communicate with the user in their own language.
|
|
11
|
+
|
|
12
|
+
## Phase 1 — Pick the model by genre (the decision that decides everything)
|
|
13
|
+
|
|
14
|
+
| Genre / need | Model |
|
|
15
|
+
|---|---|
|
|
16
|
+
| Turn-based (cards, board, strategy) | Plain request/response + authoritative state broadcast. No prediction needed. Start here whenever possible. |
|
|
17
|
+
| Co-op casual, slow-paced (party games, .io-lite) | Server tick + **snapshot interpolation** (clients render ~100ms in the past, interpolating between snapshots) |
|
|
18
|
+
| Action with player movement (shooters, arena) | Snapshot interpolation + **client prediction & server reconciliation** for your own player |
|
|
19
|
+
| Precise competitive 1v1 (fighting, sports) | **Rollback** (GGPO-style) — requires a deterministic simulation |
|
|
20
|
+
| Massive persistent worlds | Not an MVP. Interest management + sharding — scope this consciously later |
|
|
21
|
+
|
|
22
|
+
State the choice and its consequences before writing any code.
|
|
23
|
+
|
|
24
|
+
## Phase 2 — Transport
|
|
25
|
+
|
|
26
|
+
- **WebSocket**: ordered, reliable (TCP). Perfect for turn-based and fine for most casual real-time. Weakness: head-of-line blocking — one lost packet stalls everything behind it.
|
|
27
|
+
- **WebRTC DataChannel** (unreliable/unordered mode): UDP-like, what fast action games want; heavier setup (signaling, STUN/TURN — budget a TURN server, ~10–20% of connections need relay).
|
|
28
|
+
- **WebTransport**: the modern UDP-like option over HTTP/3; check current browser/server support before committing.
|
|
29
|
+
- Rule: **start with WebSocket**; move the *hot path only* (position updates) to unreliable transport when measurements demand it. Reliable events (chat, score, item pickup) stay on the reliable channel regardless.
|
|
30
|
+
|
|
31
|
+
## Phase 3 — Authoritative server (non-negotiable for anything competitive)
|
|
32
|
+
|
|
33
|
+
- The server owns truth: clients send **inputs/intents** ("move left", "play card 3"), never outcomes ("my HP is 100", "I won").
|
|
34
|
+
- Validate every input server-side: legal move? plausible rate? in range? Client-side checks are UX, not security.
|
|
35
|
+
- Hidden information (other players' hands, fog of war) never leaves the server — filter per recipient. Anything sent to a client is public to a cheater.
|
|
36
|
+
- Fixed **tick rate** (10–30Hz casual, 60Hz competitive): simulate on tick, broadcast snapshots on tick or every Nth tick.
|
|
37
|
+
- Room architecture first: rooms/lobbies with a max player count are how everything scales later. Frameworks: roll your own on `ws` (educational, fine for turn-based), **Colyseus** (rooms + state sync out of the box), or managed (Nakama, Hathora, PlayFab) when ops time is worth more than money.
|
|
38
|
+
|
|
39
|
+
## Phase 4 — State sync mechanics
|
|
40
|
+
|
|
41
|
+
- **Snapshots + interpolation**: server sends world state at tick rate; clients buffer ~2–3 snapshots and render other entities ~100ms in the past, interpolating. Smoothness beats freshness for everything that isn't you.
|
|
42
|
+
- **Delta compression** when snapshots get fat: send changes vs last-acked snapshot; full snapshot on join/desync. Quantize floats (positions to cm, angles to bytes) before compressing.
|
|
43
|
+
- **Client prediction (your own player)**: apply your input locally immediately; tag inputs with sequence numbers; server echoes last processed seq + authoritative state; on mismatch, rewind to server state and **replay unacked inputs** (reconciliation). Rubber-banding = reconciliation missing or broken.
|
|
44
|
+
- **Lag compensation (hit detection)**: server rewinds targets to where the shooter *saw* them (timestamped shots) before resolving hits — otherwise high-ping players can't hit anything.
|
|
45
|
+
- Interest management once rooms grow: send each client only what it can perceive.
|
|
46
|
+
|
|
47
|
+
## Phase 5 — Rollback (only for the genres that need it)
|
|
48
|
+
|
|
49
|
+
- Requires a **deterministic** simulation: same inputs → identical state on every client.
|
|
50
|
+
- Loop: predict remote inputs (usually "same as last frame") → when real input arrives late, rewind to that frame, re-simulate to present. Needs: fixed timestep, fully serializable game state, sim decoupled from rendering, state save/load fast enough to re-run several frames in one frame budget.
|
|
51
|
+
- **JS determinism traps**: `Math.random` → seeded PRNG (e.g. mulberry32) owned by the sim; `Date.now`/`performance.now` → tick counters only; unordered object/Map iteration feeding gameplay decisions → sort first; floating point is generally consistent same-engine but avoid `Math.sin/cos` accumulation drift — prefer integer/fixed-point for the critical sim state where feasible.
|
|
52
|
+
- Desync detection: hash the game state every N ticks, compare between peers; on mismatch log the first diverging tick — that's your bug's address.
|
|
53
|
+
|
|
54
|
+
## Phase 6 — Test like the network is hostile
|
|
55
|
+
|
|
56
|
+
- Simulated latency/jitter/loss from day one (Chrome DevTools throttling; `tc netem` on Linux; toxiproxy) — netcode that only met localhost is untested. Test at 80ms, 150ms, 250ms with 1–3% loss.
|
|
57
|
+
- Bots that send random-but-legal inputs at full rate: your load test and your fuzzer.
|
|
58
|
+
- The two-browser-windows setup is the daily driver; add one real remote friend before believing anything ships.
|
|
59
|
+
|
|
60
|
+
## Anti-patterns
|
|
61
|
+
|
|
62
|
+
Trusting the client with outcomes; TCP-only for fast action then blaming "lag"; per-frame unthrottled sends (send on tick); no seq numbers ("it works on LAN"); building matchmaking before two friends can share a room code; retrofitting determinism for rollback after the sim is built (it's a rewrite — decide up front).
|
|
63
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: node-backend
|
|
3
|
+
description: Node.js backend engineering in TypeScript, current as of September 2026 (Node 24 LTS, NestJS 11, Express 5, Fastify 5) - choosing between NestJS and a plain Express/Fastify layout, a layered structure that survives growth (route, service, repository, no logic in the handler), async error handling that actually catches, validation at the boundary with Zod, config loaded and validated once at boot, graceful shutdown and the single-process realities (event loop blocking, unhandled rejections, memory), plus the tooling layer - pnpm workspaces, tsconfig that matches the runtime, ESM versus CJS, native test runner or Vitest, debugging and profiling. Framework recipes and NestJS specifics live in reference.md. Use when building, reviewing or debugging a Node/TypeScript API, choosing a Node backend framework or project structure, fixing async or performance problems in Node, or setting up Node tooling. Türkçe tetikleyiciler - "node backend yaz", "nestjs projesi", "express api", "fastify servisi", "typescript backend yapısı", "node performans sorunu", "async hata yakalanmıyor", "esm cjs sorunu", "pnpm workspace kur", "node projesini debug et".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Node Backend
|
|
7
|
+
|
|
8
|
+
You build Node services as boring, layered, typed programs: one process, an event loop you refuse to block, errors that cannot escape silently, and a boundary where every untrusted value is parsed before it reaches your code.
|
|
9
|
+
|
|
10
|
+
Always communicate with the user in their own language.
|
|
11
|
+
|
|
12
|
+
## Freshness protocol
|
|
13
|
+
|
|
14
|
+
Current as of **September 2026**: Node 24 LTS (native TypeScript type stripping, stable `node --test`, built-in `fetch`, `node:sqlite`), NestJS 11, Express 5, Fastify 5, TypeScript 5.x, pnpm as the default package manager. Verify against nodejs.org/en/about/releases and the framework changelogs before locking a version decision — if reality moved past this skill, reality wins and say so.
|
|
15
|
+
|
|
16
|
+
## Existing codebase protocol
|
|
17
|
+
|
|
18
|
+
Detect before you write: Node version from `.nvmrc`/`engines`, package manager from the lockfile, `"type"` field and `tsconfig` module settings, framework from the dependencies, and the actual layering from two real route files. Match what exists. A Nest-style decorator module dropped into an Express codebase, or a service layer added to only one route, makes the codebase harder to read, not better.
|
|
19
|
+
|
|
20
|
+
## Framework choice
|
|
21
|
+
|
|
22
|
+
| Situation | Pick |
|
|
23
|
+
|---|---|
|
|
24
|
+
| Team backend, many modules, needs enforced structure and DI, likely to outlive its authors | **NestJS** — the opinionation is the product |
|
|
25
|
+
| Small-to-mid service, throughput matters, you want to see every line of the pipeline | **Fastify** — schema-first, fast, sane plugin encapsulation |
|
|
26
|
+
| Tiny service, glue, or an existing Express codebase | **Express 5** — universal, minimal, now with async error propagation |
|
|
27
|
+
| Full-stack app already in Next.js/Nuxt | Its server layer first; add a separate service only when there is a real reason |
|
|
28
|
+
|
|
29
|
+
Whatever the framework: the framework is delivery, not architecture. Business logic lives in plain functions and classes that can be tested without starting an HTTP server.
|
|
30
|
+
|
|
31
|
+
## Structure that survives growth
|
|
32
|
+
|
|
33
|
+
Organize **by feature**, not by technical role:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
src/
|
|
37
|
+
orders/ orders.routes.ts orders.service.ts orders.repository.ts orders.schema.ts
|
|
38
|
+
billing/ ...
|
|
39
|
+
shared/ config.ts db.ts logger.ts errors.ts
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **The handler does four things**: parse input, call one service function, map the result, return. No queries, no branching business rules, no `if (user.role === ...)` policy checks inline.
|
|
43
|
+
- **The service owns the use case** and knows nothing about HTTP — no `req`, no `res`, no status codes crossing into it. That single rule is what makes the logic testable and reusable from a job or a CLI.
|
|
44
|
+
- **The repository owns data access.** SQL or ORM calls do not appear in services; swapping Prisma for raw SQL should touch one file per feature.
|
|
45
|
+
- **Errors are typed domain values** (`NotFoundError`, `ConflictError`, `ValidationError`), mapped to HTTP status codes in exactly one place. Never `throw new Error("something")` and pattern-match on the message.
|
|
46
|
+
|
|
47
|
+
## Validation and typing at the boundary
|
|
48
|
+
|
|
49
|
+
- Every external input — body, query, params, headers, environment, third-party responses — is **parsed with Zod at the edge**, and the parsed type flows inward. `req.body as CreateOrderDto` is a lie the compiler cannot catch.
|
|
50
|
+
- Derive TypeScript types from the schema (`z.infer`) so there is one source of truth, not a schema and an interface drifting apart.
|
|
51
|
+
- `strict: true` in tsconfig, and no `any` at boundaries. `unknown` plus a parse is the honest alternative.
|
|
52
|
+
- Validated config once at boot: parse `process.env` through a schema in `config.ts` and export a typed object. Reading `process.env.FOO` deep inside a module is how a service boots fine and dies on a code path nobody exercised.
|
|
53
|
+
|
|
54
|
+
## Async and errors
|
|
55
|
+
|
|
56
|
+
- **`async`/`await` everywhere, with every promise awaited or explicitly handled.** A floating promise is a lost error; enable `no-floating-promises` in ESLint.
|
|
57
|
+
- Independent work runs with `Promise.all`; `Promise.allSettled` when partial failure is acceptable. Sequential `await`s for independent calls are latency you chose.
|
|
58
|
+
- Express 5 forwards rejected promises from async handlers to the error middleware — but only if the handler is `async` and you did not swallow the rejection. Fastify and Nest handle it natively. One error handler at the end of the pipeline, always registered last.
|
|
59
|
+
- Register `process.on('unhandledRejection')` and `uncaughtException` to log with context and exit; do not keep a process running in an unknown state.
|
|
60
|
+
- **Add `AbortSignal`/timeouts to every outbound call.** `fetch` without a timeout hangs until the socket dies, and that is how one slow dependency saturates your event loop.
|
|
61
|
+
- Retry only idempotent operations, with backoff and a cap.
|
|
62
|
+
|
|
63
|
+
## Single-process realities
|
|
64
|
+
|
|
65
|
+
- **Never block the event loop.** Synchronous crypto, big `JSON.parse` on megabyte payloads, `readFileSync` in a request path, and heavy loops all stall every concurrent request. Move CPU work to a `worker_threads` pool or a separate job; stream large payloads instead of buffering them.
|
|
66
|
+
- **Stream, do not accumulate.** Building a 200k-row array in memory to send as JSON is a memory incident waiting for traffic; use streams or pagination.
|
|
67
|
+
- **Graceful shutdown** on `SIGTERM`: stop accepting connections, finish in-flight requests with a deadline, close the DB pool, then exit. Without it, every deploy drops requests.
|
|
68
|
+
- **Connection pools are configured, not defaulted.** Pool size, idle timeout and statement timeout are deployment decisions — see **query-tuning**.
|
|
69
|
+
- Structured JSON logging with pino, one request-scoped child logger carrying a request id. `console.log` is not logging in a service.
|
|
70
|
+
- Health endpoints (`/health/live`, `/health/ready`) and readiness that actually checks the database.
|
|
71
|
+
|
|
72
|
+
## Tooling layer
|
|
73
|
+
|
|
74
|
+
| Goal | Do this |
|
|
75
|
+
|---|---|
|
|
76
|
+
| Package manager | **pnpm**, with `packageManager` pinned in `package.json`; workspaces for a monorepo |
|
|
77
|
+
| Node version | `.nvmrc` plus `engines` — one version across dev, CI and the image |
|
|
78
|
+
| Module system | ESM (`"type": "module"`) for new projects; do not mix. In ESM, relative imports need explicit extensions |
|
|
79
|
+
| TypeScript config | `strict`, `moduleResolution: "bundler"` or `"nodenext"` matched to the runtime, `isolatedModules`, `noUncheckedIndexedAccess` |
|
|
80
|
+
| Dev loop | `node --watch` with type stripping for simple services; `tsx watch` when you need transpile-time features |
|
|
81
|
+
| Build | `tsc` for libraries; `tsup`/`esbuild` for a bundled service image |
|
|
82
|
+
| Tests | Native `node --test` for lean projects, **Vitest** when you want watch mode, mocks and coverage without ceremony |
|
|
83
|
+
| Debug | `node --inspect-brk`, then attach from the editor. Faster than a console.log bisect, every time |
|
|
84
|
+
| Profile | `node --cpu-prof` / `--heap-prof`, or Clinic.js — read a flame graph before optimizing anything |
|
|
85
|
+
| Lint | ESLint flat config with `@typescript-eslint`, `no-floating-promises` and `require-await` on |
|
|
86
|
+
|
|
87
|
+
## Data access
|
|
88
|
+
|
|
89
|
+
Prisma for speed of development and migrations, Drizzle when you want SQL you can read and full type inference over it, raw `pg`/`postgres.js` for tight control. Whichever you pick: queries live in repositories, N+1 is a bug you look for on every list endpoint, and transactions wrap a use case — never a whole request. Load **query-tuning** for query and index work, **db-schema-craft** for modeling.
|
|
90
|
+
|
|
91
|
+
## Testing
|
|
92
|
+
|
|
93
|
+
- Domain logic tested as plain functions, no HTTP, no database.
|
|
94
|
+
- Integration tests hit the real app through `supertest`/`app.inject()` against a **Testcontainers** database. SQLite standing in for PostgreSQL will pass tests production fails.
|
|
95
|
+
- Each test owns its data and cleans up; shared mutable fixtures produce order-dependent suites that fail only in CI.
|
|
96
|
+
|
|
97
|
+
## Framework recipes
|
|
98
|
+
|
|
99
|
+
NestJS module/provider/DI specifics, Express 5 and Fastify pipeline recipes, and the graceful-shutdown and error-mapping snippets are in [reference.md](reference.md). Load it when you are actually writing framework-level wiring.
|
|
100
|
+
|
|
101
|
+
## Rules
|
|
102
|
+
|
|
103
|
+
1. Detect Node version, module system and framework before writing code; match the repo's layering.
|
|
104
|
+
2. Handlers parse, delegate, map, return — no business logic and no data access in a route handler.
|
|
105
|
+
3. Services never see `req`/`res`; HTTP concepts stop at the boundary.
|
|
106
|
+
4. Every external input is parsed with a schema at the edge; no casting untrusted data.
|
|
107
|
+
5. Config is validated once at boot and exported typed; no scattered `process.env` reads.
|
|
108
|
+
6. No floating promises, no unhandled rejections, no `.then` chains mixed into `async` code.
|
|
109
|
+
7. Every outbound call has a timeout or an `AbortSignal`.
|
|
110
|
+
8. Nothing blocks the event loop in a request path; CPU work moves to a worker.
|
|
111
|
+
9. Errors are typed domain values mapped to status codes in exactly one place.
|
|
112
|
+
10. Graceful shutdown, structured logging and a real readiness check are part of the service, not extras.
|
|
113
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Node Backend — Framework Recipes
|
|
2
|
+
|
|
3
|
+
Load this when writing framework-level wiring. Everything here assumes the rules in SKILL.md still hold: handlers stay thin, services never see `req`/`res`, inputs are parsed at the edge.
|
|
4
|
+
|
|
5
|
+
## NestJS 11
|
|
6
|
+
|
|
7
|
+
### Module boundaries
|
|
8
|
+
|
|
9
|
+
One module per feature, exporting only what other modules legitimately need. A module that exports every provider is a namespace, not a boundary.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
@Module({
|
|
13
|
+
imports: [TypeOrmModule.forFeature([Order]), BillingModule],
|
|
14
|
+
controllers: [OrdersController],
|
|
15
|
+
providers: [OrdersService, OrdersRepository],
|
|
16
|
+
exports: [OrdersService],
|
|
17
|
+
})
|
|
18
|
+
export class OrdersModule {}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- Circular imports between modules mean the boundary is wrong. `forwardRef` is a patch for a design problem — fix the direction, extract a shared module, or move the shared concept down.
|
|
22
|
+
- `@Global()` is almost always a mistake; the exception is a genuinely cross-cutting infrastructure module (config, logger) registered once at the root.
|
|
23
|
+
|
|
24
|
+
### Providers and scope
|
|
25
|
+
|
|
26
|
+
- Default provider scope is **singleton**. `Scope.REQUEST` propagates up the whole injection chain and destroys performance — use it only when you truly need per-request state, and prefer `AsyncLocalStorage` (via `nestjs-cls`) for request context instead.
|
|
27
|
+
- Inject by class token wherever possible; string tokens are for dynamic and third-party wiring, and they lose type safety.
|
|
28
|
+
- Custom providers (`useFactory`, `useClass`, `useValue`) are how you swap implementations per environment — not `if (process.env.NODE_ENV)` inside the service.
|
|
29
|
+
|
|
30
|
+
### The pipeline, in order
|
|
31
|
+
|
|
32
|
+
`Middleware → Guards → Interceptors (before) → Pipes → Handler → Interceptors (after) → Exception filters`
|
|
33
|
+
|
|
34
|
+
| Concern | Belongs in |
|
|
35
|
+
|---|---|
|
|
36
|
+
| Is this caller allowed | **Guard** (`CanActivate`) |
|
|
37
|
+
| Parse and validate the payload | **Pipe** (`ZodValidationPipe` or `ValidationPipe`) |
|
|
38
|
+
| Timing, logging, response envelope, caching | **Interceptor** |
|
|
39
|
+
| Turning a domain error into an HTTP response | **Exception filter**, registered once globally |
|
|
40
|
+
| Raw request concerns (correlation id, body capture) | **Middleware** |
|
|
41
|
+
|
|
42
|
+
Register the validation pipe and the exception filter globally in `main.ts`, never per-controller by copy-paste.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
app.useGlobalPipes(new ZodValidationPipe());
|
|
46
|
+
app.useGlobalFilters(new DomainExceptionFilter());
|
|
47
|
+
app.enableShutdownHooks();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Validation with Zod
|
|
51
|
+
|
|
52
|
+
Define the schema next to the feature and derive the DTO type from it; do not maintain a `class-validator` DTO *and* a schema.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
export const createOrderSchema = z.object({
|
|
56
|
+
customerId: z.uuid(),
|
|
57
|
+
lines: z.array(z.object({ sku: z.string().min(1), qty: z.int().positive() })).min(1),
|
|
58
|
+
});
|
|
59
|
+
export type CreateOrder = z.infer<typeof createOrderSchema>;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If the project already standardizes on `class-validator`, stay with it — consistency wins.
|
|
63
|
+
|
|
64
|
+
### Configuration
|
|
65
|
+
|
|
66
|
+
`ConfigModule.forRoot({ validate, isGlobal: true, cache: true })` with a schema-based `validate` function, then inject a typed config service. Never read `process.env` inside a provider.
|
|
67
|
+
|
|
68
|
+
### Testing
|
|
69
|
+
|
|
70
|
+
- Unit: instantiate the service directly with fakes. `Test.createTestingModule` for one class is ceremony.
|
|
71
|
+
- Integration: build the real module graph, override only the boundary providers, hit it with `supertest`. Use Testcontainers for the database.
|
|
72
|
+
- `app.close()` in `afterAll`, or the suite leaks handles and hangs CI.
|
|
73
|
+
|
|
74
|
+
### Common Nest mistakes
|
|
75
|
+
|
|
76
|
+
Business logic in controllers; request-scoped providers used casually; `forwardRef` instead of fixing a boundary; entities returned straight from controllers; `@Global()` on feature modules; catching errors in the service to return `null`, which erases the reason the operation failed.
|
|
77
|
+
|
|
78
|
+
## Fastify 5
|
|
79
|
+
|
|
80
|
+
- **Schema-first**: attach JSON Schema for body, querystring, params and response. Response schemas are not documentation — Fastify uses them to serialize, which is both faster and a guarantee you never leak an internal field.
|
|
81
|
+
- Use `fastify-type-provider-zod` (or the TypeBox provider) so one schema gives you validation, serialization and inference.
|
|
82
|
+
- **Plugins encapsulate.** Anything registered inside a plugin is invisible outside it; to share a decorator or hook, wrap with `fastify-plugin`. Most "my decorator is undefined" bugs are this rule working as designed.
|
|
83
|
+
- Hooks map cleanly to concerns: `onRequest` for auth, `preValidation` for shaping input, `preHandler` for authorization, `onSend` for response mutation, `onError` for logging.
|
|
84
|
+
- `setErrorHandler` once, at the root, mapping domain errors to status codes.
|
|
85
|
+
- Fastify ships pino: use `request.log`, which already carries the request id.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
app.get('/orders/:id', { schema: { params: idParams, response: { 200: orderResponse } } },
|
|
89
|
+
async (req) => toResponse(await orders.byId(req.params.id)));
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Express 5
|
|
93
|
+
|
|
94
|
+
- Async handlers that reject now reach the error middleware — no `express-async-handler` wrapper needed. Non-promise callbacks still do not; call `next(err)`.
|
|
95
|
+
- The error handler is the **last** `app.use`, takes four arguments, and is the only place that turns a domain error into a status code.
|
|
96
|
+
- Path matching changed in 5: no bare `*` wildcards (`/*` becomes `/*splat`), and optional-parameter syntax is stricter. This is the most common Express 4→5 upgrade break.
|
|
97
|
+
- Order is behavior: security headers (`helmet`) → cors → body parsers with an explicit `limit` → routes → 404 handler → error handler.
|
|
98
|
+
- `express.json({ limit: '1mb' })` — an unbounded body parser is a denial-of-service surface.
|
|
99
|
+
- Router per feature (`ordersRouter`), mounted at one path. A 600-line `app.ts` is the failure mode Express invites.
|
|
100
|
+
|
|
101
|
+
## Error mapping (any framework)
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
export class DomainError extends Error {
|
|
105
|
+
constructor(message: string, readonly code: string, readonly status: number) { super(message); }
|
|
106
|
+
}
|
|
107
|
+
export class NotFoundError extends DomainError {
|
|
108
|
+
constructor(what: string) { super(`${what} not found`, 'not_found', 404); }
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
One mapper at the boundary turns these into an RFC 9457 body:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{ "type": "about:blank", "title": "not_found", "status": 404, "detail": "Order not found", "instance": "/orders/42" }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Unknown errors become a 500 with a correlation id in the body and the full stack in the log — never the stack in the response.
|
|
119
|
+
|
|
120
|
+
## Graceful shutdown
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const shutdown = async (signal: string) => {
|
|
124
|
+
logger.info({ signal }, 'shutting down');
|
|
125
|
+
const timer = setTimeout(() => process.exit(1), 10_000).unref();
|
|
126
|
+
try { await app.close(); await db.end(); clearTimeout(timer); process.exit(0); }
|
|
127
|
+
catch (err) { logger.error({ err }, 'shutdown failed'); process.exit(1); }
|
|
128
|
+
};
|
|
129
|
+
for (const s of ['SIGTERM', 'SIGINT']) process.on(s, () => void shutdown(s));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The hard-exit timer matters: without it a hung connection makes the pod hang until the orchestrator kills it, which looks exactly like a crash.
|
|
133
|
+
|
|
134
|
+
## Monorepo layout (pnpm workspaces)
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
pnpm-workspace.yaml
|
|
138
|
+
apps/api/ the service
|
|
139
|
+
packages/contracts/ Zod schemas + inferred types shared with the frontend
|
|
140
|
+
packages/config/ tsconfig + eslint bases
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`packages/contracts` is the highest-leverage package in a full-stack repo: the API validates with the same schema the frontend's client types are inferred from, so a contract change breaks the build instead of production. Use `workspace:*` for internal dependencies and keep a single TypeScript base config.
|
|
144
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: perf-audit
|
|
3
|
+
description: Measurement-first web performance audit and optimization - Core Web Vitals (LCP, INP, CLS) diagnosis, Lighthouse and DevTools profiling, bundle size diet (code splitting, tree shaking, dependency replacement), image/font/third-party optimization, caching and network strategy, and runtime jank hunting. Use when a site or app feels slow, the bundle is too big, Core Web Vitals fail, load times are bad, or asked to audit or improve web performance. Türkçe tetikleyiciler - "site yavaş", "performans denetimi yap", "bundle'ı küçült", "sayfa geç açılıyor", "web vitals düzelt", "performansı iyileştir", "neden bu kadar yavaş yükleniyor".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Perf Audit
|
|
7
|
+
|
|
8
|
+
You are a web performance engineer. Prime directive: **measure, change one thing, measure again.** No cargo-cult optimization — every recommendation is tied to a number you observed, and every fix is verified by the same number moving.
|
|
9
|
+
|
|
10
|
+
Always communicate with the user in their own language.
|
|
11
|
+
|
|
12
|
+
## Phase 1 — Baseline before touching anything
|
|
13
|
+
|
|
14
|
+
- Run Lighthouse in mobile mode with throttling (that is where users suffer); record LCP, INP/TBT, CLS, transfer sizes.
|
|
15
|
+
- Pull field data if it exists (CrUX, analytics RUM) — lab and field can disagree; field wins.
|
|
16
|
+
- Take a DevTools Performance trace of the complained-about interaction.
|
|
17
|
+
- Identify the ONE metric behind the user's actual complaint ("slow to open" → LCP/TTFB; "laggy when I click" → INP; "things jump around" → CLS; "slow after load" → runtime). That metric is the audit's spine.
|
|
18
|
+
|
|
19
|
+
## Phase 2 — Diagnose by metric
|
|
20
|
+
|
|
21
|
+
**LCP** (slow first paint of the main content):
|
|
22
|
+
- Find what the LCP element actually is (Lighthouse tells you) — optimize *that*, not everything.
|
|
23
|
+
- Walk the chain in order: TTFB (server/CDN/redirects) → render-blocking CSS/JS → resource load time (is the hero image discoverable early? `fetchpriority="high"`, preload, no lazy-loading the LCP image) → client render (SSR/streaming vs client-only rendering).
|
|
24
|
+
|
|
25
|
+
**INP** (slow response to interaction):
|
|
26
|
+
- Performance trace → long tasks (>50ms) on the main thread; find who owns them.
|
|
27
|
+
- Usual suspects: hydration cost, heavy event handlers, synchronous state cascades, third-party scripts. Fixes: break up tasks (`scheduler.yield`/`setTimeout` chunking), defer non-critical JS, debounce, move compute to a Web Worker.
|
|
28
|
+
|
|
29
|
+
**CLS** (layout shift):
|
|
30
|
+
- Images/embeds without dimensions → always set width/height or aspect-ratio.
|
|
31
|
+
- Late-injected banners/ads → reserve the space.
|
|
32
|
+
- Web font swap reflow → see fonts below.
|
|
33
|
+
|
|
34
|
+
## Phase 3 — Bundle diet
|
|
35
|
+
|
|
36
|
+
- Visualize first: source-map-explorer, rollup-plugin-visualizer, or the framework's analyzer. Name the top 5 offenders by size.
|
|
37
|
+
- Route-level code splitting; dynamic-import anything below the fold or behind interaction (modals, editors, charts).
|
|
38
|
+
- Replace heavyweights with evidence: check bundlephobia-style cost before/after (classic wins - moment→dayjs, lodash→per-method or native, big date/chart/icon libraries → scoped imports).
|
|
39
|
+
- Tree-shaking blockers: barrel files re-exporting everything, packages without `sideEffects: false`, CommonJS-only deps.
|
|
40
|
+
- Kill duplicate dependencies (two versions of the same lib) — check the lockfile.
|
|
41
|
+
|
|
42
|
+
## Phase 4 — Assets
|
|
43
|
+
|
|
44
|
+
- **Images**: modern formats (AVIF/WebP with fallback), responsive `srcset/sizes`, lazy-load everything *except* the LCP image, CDN resizing — never ship a 4000px original into a 400px slot.
|
|
45
|
+
- **Fonts**: woff2 only, subset to used glyphs, self-host, `font-display: swap` (or `optional` for non-brand text), preload the one critical font, cap families × weights.
|
|
46
|
+
- **Third parties**: inventory them with a trace; defer what you can, facade-pattern heavy embeds (YouTube, maps, chat widgets load on interaction), delete what nobody remembers adding.
|
|
47
|
+
|
|
48
|
+
## Phase 5 — Network and caching
|
|
49
|
+
|
|
50
|
+
- Hashed immutable static assets with long-lived `Cache-Control`; correct caching on HTML (short/no-store) vs assets (immutable).
|
|
51
|
+
- Compression (brotli), HTTP/2+, `preconnect` to critical origins, no redirect chains on the critical path.
|
|
52
|
+
|
|
53
|
+
## Phase 6 — Runtime jank (after load)
|
|
54
|
+
|
|
55
|
+
- Long lists → virtualization.
|
|
56
|
+
- Layout thrash → batch DOM reads and writes; never interleave in a loop.
|
|
57
|
+
- Animations → transform/opacity only (hand off to the motion-craft skill for animation work).
|
|
58
|
+
- Heavy compute → Web Worker. Canvas/game loops → stay inside the 16.6ms frame budget; no allocations per frame.
|
|
59
|
+
- React-specific re-render storms → defer to the deka-engineering-react performance-optimization skill if installed; this audit stays at the platform level.
|
|
60
|
+
|
|
61
|
+
## Phase 7 — Report
|
|
62
|
+
|
|
63
|
+
Before/after table per metric, what was changed and why, and the ranked list of remaining opportunities with expected impact (high/medium/low) so the user can stop at the right point of diminishing returns.
|
|
64
|
+
|
|
65
|
+
## Rules
|
|
66
|
+
|
|
67
|
+
- One change per measurement cycle when verifying; batched fixes get batched credit and hide regressions.
|
|
68
|
+
- Never trade correctness or accessibility for speed silently — flag the trade-off.
|
|
69
|
+
- Do not chase a 100 score for its own sake; chase the user-felt metric.
|
|
70
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixel-art-assets
|
|
3
|
+
description: Create pixel art game assets that read as human-crafted, not AI-generated - style bible first (locked palette with hue-shifted ramps, one outline rule, one light source, one pixel density), craft fundamentals (silhouettes, clusters, hand anti-aliasing, dithering discipline), three production routes (hand-drawn in Aseprite, palette-constrained scripted generation, diffusion output rescued via downscale-quantize-handfix with the bundled px.py tool), recipes per asset type (characters, animation frames, tilesets, UI, VFX) and engine-ready export. Use when creating, generating, cleaning or reviewing pixel art sprites, tiles, animations, icons or any game asset, or building a consistent asset set. Türkçe tetikleyiciler - "pixel art asset üret", "sprite çiz", "tileset yap", "karakter sprite'ı oluştur", "ai ile asset üret", "asset'i temizle", "insan çizmiş gibi dursun", "assetler tutarlı olsun", "animasyon frame'leri".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Pixel Art Assets
|
|
7
|
+
|
|
8
|
+
You produce pixel art that reads as deliberately crafted by a human. The human-made look is not a filter — it is **constraints applied consistently**: one pixel grid, one locked palette, one outline rule, one light source, across every asset in the set. AI output becomes raw material that gets disciplined into the system, never the final word.
|
|
9
|
+
|
|
10
|
+
Always communicate with the user in their own language.
|
|
11
|
+
|
|
12
|
+
## Why AI pixel art looks AI (the tell catalog)
|
|
13
|
+
|
|
14
|
+
Diagnose these before fixing anything — three or more and the asset reads generated:
|
|
15
|
+
|
|
16
|
+
- **Fake pixel grid**: "pixels" of varying sizes and alignments (diffusion models paint pixel-*style*, not pixels).
|
|
17
|
+
- **Color flood**: hundreds of colors where an artist uses 16; muddy gradients inside what should be flat clusters.
|
|
18
|
+
- **Straight-darker shadows**: shading that only darkens the same hue — no hue shift (the single loudest tell).
|
|
19
|
+
- **No committed light source**; over-rendered noise and melty, almost-symmetric detail.
|
|
20
|
+
- **Soft alpha edges** and glow halos around sprites (sticker look).
|
|
21
|
+
- **Style drift**: each asset in the set slightly different in outline, saturation, density or proportion.
|
|
22
|
+
|
|
23
|
+
## Phase 0 — The style bible (before ANY asset exists)
|
|
24
|
+
|
|
25
|
+
Write `assets/STYLE.md` in the game repo and re-read it at the start of every asset session. Template:
|
|
26
|
+
|
|
27
|
+
```markdown
|
|
28
|
+
# <Game> Pixel Art Style Bible
|
|
29
|
+
- Grid: tiles 16x16, characters 32x32 (body ~24px tall), items/icons 16x16, UI at 1x
|
|
30
|
+
- Palette: <name/link or hex list - 16-32 colors TOTAL, locked>
|
|
31
|
+
- Ramps: 3-5 shades per material; shadows shift cool (toward blue/violet),
|
|
32
|
+
highlights shift warm (toward yellow); saturation peaks in midtones
|
|
33
|
+
- Outline: <pick ONE - full black / selout (colored per neighbor) / lineless>
|
|
34
|
+
- Light: top-left, always
|
|
35
|
+
- Proportions: <e.g. 1:1 head-to-body chibi at 32px; 3/4 top-down view>
|
|
36
|
+
- Alpha: binary (0 or 255) - no soft edges, no glows
|
|
37
|
+
- Dithering: <e.g. sparse checkerboard for large gradients only / none>
|
|
38
|
+
- AA: interior-only, hand-placed, max 1px against outline
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Palette guidance: start from a proven palette (Lospec is the library of record — e.g. a 16–32 color general palette) or build ramps by hand. Fewer colors is a *feature*: constraints create the crafted look.
|
|
42
|
+
|
|
43
|
+
## Craft fundamentals (what your hands enforce)
|
|
44
|
+
|
|
45
|
+
- **Silhouette first**: fill the sprite solid black; if it isn't readable at 1x in-game size, no amount of detail will save it. Design silhouette → big shapes → detail, in that order.
|
|
46
|
+
- **Clusters, not confetti**: group pixels into meaningful shapes of one ramp step; scattered single-pixel noise reads as texture spam. Every pixel is a decision.
|
|
47
|
+
- **Pixel-perfect lines**: no doubled pixels on diagonals, consistent staircase rhythm on curves (1-2-3 step progressions, not 1-3-1).
|
|
48
|
+
- **Hue-shifted ramps**: in HSV terms per step darker — value down, hue rotated 8–20° toward cool; per step lighter — value up, hue toward warm; saturation highest in the middle of the ramp. This is 80% of "looks hand-painted".
|
|
49
|
+
- **Hand anti-aliasing**: a few midtone pixels softening hard interior curves — never automatic AA, never against transparent edges.
|
|
50
|
+
- **Dithering with intent**: checkerboard/Bayer for large gradient fields (sky, vignette) and retro flavor; modern clean styles use almost none. Random dithering never.
|
|
51
|
+
- **Anti-patterns**: pillow shading (concentric rings of light regardless of light source), banding (parallel ramp lines hugging an outline), outline-color inconsistency, mixed pixel densities in one scene (a 1x sprite on a 2x background breaks the world).
|
|
52
|
+
- **Readability hierarchy**: gameplay-critical elements get the strongest value contrast and the most saturated colors; backgrounds recede (lower contrast, cooler, desaturated). Reserve one accent color for interactables.
|
|
53
|
+
|
|
54
|
+
## Three production routes (choose per asset)
|
|
55
|
+
|
|
56
|
+
| Asset | Route |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Player, key characters, hero animations | **A — hand-drawn** (highest craft, it's the face of the game) |
|
|
59
|
+
| Tiles, props, items, icons, UI, particles | **B — scripted generation** (perfect grid + palette by construction) |
|
|
60
|
+
| Concept art, mood refs, large backgrounds | **C — diffusion + rescue** (raw material, never final) |
|
|
61
|
+
|
|
62
|
+
**Route A — Aseprite.** Master files are `.aseprite` in `assets/src/`. Work in Indexed mode with the style-bible palette loaded (colors outside the palette become *impossible*). 1px pencil, shading ink for ramp work, onion skin for animation, tags per animation, export via CLI for repeatability.
|
|
63
|
+
|
|
64
|
+
**Route B — scripted (true pixel art from code).** For grid-friendly assets, write a small script (Python/Pillow or JS canvas) that draws on the exact grid with the style-bible palette as named constants — output is pixel-perfect by construction, and variants are a loop (6 grass tiles with shuffled detail placement). Iterate: generate → upscale preview (8x) → adjust script → regenerate. This beats diffusion for consistency on simple assets and is fully reproducible.
|
|
65
|
+
|
|
66
|
+
**Route C — diffusion rescue pipeline.** When using an image model for complex raw material:
|
|
67
|
+
1. Generate large, one subject, plain background, strong silhouette; ask for flat shading.
|
|
68
|
+
2. Downscale to the true grid and lock to palette with the bundled tool: `python "${CLAUDE_SKILL_DIR}/scripts/px.py" quantize raw.png --grid 32x32 --method box --palette assets/palette.hex` (BOX averaging then palette-snap survives fake-grid noise better than nearest).
|
|
69
|
+
3. **The mandatory hand pass** — this is where "human-made" happens: fix the silhouette, re-shade with the committed light source and hue-shifted ramps, apply the outline rule, merge noise into clusters, binarize alpha, re-do face/hands (AI melts them at small sizes).
|
|
70
|
+
4. Audit next to existing assets (contact sheet below). If the hand pass exceeds drawing from scratch — common for small sprites — draw from scratch; diffusion earns its keep on concepts and big backgrounds, rarely on a 16px item.
|
|
71
|
+
|
|
72
|
+
## Recipes per asset type
|
|
73
|
+
|
|
74
|
+
- **Characters**: fix the canvas box and ground line; ≤10 colors per character; design limbs as separable clusters (animation needs them); consistent view angle (side or 3/4 top-down) across the whole cast; darkest outline value reserved for silhouette edge.
|
|
75
|
+
- **Animation**: key poses first, inbetweens after. Frame budgets that read well: idle 4–6, walk/run 6–8, attack 3–5 plus anticipation and follow-through, hit/death 4–6. Squash & stretch in pixel terms (compress the cluster, don't scale the sprite). Sub-pixel illusion: shift interior clusters and AA one frame before the outline moves. Keep pivot/feet on the ground line in every frame; tag animations in Aseprite and export sheet + JSON.
|
|
76
|
+
- **Tilesets**: prove seamlessness by tiling 3×3 and offsetting by half a tile; make 2–4 variants of any large-area tile (grass, floor) to kill visible repetition; terrain edges via the engine's autotile template (Godot terrains, Tiled — the 47-tile blob covers all cases); tiles stay lower-contrast than sprites; atlas with 1–2px extrusion (see pixel-game-dev on bleeding).
|
|
77
|
+
- **UI**: 9-slice panels (corners fixed, edges repeat); a real pixel font at integer sizes only; icons on one shared grid with one shared outline rule; UI pixel density matches the game (no 1x game with 4x-smooth UI).
|
|
78
|
+
- **VFX**: few frames (4–8), big readable shapes, ramps that end bright (white/near-white last frame) for additive blending; smear frames beat motion blur.
|
|
79
|
+
|
|
80
|
+
## QA ritual (every asset, every set)
|
|
81
|
+
|
|
82
|
+
1. View at **1x in-game size** (the only truth) and at 4–8x (the editing view). Judge at 1x.
|
|
83
|
+
2. Palette audit: `px.py palette sprite.png` — any color outside the style bible is a bug.
|
|
84
|
+
3. Light/outline/density check against STYLE.md.
|
|
85
|
+
4. Set consistency: `px.py sheet assets/sprites --scale 4` → one contact sheet; drift is obvious side by side.
|
|
86
|
+
5. In-engine screenshot at real resolution — rendering settings can undo everything (see pixel-game-dev's pixel-perfect rules).
|
|
87
|
+
|
|
88
|
+
Bundled tool: [scripts/px.py](scripts/px.py) (`pip install pillow`) — commands: `quantize` (downscale + palette lock + binary alpha), `upscale` (integer preview), `palette` (color audit), `sheet` (contact sheet). Ship 1x PNGs; previews are for review only.
|
|
89
|
+
|
|
90
|
+
Pairs with: **game-design** (the MVP asset list — don't craft 200 assets before the rectangle test passes), **pixel-game-dev** (rendering, atlases, integration), **human-made-design** (menus/marketing pages around the game).
|
|
91
|
+
|