docs(02): create phase plan for Gemini integration + asset pipeline
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
---
|
||||
phase: 02-gemini-integration-asset-pipeline
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["02-01"]
|
||||
files_modified:
|
||||
- src/storage/db.ts
|
||||
- scripts/generate-assets.ts
|
||||
- package.json
|
||||
- src/assets/companions/fee-lila/character-sheet.png
|
||||
- src/assets/companions/fee-lila/avatar.png
|
||||
- src/assets/companions/einhorn-stella/character-sheet.png
|
||||
- src/assets/companions/einhorn-stella/avatar.png
|
||||
- src/assets/companions/fuchs-finn/character-sheet.png
|
||||
- src/assets/companions/fuchs-finn/avatar.png
|
||||
- src/assets/companions/eule-elsa/character-sheet.png
|
||||
- src/assets/companions/eule-elsa/avatar.png
|
||||
- src/assets/style-reference/initial-forest-scene.png
|
||||
autonomous: true
|
||||
requirements: [ASST-01, ASST-02]
|
||||
user_setup:
|
||||
- service: gemini
|
||||
why: "Asset generation requires Gemini API key"
|
||||
env_vars:
|
||||
- name: geminiApiKey
|
||||
source: "public/config.json — already exists from Phase 1 setup"
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "npm run generate-assets produces 4 character sheets, 4 avatars, and 1 style reference"
|
||||
- "Generated PNG files exist in src/assets/companions/{type}/ and src/assets/style-reference/"
|
||||
- "IndexedDB accessor methods exist for companionAssets, styleReference, and forestElements stores"
|
||||
artifacts:
|
||||
- path: "scripts/generate-assets.ts"
|
||||
provides: "Build script for pre-generating character assets"
|
||||
- path: "src/storage/db.ts"
|
||||
provides: "Extended DB wrapper with all store accessors"
|
||||
exports: ["saveCompanionAssets", "getCompanionAssets", "saveStyleReference", "getStyleReference", "saveForestElement", "getForestElements"]
|
||||
- path: "src/assets/companions/fee-lila/character-sheet.png"
|
||||
provides: "Fee Lila character sheet"
|
||||
- path: "src/assets/companions/fee-lila/avatar.png"
|
||||
provides: "Fee Lila avatar"
|
||||
- path: "src/assets/style-reference/initial-forest-scene.png"
|
||||
provides: "Style reference image for consistent art direction"
|
||||
key_links:
|
||||
- from: "scripts/generate-assets.ts"
|
||||
to: "src/api/gemini.ts"
|
||||
via: "imports generateImage for Gemini API calls"
|
||||
pattern: "import.*generateImage.*from"
|
||||
- from: "scripts/generate-assets.ts"
|
||||
to: "public/config.json"
|
||||
via: "reads API key via fs.readFileSync"
|
||||
pattern: "readFileSync.*config\\.json"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Add IndexedDB accessor methods for all stores, create the asset generation build script, and run it to produce all character sheets, avatars, and the style reference image.
|
||||
|
||||
Purpose: Pre-generated assets eliminate onboarding wait times and ensure visual consistency across sessions.
|
||||
Output: Extended db.ts, working generate-assets.ts script, 9 PNG files checked into git.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/02-gemini-integration-asset-pipeline/02-01-SUMMARY.md
|
||||
|
||||
@src/types.ts
|
||||
@src/storage/db.ts
|
||||
@src/api/config.ts
|
||||
@src/api/gemini.ts
|
||||
@src/companion/characters.ts
|
||||
|
||||
<interfaces>
|
||||
<!-- From src/api/gemini.ts (created in Plan 01) -->
|
||||
```typescript
|
||||
export async function generateText(prompt: string, systemPrompt: string, config: GeminiConfig): Promise<string | null>;
|
||||
export async function generateImage(prompt: string, referenceImages: Blob[], config: GeminiConfig): Promise<Blob | null>;
|
||||
```
|
||||
|
||||
<!-- From src/api/config.ts (created in Plan 01) -->
|
||||
```typescript
|
||||
export interface GeminiConfig {
|
||||
geminiApiKey: string;
|
||||
geminiModel: string;
|
||||
imageModel: string;
|
||||
}
|
||||
export function loadConfigFromObject(obj: GeminiConfig): void;
|
||||
```
|
||||
|
||||
<!-- From src/types.ts -->
|
||||
```typescript
|
||||
export interface CompanionAssets {
|
||||
characterType: CompanionType;
|
||||
characterSheet: Blob;
|
||||
avatarImage: Blob;
|
||||
generatedAt: string;
|
||||
}
|
||||
|
||||
export interface StyleReference {
|
||||
id: 1;
|
||||
imageBlob: Blob;
|
||||
generatedAt: string;
|
||||
}
|
||||
|
||||
export interface ForestElement {
|
||||
id?: number;
|
||||
levelCompleted: number;
|
||||
imageBlob: Blob;
|
||||
imagePrompt: string;
|
||||
description: string;
|
||||
companionText: string;
|
||||
position: { x: number; y: number };
|
||||
createdAt: string;
|
||||
}
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add IndexedDB accessor methods for remaining stores</name>
|
||||
<files>src/storage/db.ts</files>
|
||||
<read_first>
|
||||
- src/storage/db.ts (current code with getProgress/saveProgress/getSettings/saveSettings pattern)
|
||||
- src/types.ts (CompanionAssets, StyleReference, ForestElement interfaces)
|
||||
</read_first>
|
||||
<action>
|
||||
Add the following methods to `src/storage/db.ts` following the existing Promise-wrapped pattern (per D-04):
|
||||
|
||||
**companionAssets store** (keyed by characterType):
|
||||
```typescript
|
||||
export function saveCompanionAssets(db: IDBDatabase, assets: CompanionAssets): Promise<void>
|
||||
// Same pattern as saveProgress — tx "readwrite", store.put(assets)
|
||||
|
||||
export function getCompanionAssets(db: IDBDatabase, characterType: CompanionType): Promise<CompanionAssets | null>
|
||||
// Same pattern as getProgress — tx "readonly", store.get(characterType)
|
||||
```
|
||||
|
||||
**styleReference store** (singleton, id: 1):
|
||||
```typescript
|
||||
export function saveStyleReference(db: IDBDatabase, ref: StyleReference): Promise<void>
|
||||
// store.put(ref) — same as saveSettings pattern
|
||||
|
||||
export function getStyleReference(db: IDBDatabase): Promise<StyleReference | null>
|
||||
// store.get(1) — same as getSettings pattern
|
||||
```
|
||||
|
||||
**forestElements store** (auto-increment):
|
||||
```typescript
|
||||
export function saveForestElement(db: IDBDatabase, element: ForestElement): Promise<number>
|
||||
// store.add(element), return request.result as number (the auto-incremented id)
|
||||
|
||||
export function getForestElements(db: IDBDatabase): Promise<ForestElement[]>
|
||||
// store.getAll(), return result as ForestElement[]
|
||||
```
|
||||
|
||||
Add required imports at top: `import type { CompanionAssets, CompanionType, ForestElement, StyleReference } from "../types";`
|
||||
(Keep existing Progress and Settings imports.)
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && npx tsx -e "import { saveCompanionAssets, getCompanionAssets, saveStyleReference, getStyleReference, saveForestElement, getForestElements } from './src/storage/db'; console.log('All 6 methods exported OK')"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -c "export function saveCompanionAssets" src/storage/db.ts` returns 1
|
||||
- `grep -c "export function getCompanionAssets" src/storage/db.ts` returns 1
|
||||
- `grep -c "export function saveStyleReference" src/storage/db.ts` returns 1
|
||||
- `grep -c "export function getStyleReference" src/storage/db.ts` returns 1
|
||||
- `grep -c "export function saveForestElement" src/storage/db.ts` returns 1
|
||||
- `grep -c "export function getForestElements" src/storage/db.ts` returns 1
|
||||
- `grep "CompanionAssets\|CompanionType\|ForestElement\|StyleReference" src/storage/db.ts | head -1` shows the types imported
|
||||
</acceptance_criteria>
|
||||
<done>All 6 new IndexedDB accessor methods added to db.ts — saveCompanionAssets, getCompanionAssets, saveStyleReference, getStyleReference, saveForestElement, getForestElements — following existing Promise-wrapped pattern.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Build script for asset generation + run it</name>
|
||||
<files>scripts/generate-assets.ts, package.json</files>
|
||||
<read_first>
|
||||
- src/api/gemini.ts (generateImage function signature from Plan 01)
|
||||
- src/api/config.ts (GeminiConfig interface, loadConfigFromObject)
|
||||
- src/companion/characters.ts (companion definitions — 4 companions with type, name, personality)
|
||||
- SPEC.md section 9.2 (Character Sheet prompt, Avatar prompt, visual descriptions per character)
|
||||
- SPEC.md section 14.2 (asset generation flow)
|
||||
- public/config.json (actual config file path)
|
||||
</read_first>
|
||||
<action>
|
||||
**Step 1: Add tsx devDependency and npm script**
|
||||
In `package.json`, add to devDependencies: `"tsx": "^4.0.0"`
|
||||
Add script: `"generate-assets": "tsx scripts/generate-assets.ts"`
|
||||
Run `npm install` after editing.
|
||||
|
||||
**Step 2: Create `scripts/generate-assets.ts`** (per D-01, D-02, ASST-01)
|
||||
|
||||
The script runs in Node.js via `npx tsx`. It:
|
||||
|
||||
1. Reads `public/config.json` via `fs.readFileSync('./public/config.json', 'utf-8')` and parses as `GeminiConfig`
|
||||
2. Uses `loadConfigFromObject()` from `src/api/config.ts` to set the config
|
||||
3. For each of the 4 companions, generates:
|
||||
a. **Character Sheet** using the prompt from SPEC.md section 9.2:
|
||||
```
|
||||
Character sheet for a children's book. The character is {name}, {visualDescription}.
|
||||
Show the character in 3 views: front view, side view, and a close-up of the face.
|
||||
All views must show the EXACT SAME character with identical colors, proportions, and features.
|
||||
Art style: Watercolor children's book illustration, warm pastel colors, soft edges, magical forest theme. White background.
|
||||
Label each view clearly: "FRONT" "SIDE" "FACE"
|
||||
The character must be: cute, friendly, approachable, with large expressive eyes. Suitable for a 7-year-old audience.
|
||||
```
|
||||
b. **Avatar** using the character sheet as reference image:
|
||||
```
|
||||
Create a single portrait of this exact character from the reference sheet.
|
||||
Show only the face and upper body, looking directly at the viewer with a warm, friendly expression.
|
||||
Same art style as the reference. Square format. No text. No background (or simple soft gradient).
|
||||
```
|
||||
|
||||
4. Generates **Style Reference** — an initial forest scene:
|
||||
```
|
||||
Watercolor children's book illustration of a magical forest clearing.
|
||||
Warm pastel colors, soft edges, dreamy atmosphere. A gentle meadow surrounded
|
||||
by friendly trees, dappled sunlight, small flowers, and a hint of magic sparkles.
|
||||
No text, no letters, no characters. This sets the visual style for all future forest images.
|
||||
```
|
||||
|
||||
5. Saves each file using `fs.writeFileSync`:
|
||||
- `src/assets/companions/fee-lila/character-sheet.png`
|
||||
- `src/assets/companions/fee-lila/avatar.png`
|
||||
- `src/assets/companions/einhorn-stella/character-sheet.png`
|
||||
- `src/assets/companions/einhorn-stella/avatar.png`
|
||||
- `src/assets/companions/fuchs-finn/character-sheet.png`
|
||||
- `src/assets/companions/fuchs-finn/avatar.png`
|
||||
- `src/assets/companions/eule-elsa/character-sheet.png`
|
||||
- `src/assets/companions/eule-elsa/avatar.png`
|
||||
- `src/assets/style-reference/initial-forest-scene.png`
|
||||
|
||||
Visual descriptions per companion (from SPEC.md section 9.2):
|
||||
- fee: "a small fairy with lavender-purple wings, a flowing lilac dress, big warm brown eyes, a tiny flower crown made of forget-me-nots, light skin, and short wavy auburn hair"
|
||||
- einhorn: "a small unicorn with a white coat, a shimmering golden horn, a flowing mane in soft rainbow pastels (pink, lavender, light blue), big dark eyes with long lashes, and small silver hooves"
|
||||
- fuchs: "a young red fox with warm orange-brown fur, a white chest and belly, a bushy tail with a white tip, bright curious amber eyes, and slightly oversized pointed ears"
|
||||
- eule: "a small round owl with soft brown-and-cream feathers, a heart-shaped face, large round golden eyes, tiny ear tufts, and small talons perched on a mossy branch"
|
||||
|
||||
The script should:
|
||||
- Log progress to stdout: "Generating character sheet for {name}..." etc.
|
||||
- Convert Blob responses to Buffer via `blob.arrayBuffer()` then `Buffer.from()`
|
||||
- Handle errors gracefully: if one generation fails, log error and continue with others
|
||||
- Exit with code 1 if any generation failed, 0 if all succeeded
|
||||
- Add a 2-second delay between API calls to avoid rate limiting
|
||||
|
||||
**Step 3: Run the script**
|
||||
Execute `npm run generate-assets` and verify all 9 PNG files are created.
|
||||
If any fail, check the error and retry (the script should be re-runnable).
|
||||
|
||||
**Important:** The `generateImage` function in `src/api/gemini.ts` uses browser `fetch`. Node.js 18+ has native `fetch`, so this should work. If there are issues with `Blob` in Node.js, use `Buffer` and convert. The script may need to use `node:buffer` Blob or polyfill — handle at implementation time.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/dev/workspace/zauberwald && ls -la src/assets/companions/*/character-sheet.png src/assets/companions/*/avatar.png src/assets/style-reference/initial-forest-scene.png 2>&1 | grep -c ".png"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep "generate-assets" package.json` shows the npm script
|
||||
- `grep "tsx" package.json` shows tsx in devDependencies
|
||||
- `test -f scripts/generate-assets.ts && echo "exists"` returns "exists"
|
||||
- `ls src/assets/companions/fee-lila/character-sheet.png src/assets/companions/fee-lila/avatar.png` succeeds
|
||||
- `ls src/assets/companions/einhorn-stella/character-sheet.png src/assets/companions/einhorn-stella/avatar.png` succeeds
|
||||
- `ls src/assets/companions/fuchs-finn/character-sheet.png src/assets/companions/fuchs-finn/avatar.png` succeeds
|
||||
- `ls src/assets/companions/eule-elsa/character-sheet.png src/assets/companions/eule-elsa/avatar.png` succeeds
|
||||
- `ls src/assets/style-reference/initial-forest-scene.png` succeeds
|
||||
- All 9 PNG files are > 1KB (not empty): `find src/assets -name "*.png" -size +1k | wc -l` returns 9
|
||||
</acceptance_criteria>
|
||||
<done>Build script generates 4 character sheets + 4 avatars + 1 style reference via Gemini API. All 9 PNG files exist in correct directories and are non-empty. `npm run generate-assets` is a working npm script.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `npm run generate-assets` completes without errors (or re-run successfully)
|
||||
- `find src/assets -name "*.png" -size +1k | wc -l` returns 9
|
||||
- All 6 new DB methods compile: `npx tsc --noEmit src/storage/db.ts`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- IndexedDB has accessors for all 5 stores (progress, settings from Phase 1 + companionAssets, styleReference, forestElements new)
|
||||
- Build script reads config.json, calls Gemini API, saves 9 PNG files
|
||||
- All generated assets are suitable quality (child-friendly watercolor style)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-gemini-integration-asset-pipeline/02-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user