Frontend
The web app is Next.js (App Router) + React + TypeScript with an embedded Phaser game. Supporting libraries: TanStack Query (server state), Zustand (client/game state), Zod (validation), Solana wallet adapter.
Folder structure
alpun-app/src/
├── app/
│ ├── (marketing)/ Homepage, tokenomics, roadmap, universe
│ ├── play/ Phaser game container
│ ├── dashboard/ Staking, inventory, missions, leaderboard
│ └── layout.tsx
├── components/
│ ├── game/ PhaserGame, GameOverlay, LoadingScreen
│ ├── wallet/ Connect button, wallet modal
│ ├── staking/ Positions, deposit/withdraw flows
│ └── ui/
├── game/
│ ├── config.ts Phaser.Game configuration
│ ├── events.ts Typed EventBus bridging React ↔ Phaser
│ ├── objects/ Alpaca, Resource, Building
│ ├── scenes/ Boot, Preload, Farm, UI
│ └── systems/ Movement, Mission, Network
├── stores/ game-store, player-store, wallet-store
├── lib/ api-client, websocket-client, solana-client
└── types/
Responsibilities
| Layer | Owns |
|---|---|
| Next.js | Homepage, SEO pages, wallet connection, dashboard, staking UI, leaderboards, game container |
| Phaser | Rendering, movement, animations, farm interactions, collisions, sound, particles, local prediction |
| TanStack Query | Player profile, missions, staking positions, inventory and leaderboard caching/refresh |
| Zustand | Current session, selected alpaca, audio settings, scene, wallet state shared with Phaser |
| Zod | Validating API responses and game commands |
React ↔ Phaser bridge
React state never lives inside Phaser. A typed event bus connects the two:
export type GameEvents = {
"player:ready": { playerId: string };
"resource:collected": { resourceId: string; quantity: number };
"mission:started": { missionId: string };
"hud:update": { energy: number; wool: number };
};
Direction of traffic:
React → Phaser
Start mission, change selected alpaca, change map,
update wallet-derived information
Phaser → React
Open staking modal, update HUD, show mission result,
display transaction request
Scenes
Phaser scenes are separated by responsibility — BootScene (config),
PreloadScene (asset loading with progress), FarmScene (the world),
UIScene (HUD overlay running in parallel).
Rendering rule
The client is allowed to animate optimistically — collecting a resource plays immediately — but every credited change comes back from the backend via the command response or a WebSocket state delta. If the server rejects a command, the UI reconciles.