- Restructure flat static prototype into pnpm workspace monorepo - apps/game: playable shell with R3F 3D scene, HUD, SpacetimeDB connection - apps/docs: design docs and prototypes - apps/site: landing page - packages/ui: shared Button and Panel primitives - services/spacetimedb: backend module (9 tables, 11 reducers) - Archive legacy static files to archive/legacy-static/ - Game loop: connect, undock, target, approach, dock, mine, sell - Add pnpm-workspace.yaml, tsconfig.base.json, spacetime.json
175 lines
8.5 KiB
JavaScript
175 lines
8.5 KiB
JavaScript
window.GDD = window.GDD || {};
|
|
|
|
function TechStackPage() {
|
|
const [activeTab, setActiveTab] = React.useState('frontend');
|
|
|
|
const tabs = [
|
|
{ id: 'frontend', label: 'Frontend' },
|
|
{ id: 'rendering', label: '3D Rendering' },
|
|
{ id: 'state', label: 'State' },
|
|
{ id: 'backend', label: 'Backend' },
|
|
{ id: 'styling', label: 'Styling' },
|
|
{ id: 'auth', label: 'Auth' },
|
|
];
|
|
|
|
const decisions = {
|
|
frontend: {
|
|
choice: 'Vite + React + TypeScript',
|
|
reason: 'Fast client-side app setup, no server-rendering complexity, good for a real-time game UI.',
|
|
whyNot: "Next.js is not necessary for the MVP. The prototype is a client-side real-time game, not a content site. Next.js can be introduced later for marketing pages, account pages, SSR, or API routes outside SpacetimeDB.",
|
|
},
|
|
rendering: {
|
|
choice: 'React Three Fiber',
|
|
reason: 'Declarative React renderer for Three.js; good for a browser prototype and React integration.',
|
|
whyNot: "A full engine like Unity or Bevy would slow iteration on panels, tables, forms, chat, and market UX. The gameplay is UI-heavy and economy/social-system-heavy.",
|
|
},
|
|
state: {
|
|
choice: 'Zustand',
|
|
reason: 'Simple local state for panels, selection, active tabs, camera preferences, and modal state.',
|
|
whyNot: "Redux would add ceremony without benefit at prototype scale. Context API re-renders would hurt performance with frequent state updates.",
|
|
},
|
|
backend: {
|
|
choice: 'SpacetimeDB',
|
|
reason: 'Real-time backend/database with server-side reducers and live client subscriptions. Authoritative state, persistence, multiplayer all in one.',
|
|
whyNot: "A custom Node.js + PostgreSQL backend would require building real-time sync, subscriptions, and authoritative game logic from scratch.",
|
|
},
|
|
styling: {
|
|
choice: 'Tailwind CSS',
|
|
reason: 'Fast UI iteration for panels, tables, HUDs, and dense game interfaces.',
|
|
whyNot: "CSS-in-JS adds runtime overhead. Vanilla CSS at this scale would slow panel iteration.",
|
|
},
|
|
auth: {
|
|
choice: 'SpacetimeDB Identity (MVP)',
|
|
reason: 'Keep early identity/session handling simple. Add external auth only after core loop works.',
|
|
whyNot: "Full OAuth/JWT would add complexity before we know the right identity model for the game.",
|
|
},
|
|
};
|
|
|
|
const d = decisions[activeTab];
|
|
|
|
return (
|
|
<div className="content-inner">
|
|
<h1 style={{ marginBottom: '8px' }}>Technical Direction</h1>
|
|
<p style={{ color: 'var(--fg-dim)', fontSize: '0.95rem', maxWidth: '680px' }}>
|
|
Each layer is chosen for iteration speed during the prototype phase. Architecture is designed
|
|
so any layer can be replaced as the game evolves.
|
|
</p>
|
|
|
|
{/* Decision tabs */}
|
|
<div style={{ display: 'flex', gap: 'var(--sp-2)', marginBottom: 'var(--sp-6)', flexWrap: 'wrap' }}>
|
|
{tabs.map(t => (
|
|
<button
|
|
key={t.id}
|
|
className={`btn btn-sm${activeTab === t.id ? ' btn-primary' : ''}`}
|
|
onClick={() => setActiveTab(t.id)}
|
|
>
|
|
{t.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Active decision */}
|
|
<div className="card" style={{ marginBottom: 'var(--sp-6)' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-3)', marginBottom: 'var(--sp-4)' }}>
|
|
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
|
{activeTab}
|
|
</span>
|
|
<span style={{ width: '1px', height: '16px', background: 'var(--border)' }} />
|
|
<span style={{ fontFamily: 'var(--font-display)', fontSize: '1.3rem', fontWeight: 600, color: 'var(--fg-bright)' }}>
|
|
{d.choice}
|
|
</span>
|
|
</div>
|
|
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', marginBottom: 'var(--sp-4)' }}>
|
|
<strong style={{ color: 'var(--fg)' }}>Why:</strong> {d.reason}
|
|
</p>
|
|
<div style={{ background: 'var(--surface-raised)', borderRadius: 'var(--radius-md)', padding: 'var(--sp-4)', fontSize: '0.85rem', color: 'var(--fg-dim)' }}>
|
|
<strong style={{ color: 'var(--muted)' }}>Why not the alternatives:</strong> {d.whyNot}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Full decision table */}
|
|
<div className="section-header">
|
|
<span className="section-num">TECH-ALL</span>
|
|
<h2 style={{ margin: 0 }}>Decision Matrix</h2>
|
|
</div>
|
|
|
|
<table className="data-table">
|
|
<thead>
|
|
<tr><th>Layer</th><th>Choice</th><th>Reason</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td style={{ color: 'var(--cyan)' }}>Frontend</td>
|
|
<td>Vite + React + TypeScript</td>
|
|
<td style={{ color: 'var(--fg-dim)' }}>Fast client-side app, no SSR complexity.</td>
|
|
</tr>
|
|
<tr>
|
|
<td style={{ color: 'var(--purple)' }}>3D Rendering</td>
|
|
<td>React Three Fiber</td>
|
|
<td style={{ color: 'var(--fg-dim)' }}>Declarative React renderer for Three.js.</td>
|
|
</tr>
|
|
<tr>
|
|
<td style={{ color: 'var(--green)' }}>UI State</td>
|
|
<td>Zustand</td>
|
|
<td style={{ color: 'var(--fg-dim)' }}>Simple local state for panels and UI.</td>
|
|
</tr>
|
|
<tr>
|
|
<td style={{ color: 'var(--accent)' }}>Backend</td>
|
|
<td>SpacetimeDB</td>
|
|
<td style={{ color: 'var(--fg-dim)' }}>Real-time backend with reducers and subscriptions.</td>
|
|
</tr>
|
|
<tr>
|
|
<td style={{ color: 'var(--fg-dim)' }}>Styling</td>
|
|
<td>Tailwind CSS</td>
|
|
<td style={{ color: 'var(--fg-dim)' }}>Fast iteration for panels, tables, HUDs.</td>
|
|
</tr>
|
|
<tr>
|
|
<td style={{ color: 'var(--muted)' }}>Auth</td>
|
|
<td>SpacetimeDB identity</td>
|
|
<td style={{ color: 'var(--fg-dim)' }}>Simple identity first; add auth later.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
{/* File structure */}
|
|
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
|
|
<span className="section-num">TECH-FS</span>
|
|
<h2 style={{ margin: 0 }}>Starter File Structure</h2>
|
|
</div>
|
|
|
|
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)', fontSize: '0.82rem' }}>
|
|
<strong>Note:</strong> The file structure below is the <em>production target</em> for a Vite + React + TypeScript project.
|
|
The current prototype uses a simpler layout: <code>js/pages/</code>, <code>js/components/</code>, <code>js/demos/</code>, <code>js/lib/</code>, and <code>css/</code> —
|
|
a flat structure loaded via Babel standalone without a build step. The prototype structure will migrate to the layout below when moving to Vite.
|
|
</div>
|
|
|
|
<div className="code-block">
|
|
<code>
|
|
<span className="cm">{'//'} Starter project layout</span><br/>
|
|
<span className="kw">/client/src/app</span> <span className="cm">{' //'} App shell and providers</span><br/>
|
|
<span className="kw">/client/src/network</span> <span className="cm">{' //'} SpacetimeDB client, subscriptions</span><br/>
|
|
<span className="kw">/client/src/game</span> <span className="cm">{' //'} Renderer-independent types, view models</span><br/>
|
|
<span className="kw">/client/src/store</span> <span className="cm">{' //'} Zustand stores</span><br/>
|
|
<span className="kw">/client/src/renderers/r3f</span> <span className="cm">{'//'} R3F scene, meshes, camera</span><br/>
|
|
<span className="kw">/client/src/ui</span> <span className="cm">{' //'} HUD, inventory, market, chat</span><br/>
|
|
<span className="kw">/server-spacetime/src</span> <span className="cm">{' //'} SpacetimeDB module, reducers</span><br/>
|
|
</code>
|
|
</div>
|
|
|
|
<h3>Packages</h3>
|
|
<table className="data-table">
|
|
<thead><tr><th>Package</th><th>Purpose</th></tr></thead>
|
|
<tbody>
|
|
<tr><td>vite, react, react-dom, typescript</td><td>Core frontend.</td></tr>
|
|
<tr><td>three, @react-three/fiber, @react-three/drei</td><td>3D scene and helper controls.</td></tr>
|
|
<tr><td>zustand</td><td>Local UI/game view state.</td></tr>
|
|
<tr><td>tailwindcss</td><td>Panel and HUD styling.</td></tr>
|
|
<tr><td>spacetimedb TS client</td><td>Backend connection, reducers, subscriptions.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
window.GDD.TechStackPage = TechStackPage;
|