first commit
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# OpenRouter Model Sync Script
|
||||
|
||||
## Context
|
||||
User wants a Python script that fetches all available models from the OpenRouter API (`GET https://openrouter.ai/api/v1/models`) and generates:
|
||||
1. `~/.litellm/config.yaml` — LiteLLM proxy config with all matching models
|
||||
2. `~/.env_keys.example` — single `OPENROUTER_API_KEY` entry
|
||||
|
||||
Filtering is controlled by a YAML config file with provider-level and model-name-pattern include/exclude rules. The script overwrites (not merges) the target files.
|
||||
|
||||
## Files to Create
|
||||
|
||||
### 1. `~/.litellm/openrouter_sync.yaml` — Filter config (with defaults/example)
|
||||
|
||||
```yaml
|
||||
# Which providers/models to include or exclude from OpenRouter.
|
||||
# Filters are evaluated in order: include first, then exclude.
|
||||
# Glob patterns supported (fnmatch-style).
|
||||
|
||||
filters:
|
||||
providers:
|
||||
include: [] # empty = all providers included
|
||||
exclude: [] # e.g. ["openrouter", "auto"]
|
||||
|
||||
models:
|
||||
include: [] # empty = all models included (after provider filter)
|
||||
exclude: [] # e.g. ["*:free", "*-preview", "*/old-*"]
|
||||
|
||||
# Output paths (defaults shown)
|
||||
output:
|
||||
config_yaml: "~/.litellm/config.yaml"
|
||||
env_keys: "~/.env_keys.example"
|
||||
```
|
||||
|
||||
**Filter logic:**
|
||||
- `providers.include`: if non-empty, only models from these providers are kept. Provider = first path segment of model id (e.g. `google` from `google/gemini-2.0-flash`).
|
||||
- `providers.exclude`: remove models from these providers (applied after include).
|
||||
- `models.include`: if non-empty, only model ids matching any of these glob patterns are kept.
|
||||
- `models.exclude`: remove model ids matching any of these glob patterns.
|
||||
|
||||
### 2. `~/.litellm/sync_openrouter.py` — Main script
|
||||
|
||||
**Logic:**
|
||||
1. Load filter config from `~/.litellm/openrouter_sync.yaml` (create default if missing).
|
||||
2. `GET https://openrouter.ai/api/v1/models` — fetch all models.
|
||||
3. Apply provider include/exclude filters.
|
||||
4. Apply model name include/exclude filters (fnmatch glob).
|
||||
5. Build LiteLLM `model_list` entries:
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: google/gemini-2.0-flash # the OpenRouter model id
|
||||
litellm_params:
|
||||
model: openrouter/google/gemini-2.0-flash
|
||||
api_key: os.environ/OPENROUTER_API_KEY
|
||||
```
|
||||
6. Write `config.yaml` (overwrite).
|
||||
7. Write `.env_keys.example` with single line: `export OPENROUTER_API_KEY="sk-or-REPLACE_ME"`.
|
||||
8. Print summary: total models fetched, filtered count, written count.
|
||||
|
||||
**Dependencies:** `requests`, `pyyaml` (both common; script will check and error clearly if missing).
|
||||
|
||||
**No external packages beyond stdlib+requests+pyyaml.** Uses `fnmatch` from stdlib for glob matching.
|
||||
|
||||
## Verification
|
||||
1. Run `python ~/.litellm/sync_openrouter.py` with default (empty) filters — should populate config.yaml with all OpenRouter models.
|
||||
2. Edit `openrouter_sync.yaml` to exclude a provider, re-run, confirm those models are gone.
|
||||
3. Verify `~/.env_keys.example` contains the OPENROUTER_API_KEY line.
|
||||
4. Validate generated `config.yaml` with `python -c "import yaml; yaml.safe_load(open('config.yaml'))"`.
|
||||
@@ -0,0 +1,158 @@
|
||||
# Replace Postgres Sync Bridge with Native Async pg.Pool
|
||||
|
||||
## Context
|
||||
|
||||
The app currently uses a "sync bridge" (`dbPostgresSyncBridge.js`) that wraps async Postgres queries in a worker thread and blocks the main thread with `Atomics.wait` polling at 10ms intervals. Every query pays: serialization + MessageChannel IPC + worker thread round-trip + 10ms polling tax. This makes Postgres mode ~10-100x slower than SQLite and fragile (SQLite SQL translated on the fly).
|
||||
|
||||
The fix: use `pg.Pool` natively with async/await. The async pool, query helpers, and full Postgres schema already exist in `dbPostgres.js`. The work is making the call chain async.
|
||||
|
||||
## Design
|
||||
|
||||
### Async DB Interface
|
||||
|
||||
Both SQLite and Postgres return the same async interface:
|
||||
|
||||
```js
|
||||
{
|
||||
engine: 'postgres' | 'sqlite',
|
||||
prepare(sql) → { async get(...p), async all(...p), async run(...p) },
|
||||
async exec(sql),
|
||||
async close(),
|
||||
}
|
||||
```
|
||||
|
||||
- `prepare()` is synchronous (captures SQL, returns statement object)
|
||||
- `get/all/run/exec` are async (return Promises)
|
||||
- For SQLite: the async methods resolve synchronously (zero overhead)
|
||||
|
||||
### Transaction Isolation via AsyncLocalStorage
|
||||
|
||||
**Problem**: With `pg.Pool`, concurrent requests get different connections — a `BEGIN` on one connection doesn't affect queries on another.
|
||||
|
||||
**Solution**: Module-scoped `AsyncLocalStorage` in `dbPostgres.js`. When `tx()` starts a transaction, it checks out a client from the pool, pins it in `AsyncLocalStorage`, and all `db.prepare()` calls within that async context automatically route to the pinned client. The callback uses the same `db` variable from the closure — no parameter changes needed.
|
||||
|
||||
```js
|
||||
// Postgres tx
|
||||
const client = await db.pool.connect();
|
||||
await client.query('BEGIN');
|
||||
const result = await txStorage.run({ client }, fn); // fn's db calls see pinned client
|
||||
await client.query('COMMIT');
|
||||
client.release();
|
||||
```
|
||||
|
||||
Nested `tx()` calls detect the existing store and run inline (no double-BEGIN).
|
||||
|
||||
### `openDatabase()` becomes async
|
||||
|
||||
- **Postgres**: `createPostgresPool()` → `initializePostgresSchema(pool)` → `createAsyncPostgresDb(pool)` → return
|
||||
- **SQLite**: `new DatabaseSync()` → sync `initializeSchema()` → `wrapSyncDbAsAsync()` → return
|
||||
|
||||
Schema init stays engine-specific: sync for SQLite (unchanged), async for Postgres (already exists).
|
||||
|
||||
### Return value contracts (unchanged)
|
||||
|
||||
- `.get()` → row or `undefined`
|
||||
- `.all()` → array (empty if no results)
|
||||
- `.run()` → `{ changes: number, lastInsertRowid: null }`
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: `src/dbPostgres.js` — Add async db adapter
|
||||
|
||||
- Add `AsyncLocalStorage` import and module-scoped `txStorage`
|
||||
- Add `createAsyncPostgresDb(pool)` — returns async db object with `getClient()` routing via `txStorage`
|
||||
- Add `export async function asyncTx(db, fn)` — acquires client, pins in ALS, runs fn, commits/rollbacks
|
||||
- Keep all existing exports (pool creation, schema init, param conversion)
|
||||
|
||||
### Step 2: `src/db.js` — Add SQLite async wrapper + async openDatabase
|
||||
|
||||
- Add `wrapSyncDbAsAsync(syncDb)` — wraps sync prepare/exec/close in async functions
|
||||
- Make `openDatabase()` async:
|
||||
- Postgres path: pool + async schema init + `createAsyncPostgresDb(pool)`
|
||||
- SQLite path: sync init + `wrapSyncDbAsAsync(syncDb)`
|
||||
- Remove `openPostgresSyncBridge` import
|
||||
- Keep sync `initializeSchema()` (only used for SQLite, called on raw `DatabaseSync`)
|
||||
|
||||
### Step 3: `src/store/coreStore.js` — Convert store layer to async
|
||||
|
||||
The bulk of the work (~200 lines touched, all mechanical):
|
||||
|
||||
- Make `tx()` async and driver-aware (calls `asyncTx` for Postgres, inline BEGIN/COMMIT for SQLite)
|
||||
- 82 exported functions → `async function`
|
||||
- ~15 internal helpers → `async function`
|
||||
- 85 `db.prepare().get/all/run()` calls → `await`
|
||||
- 12 `tx(db, () => ...)` calls → `await tx(db, async () => ...)`
|
||||
- Store-to-store calls (e.g. `listRoster` inside `getGroupSummary`) → `await`
|
||||
|
||||
### Step 4: `src/itemCatalog.js` — Convert to async
|
||||
|
||||
- 7 functions become async
|
||||
- 9 db calls get `await`
|
||||
- Manual `BEGIN`/`COMMIT` in `refreshItemCatalog` and `rollbackItemCatalogToSnapshot` → use `tx()`
|
||||
|
||||
### Step 5: All 7 route files — Make handlers async
|
||||
|
||||
80 route handlers across:
|
||||
- `src/routes/syncRoutes.js` (14)
|
||||
- `src/routes/groupRoutes.js` (27)
|
||||
- `src/routes/authRoutes.js` (13)
|
||||
- `src/routes/webRoutes.js` (10)
|
||||
- `src/routes/billingRoutes.js` (7 + 2 direct db calls)
|
||||
- `src/routes/opsRoutes.js` (6)
|
||||
- `src/routes/internalRoutes.js` (3)
|
||||
|
||||
Each: `(req, res) =>` → `async (req, res) =>`, add `await` before store calls. Express 5 handles async errors natively.
|
||||
|
||||
### Step 6: `src/server.js` — Async startup + local functions
|
||||
|
||||
- `const db = await openDatabase()` (top-level await in ESM)
|
||||
- ~12 local functions that call db become async: `probeDatabaseHealth`, `wipeRuntimeDataForDev`, `findGroupByJoinCode`, `lookupGroupAccessByUser`, `readAuthenticatedWebUser`, `requireAuthenticatedWebUser`, `requireGroupMembership`, `getRequestPremiumState`, `getRequestFeatureState`, `isApiFeatureEnabled`, `requireApiFeatureFlag`, `publishDashboardEventForUserGroups`, etc.
|
||||
- `await seedDemoData(db)` at startup
|
||||
- Add pool shutdown on SIGINT/SIGTERM
|
||||
|
||||
### Step 7: `src/activityWebhookDelivery.js`
|
||||
|
||||
- Already async, just add `await` to 2 store function calls
|
||||
|
||||
### Step 8: Delete sync bridge
|
||||
|
||||
- Delete `src/dbPostgresSyncBridge.js`
|
||||
- Delete `src/dbPostgresSyncWorker.js`
|
||||
- Update `resolveDatabaseRuntimeConfig()` to remove `sync_bridge` compatibility reference
|
||||
|
||||
### Step 9: Update tests
|
||||
|
||||
- 32 test files: test callbacks become `async`, db created via `wrapSyncDbAsAsync()`, store calls get `await`
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/dbPostgres.js` | Add `createAsyncPostgresDb`, `asyncTx`, `txStorage` (~60 new lines) |
|
||||
| `src/db.js` | Add `wrapSyncDbAsAsync`; async `openDatabase`; remove sync bridge import |
|
||||
| `src/store/coreStore.js` | All 82 exports + ~15 helpers async; await 85 prepare + 12 tx calls |
|
||||
| `src/itemCatalog.js` | 7 functions async; await 9 db calls; replace manual tx |
|
||||
| `src/routes/*.js` (7 files) | 80 handlers async; await store calls |
|
||||
| `src/server.js` | Async startup; ~12 local functions async; pool shutdown |
|
||||
| `src/activityWebhookDelivery.js` | Await 2 store calls |
|
||||
|
||||
## Files Deleted
|
||||
|
||||
- `src/dbPostgresSyncBridge.js`
|
||||
- `src/dbPostgresSyncWorker.js`
|
||||
|
||||
## Key Risks
|
||||
|
||||
1. **SQLite transaction interleaving**: Won't happen — SQLite async wrapper resolves synchronously (no real yielding between BEGIN/COMMIT). Rule: no network I/O inside `tx()` callbacks.
|
||||
2. **Pool exhaustion**: `asyncTx` uses `try/finally` to guarantee `client.release()`. Pool timeout (10s default) prevents silent hangs.
|
||||
3. **Return value `.get()` undefined vs null**: New adapter returns `undefined` (matching SQLite behavior). All call sites use `if (!row)` — works for both.
|
||||
4. **Cascading async in server.js**: `getRequestPremiumState` → `getRequestFeatureState` → `isApiFeatureEnabled` etc. chain needs async. All call sites are in route handlers (already being made async).
|
||||
|
||||
## Verification
|
||||
|
||||
1. `npm test` — all 32 test files pass with async wrapper
|
||||
2. `docker compose down -v && docker compose up --build -d` — app starts cleanly
|
||||
3. Full onboarding flow: mock OAuth → create group → plugin sync → vault shows items
|
||||
4. Check Docker logs: no `Atomics`, no worker thread, no 10ms polling
|
||||
5. Compare response times: `time curl http://localhost:3000/api/v1/groups/:id/vault` (before/after)
|
||||
6. Verify SQLite mode still works: `DB_DRIVER=sqlite npm start`
|
||||
219
.app_factory/claude_home/.claude/plans/snug-tumbling-sun.md
Normal file
219
.app_factory/claude_home/.claude/plans/snug-tumbling-sun.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Cattopia: Hero, Parrot, Pirate Ship & Level 2
|
||||
|
||||
## Context
|
||||
The refactoring to PixiJS/GSAP/Vite/ES6 modules is complete. Now we're adding new gameplay: a player-controlled Hero (auto-move + click-to-attack), a pet Parrot companion, both fighting alongside cats in all levels. After defeating the Blood Wolf (boss 5), a pirate ship transition leads to Level 2 where skeleton rats replace mice and the final boss — Lord of the Rat Dead — summons skeleton rats.
|
||||
|
||||
## Game Flow (Updated)
|
||||
|
||||
```
|
||||
Intro → Creator (cats + hero + parrot) → Level 1 (hunt mice → 5 bosses)
|
||||
→ Pirate Ship Transition → Level 2 (hunt skeleton rats → Lord of the Rat Dead)
|
||||
→ Victory
|
||||
```
|
||||
|
||||
## New Files
|
||||
|
||||
```
|
||||
src/
|
||||
entities/
|
||||
Hero.js (auto-move AI + click-attack, stats, XP/leveling)
|
||||
Parrot.js (follows hero, auto-attacks, stats)
|
||||
SkeletonRat.js (Level 2 prey — tougher than Mouse, bone visual)
|
||||
renderers/
|
||||
HeroRenderer.js (dark green hoodie, emerald knife, legs, attack anim)
|
||||
ParrotRenderer.js (colorful bird body, flapping wings, color-customizable)
|
||||
SkeletonRatRenderer.js (bone-colored rat with skeletal details)
|
||||
PirateShipRenderer.js (ship hull, mast, sails, water — used by transition scene)
|
||||
scenes/
|
||||
PirateShipScene.js (boarding animation: cats/hero/parrot walk onto ship, ship sails off, GSAP transition)
|
||||
```
|
||||
|
||||
## Modified Files
|
||||
|
||||
| File | Changes |
|
||||
|---|---|
|
||||
| `src/constants.js` | Add `LEVEL_2_BOSS_DEF`, `SKELETON_RAT_GOAL`, new game phases (`SHIP_TRANSITION`) |
|
||||
| `src/state/GameState.js` | Add `hero`, `parrot`, `skeletonRats[]`, `currentLevel` (1 or 2) |
|
||||
| `src/scenes/CreatorScene.js` | Add hero creator section (name + color) and parrot creator section (name + color) |
|
||||
| `src/scenes/GameScene.js` | Integrate hero + parrot into update/render loops, handle click-to-attack, Level 2 skeleton rat logic, Lord of the Rat Dead boss |
|
||||
| `src/scenes/VictoryScene.js` | Include hero + parrot in final scoreboard |
|
||||
| `src/main.js` | Register new PirateShipScene |
|
||||
| `index.html` | Add hero + parrot creator DOM elements |
|
||||
| `styles/style.css` | Styles for hero/parrot creator sections |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Constants, State & Entity Classes
|
||||
|
||||
### 1a. Update `src/constants.js`
|
||||
- Add `SHIP_TRANSITION` to `GAME_PHASES`
|
||||
- Add Level 2 constants:
|
||||
```js
|
||||
export const SKELETON_RAT_GOAL = 25;
|
||||
export const LEVEL_2_BOSS_DEF = {
|
||||
name: 'Lord of the Rat Dead',
|
||||
icon: '💀🐀',
|
||||
color: '#2a1a2a',
|
||||
loot: { name: "Necro Crown", icon: '💀👑', stats: { hunting: 12, speed: 8, agility: 8, stealth: 8 } },
|
||||
summonInterval: 300, // frames between skeleton rat summons
|
||||
hpPerAlly: 250 // HP per cat+hero
|
||||
};
|
||||
```
|
||||
|
||||
### 1b. Update `src/state/GameState.js`
|
||||
- Add to `reset()`:
|
||||
```js
|
||||
this.hero = null;
|
||||
this.parrot = null;
|
||||
this.skeletonRats = [];
|
||||
this.currentLevel = 1;
|
||||
```
|
||||
|
||||
### 1c. Create `src/entities/Hero.js`
|
||||
- Similar structure to `Cat.js` — has name, color, stats, level, xp, gear
|
||||
- `updateHunt(targets, bounds)`: auto-move AI chases nearest mouse/skeleton rat (same pattern as Cat.updateHunt)
|
||||
- `updateBossChase(boss)`: auto-move toward boss (same pattern as Cat.updateBossChase)
|
||||
- `triggerAttack(enemies, boss)`: called on player click — finds nearest enemy within ~80px range and deals bonus damage burst. Returns `{ type: 'attackHit', target, damage }` or null
|
||||
- `applyPhysics(bounds)`: identical to Cat
|
||||
- Distinguishing stats: higher base hunting, lower stealth (knife fighter)
|
||||
|
||||
### 1d. Create `src/entities/Parrot.js`
|
||||
- Has name, color, stats (auto-generated like cats), level, xp
|
||||
- `update(hero, targets, boss, bounds)`: follows hero loosely (offset position), periodically attacks nearest enemy within range (every ~90 frames). Returns action: `{ type: 'peck', target, damage }` or null
|
||||
- `applyPhysics(bounds)`: same clamping pattern
|
||||
- Smaller hitbox, faster movement, lower damage than hero
|
||||
|
||||
### 1e. Create `src/entities/SkeletonRat.js`
|
||||
- Based on `Mouse.js` but tougher: `hp` field (takes 2-3 hits to kill), slightly faster
|
||||
- `update(hunters, bounds)`: flee AI like Mouse but with more erratic movement
|
||||
- `alive` flag set to false when hp <= 0
|
||||
|
||||
## Phase 2: Renderers
|
||||
|
||||
### 2a. Create `src/renderers/HeroRenderer.js`
|
||||
- Follow `CatRenderer.js` pooling pattern (single hero, but consistent API)
|
||||
- Draw: human-ish figure (slightly taller than cats), dark green hoodie body, hood, legs (animated walk cycle), arms
|
||||
- Emerald knife: green-tinted blade held in right hand, glows on attack
|
||||
- Attack animation: knife slash arc (brief GSAP or frame-based flash)
|
||||
- Name tag above head like cats
|
||||
|
||||
### 2b. Create `src/renderers/ParrotRenderer.js`
|
||||
- Colorful bird body using user-selected color as base
|
||||
- Flapping wings (oscillating wing angle per frame)
|
||||
- Small beak, tail feathers, beady eyes
|
||||
- Name tag (smaller than hero/cat)
|
||||
|
||||
### 2c. Create `src/renderers/SkeletonRatRenderer.js`
|
||||
- Based on `MouseRenderer.js` but bone-white/gray color palette
|
||||
- Visible ribs/bones on body, hollow eye sockets
|
||||
- HP bar above (since they take multiple hits)
|
||||
- Scared state: bones rattle (slight shake)
|
||||
|
||||
### 2d. Create `src/renderers/PirateShipRenderer.js`
|
||||
- Ship hull (brown, curved), mast with sails, jolly roger flag
|
||||
- Water beneath (blue waves, animated)
|
||||
- Used by PirateShipScene for the transition
|
||||
|
||||
## Phase 3: Creator Scene Updates
|
||||
|
||||
### 3a. Update `index.html`
|
||||
- Inside `#creator-screen > .creator-panel`, add after cat creation form:
|
||||
- Hero section: name input + color picker (dark green hoodie preview + alternate colors)
|
||||
- Parrot section: name input + color picker (red/blue/green/yellow parrot previews)
|
||||
- Reorder: cats first, then hero, then parrot, then "Start the Hunt!" button
|
||||
|
||||
### 3b. Update `src/scenes/CreatorScene.js`
|
||||
- Add hero creation logic: name + color → `new Hero({...})` → `gameState.hero = hero`
|
||||
- Add parrot creation logic: name + color → `new Parrot({...})` → `gameState.parrot = parrot`
|
||||
- Stats reveal for hero and parrot (same animated bar pattern as cats)
|
||||
- Require at least 1 cat + hero + parrot before "Start the Hunt!" enables
|
||||
- Add hero/parrot to roster display
|
||||
|
||||
### 3c. Update `styles/style.css`
|
||||
- Styles for hero/parrot creator sections, color picker buttons, preview icons
|
||||
|
||||
## Phase 4: GameScene Integration (Level 1 + Level 2)
|
||||
|
||||
### 4a. Add hero to update loop (`GameScene.js`)
|
||||
- Hunt phase: `hero.updateHunt(currentTargets, bounds)` where `currentTargets` = mice (L1) or skeletonRats (L2)
|
||||
- Boss phase: `hero.updateBossChase(boss)`
|
||||
- Catch/damage checks same pattern as cats but with hero's stats
|
||||
- Click handler on PixiJS container: `this.container.on('pointerdown', (e) => this._onHeroAttack(e))`
|
||||
- `_onHeroAttack(e)`: call `hero.triggerAttack()` to find and damage nearest enemy
|
||||
|
||||
### 4b. Add parrot to update loop
|
||||
- Every frame: `parrot.update(hero, targets, boss, bounds)`
|
||||
- Process returned peck actions: apply damage, spawn hit particles, award XP to parrot
|
||||
|
||||
### 4c. Add renderers to GameScene
|
||||
- Create `HeroRenderer`, `ParrotRenderer`, `SkeletonRatRenderer` instances
|
||||
- Add containers in render order (hero between cats and giga dog, parrot near hero)
|
||||
- Sync in `render()`: `this.heroRenderer.sync(gameState.hero)`, `this.parrotRenderer.sync(gameState.parrot)`, `this.skeletonRatRenderer.sync(gameState.skeletonRats)`
|
||||
|
||||
### 4d. Level 2 hunt logic
|
||||
- When `gameState.currentLevel === 2`:
|
||||
- "Add Mouse" buttons become "Add Skeleton Rat" buttons
|
||||
- Mice array unused; skeleton rats array used instead
|
||||
- Catch = skeleton rat HP reduced; when HP <= 0, award XP + increment totalCaught
|
||||
- Goal: `SKELETON_RAT_GOAL` skeleton rats killed triggers Lord of the Rat Dead
|
||||
|
||||
### 4e. Lord of the Rat Dead boss
|
||||
- Reuse `Boss.js` entity with a new defIndex (6) or create specialized subclass
|
||||
- **Approach**: extend `BOSS_DEFS` array with the Lord entry at index 5 (only used in Level 2)
|
||||
- Boss mechanics: standard movement + periodically summons skeleton rats (using `summonInterval` from def)
|
||||
- On defeat: transition to victory (`sceneManager.switchTo('victory')`)
|
||||
|
||||
### 4f. Update `_defeatBoss()` flow
|
||||
- After Blood Wolf defeat (boss index 4, Level 1): instead of victory, set `gameState.currentLevel = 2` and `sceneManager.switchTo('ship')`
|
||||
- After Lord of the Rat Dead defeat (Level 2): go to victory
|
||||
|
||||
## Phase 5: Pirate Ship Transition Scene
|
||||
|
||||
### 5a. Create `src/scenes/PirateShipScene.js`
|
||||
- PixiJS scene with PirateShipRenderer for the ship + water background
|
||||
- GSAP timeline:
|
||||
1. Show "The seas await..." text overlay (fade in)
|
||||
2. Cats, hero, and parrot walk/fly from left toward ship (small sprites moving right)
|
||||
3. Board the ship (sprites move onto deck)
|
||||
4. Ship sails right, screen fades to black
|
||||
5. Switch to GameScene with Level 2 state
|
||||
- Duration: ~4-5 seconds total
|
||||
|
||||
### 5b. Update `src/main.js`
|
||||
- Import and register PirateShipScene: `sceneManager.register('ship', pirateShipScene)`
|
||||
|
||||
### 5c. Update GameScene.enter()
|
||||
- Check `gameState.currentLevel`:
|
||||
- Level 1: current behavior (forest, mice)
|
||||
- Level 2: reset totalCaught, update HUD text ("Skeleton Rats" instead of "Mice"), different forest theme (darker/undead aesthetic via ForestRenderer flag), spawn skeleton rats
|
||||
|
||||
### 5d. Optional: ForestRenderer Level 2 theme
|
||||
- When `currentLevel === 2`: darker sky gradient, dead/gray trees, eerie green ground glow
|
||||
- Reuse blood moon infrastructure but with green/purple tint instead of red
|
||||
|
||||
## Phase 6: Victory Scene Updates
|
||||
|
||||
### 6a. Update `VictoryScene.js`
|
||||
- Include hero and parrot in final scoreboard alongside cats
|
||||
- Hero gets a special "Hero" badge, parrot gets "Companion" badge
|
||||
- MVP calculation includes hero (hero.catches or boss damage dealt)
|
||||
|
||||
## Phase 7: Polish & Cleanup
|
||||
|
||||
- Update SpawnSystem with `spawnSkeletonRats()` method
|
||||
- Update `_updateHUD()` to show level indicator ("Level 1" / "Level 2")
|
||||
- Update `_updateScoreboard()` to include hero + parrot entries
|
||||
- Ensure Giga Dog works in both levels
|
||||
- Update README with new features
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
After each phase, verify with `npm run dev`:
|
||||
1. **Creator**: Can create cats, hero (name + color), and parrot (name + color). All show in roster with stats.
|
||||
2. **Level 1**: Hero auto-moves and chases mice. Click near hero attacks. Parrot follows hero and pecks enemies. All 5 bosses work.
|
||||
3. **Ship Transition**: After Blood Wolf defeat, pirate ship scene plays, transitions to Level 2.
|
||||
4. **Level 2**: Skeleton rats spawn instead of mice. Hunt → Lord of the Rat Dead boss. Boss summons skeleton rats. Defeating boss triggers victory.
|
||||
5. **Victory**: Scoreboard shows cats, hero, and parrot. MVP includes all.
|
||||
6. **Full build**: `npm run build` succeeds, Docker still works.
|
||||
Reference in New Issue
Block a user