chore(01-01): scaffold Phaser 4 + React 19 + Vite + TS template + Phase-1 deps + firewall directories
- Built equivalent React + Vite + TypeScript scaffold by hand because the official npm create @phaserjs/game@latest scaffolder is interactive-only and the documented --template/--yes flags are ignored (verified 2026-05-08 with create-game v1.3.2). Plan Step 1 explicitly authorizes this fallback. Resulting tree mirrors the official template shape: index.html, src/main.tsx, src/App.tsx, src/PhaserGame.tsx, src/game/main.ts, src/game/scenes/Boot.ts. - Installed Phase-1 production deps at versions verified in RESEARCH.md: phaser@4.1.0, react@19.2.6, react-dom@19.2.6, idb@8.0.3, lz-string@1.5.0, zod@4.4.3, crc-32@1.2.2, gray-matter@4.0.3, yaml@2.8.4, inkjs@2.4.0. - Installed Phase-1 dev deps: vite@8.0.11, @vitejs/plugin-react@6.0.1, typescript@6.0.3, @types/react@19, @types/react-dom@19, @types/node@22, vitest@4.1.5, @vitest/ui, happy-dom, fake-indexeddb@6 (for Plan 03 IDB tests), @playwright/test@1.59.1, eslint@9, eslint-plugin-boundaries@6.0.2, inklecate@1.8.1. - Created the seven architectural-firewall directories under src/ with .gitkeep markers (sim, render, ui, save, content, audio, store) — siblings to the template-provided src/game/ — so Plan 02's ESLint boundaries rule has clean targets per CLAUDE.md 'Architectural Firewall'. - Created repo-root /content/ (with /dialogue/ and /seasons/ subdirs) and /assets/ trees per CONTEXT D-11, D-12. - Pre-declared all downstream-required scripts in package.json so Plans 02–06 only edit code, not script keys: dev, build, preview, lint (--max-warnings 0 per RESEARCH CI Pitfall C), test (--passWithNoTests=false per CI Pitfall B), test:watch, validate:assets, compile:ink (no-op stub for Phase 1; Phase 2 replaces with real inklecate invocation), ci. - TypeScript strict mode enforced via tsconfig.json + tsconfig.app.json + tsconfig.node.json. - npm run build succeeds (tsc -b && vite build) producing dist/index.html and dist/assets/index-*.js (~1.5MB Phaser bundle; code-splitting deferred to Phase 2+ when actual scenes exist).
This commit is contained in:
+45
@@ -0,0 +1,45 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
dist-ssr/
|
||||||
|
*.local
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Test artifacts
|
||||||
|
coverage/
|
||||||
|
.vitest-cache/
|
||||||
|
playwright-report/
|
||||||
|
test-results/
|
||||||
|
.playwright/
|
||||||
|
|
||||||
|
# Editor / OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea/
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Vite cache
|
||||||
|
.vite/
|
||||||
|
node_modules/.vite/
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="The Last Garden — a 7-Season browser narrative idle game." />
|
||||||
|
<title>The Last Garden</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+3341
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "the-last-garden",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"description": "A 7-Season browser narrative idle game in the lineage of A Dark Room and Universal Paperclips.",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --max-warnings 0",
|
||||||
|
"test": "vitest run --passWithNoTests=false",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"validate:assets": "node scripts/validate-assets.mjs",
|
||||||
|
"compile:ink": "echo \"[compile:ink] no .ink files yet — Phase 2 will populate /content/dialogue/\" && exit 0",
|
||||||
|
"ci": "npm run lint && npm run test && npm run validate:assets && npm run build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"crc-32": "^1.2.2",
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
|
"idb": "^8.0.3",
|
||||||
|
"inkjs": "^2.4.0",
|
||||||
|
"lz-string": "^1.5.0",
|
||||||
|
"phaser": "^4.1.0",
|
||||||
|
"react": "^19.2.6",
|
||||||
|
"react-dom": "^19.2.6",
|
||||||
|
"yaml": "^2.8.4",
|
||||||
|
"zod": "^4.4.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.59.1",
|
||||||
|
"@types/node": "^22.19.18",
|
||||||
|
"@types/react": "^19.2.14",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"@vitest/ui": "^4.1.5",
|
||||||
|
"eslint": "^9.39.4",
|
||||||
|
"eslint-plugin-boundaries": "^6.0.2",
|
||||||
|
"fake-indexeddb": "^6.2.5",
|
||||||
|
"happy-dom": "^20.9.0",
|
||||||
|
"inklecate": "^1.8.1",
|
||||||
|
"typescript": "^6.0.3",
|
||||||
|
"vite": "^8.0.11",
|
||||||
|
"vitest": "^4.1.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
import { PhaserGame, type IRefPhaserGame } from './PhaserGame.tsx';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
// PhaserGame ref — Phase 2+ will use this to access the active scene from React.
|
||||||
|
const phaserRef = useRef<IRefPhaserGame | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="app">
|
||||||
|
<PhaserGame ref={phaserRef} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef } from 'react';
|
||||||
|
import StartGame from './game/main.ts';
|
||||||
|
import type * as Phaser from 'phaser';
|
||||||
|
|
||||||
|
export interface IRefPhaserGame {
|
||||||
|
game: Phaser.Game | null;
|
||||||
|
scene: Phaser.Scene | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
currentActiveScene?: (sceneInstance: Phaser.Scene) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PhaserGame = forwardRef<IRefPhaserGame, IProps>(function PhaserGame(_props, ref) {
|
||||||
|
const game = useRef<Phaser.Game | null>(null);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (game.current === null) {
|
||||||
|
game.current = StartGame('game-container');
|
||||||
|
|
||||||
|
if (typeof ref === 'function') {
|
||||||
|
ref({ game: game.current, scene: null });
|
||||||
|
} else if (ref) {
|
||||||
|
ref.current = { game: game.current, scene: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (game.current) {
|
||||||
|
game.current.destroy(true);
|
||||||
|
game.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Phase 2+: subscribe to scene-ready events here and surface the active scene
|
||||||
|
// through `currentActiveScene` so React can talk to Phaser.
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
game: game.current,
|
||||||
|
scene: null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return <div id="game-container" />;
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import * as Phaser from 'phaser';
|
||||||
|
import { Boot } from './scenes/Boot.ts';
|
||||||
|
|
||||||
|
// Phase 1: minimal Phaser config that boots cleanly. Real scenes (garden, weather,
|
||||||
|
// watercolor post-process) land in Phase 2+. The architectural-firewall directories
|
||||||
|
// (src/sim, src/render, src/ui) are siblings to this one — see `.planning/phases/
|
||||||
|
// 01-foundations-and-doctrine/01-RESEARCH.md` § "Architectural Responsibility Map".
|
||||||
|
|
||||||
|
const config: Phaser.Types.Core.GameConfig = {
|
||||||
|
type: Phaser.AUTO,
|
||||||
|
width: 1024,
|
||||||
|
height: 768,
|
||||||
|
parent: 'game-container',
|
||||||
|
backgroundColor: '#1a1a1a',
|
||||||
|
scale: {
|
||||||
|
mode: Phaser.Scale.FIT,
|
||||||
|
autoCenter: Phaser.Scale.CENTER_BOTH,
|
||||||
|
},
|
||||||
|
scene: [Boot],
|
||||||
|
};
|
||||||
|
|
||||||
|
const StartGame = (parent: string): Phaser.Game => {
|
||||||
|
return new Phaser.Game({ ...config, parent });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StartGame;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import * as Phaser from 'phaser';
|
||||||
|
|
||||||
|
// Phase 1 placeholder: empty Boot scene that just proves Phaser starts.
|
||||||
|
// Phase 2 replaces this with the real boot → preloader → garden flow,
|
||||||
|
// gated behind the "Tend the garden / Begin" gesture screen that calls
|
||||||
|
// AudioContext.resume() per CLAUDE.md banner concern #7.
|
||||||
|
export class Boot extends Phaser.Scene {
|
||||||
|
constructor() {
|
||||||
|
super('Boot');
|
||||||
|
}
|
||||||
|
|
||||||
|
preload(): void {
|
||||||
|
// No assets in Phase 1.
|
||||||
|
}
|
||||||
|
|
||||||
|
create(): void {
|
||||||
|
// Phase 2 will start the preloader from here.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { StrictMode } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import App from './App.tsx';
|
||||||
|
|
||||||
|
const rootEl = document.getElementById('root');
|
||||||
|
if (!rootEl) {
|
||||||
|
throw new Error('Root element #root not found in index.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
createRoot(rootEl).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true,
|
||||||
|
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
|
||||||
|
"types": ["vite/client"]
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts", "vitest.config.ts", "playwright.config.ts", "scripts/**/*.mjs"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user