Initial public release of PSX Adventure Engine
Browser reference stack for PSX-era third-person adventure: fixed cameras, inventory puzzles, Box3D physics, host-authoritative P2P co-op, and a modular character harness. Ships the Ashgrove Precinct Level 1 investigation demo with a full cast and nine linked rooms.
This commit is contained in:
@@ -0,0 +1,481 @@
|
||||
# PSX Adventure Engine
|
||||
**Reference Project / Game Design Document**
|
||||
Version 0.1 — July 2026
|
||||
|
||||
---
|
||||
|
||||
## 1. Vision
|
||||
|
||||
A modern, browser-first **reference engine and project template** for creating authentic PSX-era third-person survival-horror and action-adventure games in the style of:
|
||||
|
||||
- Resident Evil 1 & 2
|
||||
- Parasite Eve
|
||||
- Dino Crisis
|
||||
- Silent Hill (classic)
|
||||
|
||||
The engine prioritizes:
|
||||
|
||||
- Fixed or limited cameras
|
||||
- Tank or camera-relative controls
|
||||
- Inventory, item combination, and puzzle gameplay
|
||||
- Atmospheric low-poly aesthetics
|
||||
- True peer-to-peer multiplayer
|
||||
- Rapid content creation via photo-to-character pipelines
|
||||
|
||||
It is designed as a clean, fully-typed foundation that teams can fork and extend into complete games (or other genres later).
|
||||
|
||||
---
|
||||
|
||||
## 2. Core Pillars
|
||||
|
||||
1. **Authentic PSX Feel**
|
||||
Low-poly geometry, affine texture mapping, dithering, color quantization, fixed cameras, optional tank controls, atmospheric fog and limited draw distance.
|
||||
|
||||
2. **Content Velocity**
|
||||
Powerful img2mesh + common-skeleton harness so characters and items can be generated from reference photographs and immediately animated.
|
||||
|
||||
3. **Multiplayer-Native**
|
||||
Host-authoritative P2P WebRTC for 2–4 player co-op out of the box.
|
||||
|
||||
4. **Cloud Persistence**
|
||||
Neon-backed accounts, cloud saves, lobby system, and shared campaign progress.
|
||||
|
||||
5. **Clean Architecture**
|
||||
Strict separation between runtime engine, backend services, and content-creation harnesses.
|
||||
|
||||
---
|
||||
|
||||
## 3. Goals & Non-Goals
|
||||
|
||||
### Goals
|
||||
- Provide a production-quality reference "engine of sorts" for the target genre.
|
||||
- Support both classic fixed-camera and freer camera styles.
|
||||
- Deliver true browser P2P multiplayer with host-authoritative simulation.
|
||||
- Enable rapid character and item creation from reference photos.
|
||||
- Keep everything TypeScript-first and highly modular.
|
||||
|
||||
### Non-Goals
|
||||
- Competing with Unity or Unreal as a general-purpose engine.
|
||||
- High-fidelity modern PBR graphics or complex soft-body physics.
|
||||
- Dedicated-server authoritative multiplayer (we stay pure P2P + lightweight signaling).
|
||||
- Mobile-first design (desktop + modern browsers are the primary target).
|
||||
|
||||
---
|
||||
|
||||
## 4. Technical Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|--------------------|-------------------------------------------------|
|
||||
| Runtime | Three.js (vanilla or React Three Fiber) + TypeScript + Vite |
|
||||
| Physics | Box3D (via `box3d-wasm`) |
|
||||
| Networking | PeerJS (or simple-peer) + custom WebSocket signaling |
|
||||
| Backend | Hono + Neon Postgres |
|
||||
| Asset Pipeline | Node-based tools + optional local web UI |
|
||||
| Build / Monorepo | pnpm / Turborepo or simple Vite workspaces |
|
||||
|
||||
---
|
||||
|
||||
## 5. Monorepo Structure
|
||||
|
||||
```
|
||||
/
|
||||
├── client/ # Three.js game runtime
|
||||
│ ├── src/
|
||||
│ │ ├── engine/ # core loop, scene manager
|
||||
│ │ ├── systems/ # Camera, Player, Inventory, Interaction, Multiplayer…
|
||||
│ │ ├── post/ # PSX visual pipeline
|
||||
│ │ └── ...
|
||||
│ └── public/
|
||||
│
|
||||
├── server/ # Hono API + WebSocket signaling
|
||||
│ ├── routes/ # auth, saves, lobbies
|
||||
│ ├── ws/ # SDP/ICE exchange
|
||||
│ └── db/ # Neon + Drizzle/Prisma
|
||||
│
|
||||
├── shared/ # Shared types & definitions
|
||||
│ ├── skeleton/ # canonical skeleton
|
||||
│ ├── types/
|
||||
│ └── schemas/
|
||||
│
|
||||
├── tools/ # Content creation harness
|
||||
│ ├── src/
|
||||
│ │ ├── img2mesh/
|
||||
│ │ ├── rig/
|
||||
│ │ ├── anim/
|
||||
│ │ ├── items/
|
||||
│ │ ├── postprocess/
|
||||
│ │ └── export/
|
||||
│ └── web/ # optional local UI
|
||||
│
|
||||
└── assets/ # Generated GLBs + animation clips
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Core Runtime Systems
|
||||
|
||||
### 6.1 CameraManager
|
||||
- Fixed camera zones using trigger volumes (classic RE style).
|
||||
- Smooth or hard cuts between cameras.
|
||||
- Optional free / follow / rail cameras for Silent Hill style.
|
||||
- Camera-relative movement so "up" always feels correct after a cut.
|
||||
|
||||
### 6.2 PlayerController
|
||||
- Classic tank controls (toggleable).
|
||||
- Modern camera-relative movement.
|
||||
- Quick-turn, aim, interact, item use.
|
||||
- Uses Box3D character mover under the hood.
|
||||
|
||||
### 6.3 InventorySystem
|
||||
- Classic RE-style list or RE4-style grid.
|
||||
- Item examination, combination, and key-item usage.
|
||||
- Fully serializable to JSON for cloud saves.
|
||||
|
||||
### 6.4 Interaction & Puzzle Framework
|
||||
- Examine, pick up, use item on object, door/key systems.
|
||||
- Trigger volumes and state machines for puzzles.
|
||||
- Sensors in Box3D for detection.
|
||||
|
||||
### 6.5 Combat & AI (baseline)
|
||||
- Simple melee and limited-ammo ranged combat.
|
||||
- Basic enemy behaviors suitable for the genre.
|
||||
- Host simulates all AI and damage.
|
||||
|
||||
### 6.6 Multiplayer Layer
|
||||
See section 8.
|
||||
|
||||
---
|
||||
|
||||
## 7. Physics — Box3D
|
||||
|
||||
Box3D (Erin Catto, 2026) is the primary physics engine.
|
||||
|
||||
**Usage guidelines:**
|
||||
- Player → Capsule + character mover
|
||||
- Rooms / static geometry → Triangle meshes or static compounds
|
||||
- Triggers (cameras, items, doors, puzzles) → Sensors
|
||||
- Dynamic props / enemies → Capsules or convex hulls
|
||||
- Determinism is leveraged for host-authoritative multiplayer
|
||||
|
||||
Integration via `box3d-wasm`. Colliders and sensors are auto-generated or defined in asset metadata.
|
||||
|
||||
---
|
||||
|
||||
## 8. Multiplayer Design
|
||||
|
||||
- **Transport**: PeerJS (or simple-peer) data channels after WebSocket signaling.
|
||||
- **Model**: Host-authoritative.
|
||||
- Host (usually lobby creator) owns world simulation, AI, inventory truth, door/puzzle states.
|
||||
- Clients send high-level actions / inputs.
|
||||
- Host validates and broadcasts state or deltas.
|
||||
- Update rate: 10–20 Hz (adventure pacing is forgiving).
|
||||
- Interpolation + simple prediction on clients.
|
||||
- STUN (Google public) by default; TURN optional for reliability.
|
||||
- Shared campaign progress is written to Neon on checkpoints or session end.
|
||||
|
||||
---
|
||||
|
||||
## 9. Backend & Persistence (Neon)
|
||||
|
||||
### Core Tables
|
||||
|
||||
```sql
|
||||
users (
|
||||
id UUID PRIMARY KEY,
|
||||
username TEXT UNIQUE,
|
||||
email TEXT UNIQUE,
|
||||
display_name TEXT,
|
||||
created_at, last_login
|
||||
)
|
||||
|
||||
saves (
|
||||
id UUID PRIMARY KEY,
|
||||
user_id UUID REFERENCES users,
|
||||
slot INT,
|
||||
campaign TEXT,
|
||||
title TEXT,
|
||||
data JSONB NOT NULL, -- inventory, flags, positions, puzzle states…
|
||||
playtime_seconds INT,
|
||||
updated_at,
|
||||
UNIQUE(user_id, slot, campaign)
|
||||
)
|
||||
|
||||
lobbies (
|
||||
id UUID PRIMARY KEY,
|
||||
code TEXT UNIQUE, -- short room code
|
||||
host_id UUID,
|
||||
status TEXT, -- waiting / starting / in_game / closed
|
||||
max_players INT DEFAULT 4,
|
||||
settings JSONB,
|
||||
created_at, expires_at
|
||||
)
|
||||
|
||||
lobby_players (
|
||||
lobby_id, user_id,
|
||||
peer_id INT,
|
||||
is_ready BOOLEAN,
|
||||
PRIMARY KEY (lobby_id, user_id)
|
||||
)
|
||||
```
|
||||
|
||||
JSONB is used heavily for flexible save data and lobby settings.
|
||||
|
||||
---
|
||||
|
||||
## 10. Asset Pipeline & Harness
|
||||
|
||||
### 10.1 Prioritization
|
||||
|
||||
1. **Primary (Characters) — shipped path**
|
||||
Photo (Grok `/imagine` or file) → silhouette measure → code-drawn low-poly mesh on the
|
||||
canonical skeleton → automatic skin weights → procedural clips → GLB.
|
||||
Geometry is constructed in code from measured proportions; we do **not** use Meshy
|
||||
or other third-party mesh APIs for the default harness.
|
||||
|
||||
2. **Secondary (Props / Stylized)**
|
||||
Same measurement → code-drawn approach, simplified for non-humanoids.
|
||||
|
||||
### 10.2 Canonical Skeleton
|
||||
|
||||
Mixamo-compatible core set:
|
||||
|
||||
```
|
||||
Hips
|
||||
Spine → Spine1 → Spine2 → Neck → Head
|
||||
LeftShoulder → LeftArm → LeftForeArm → LeftHand
|
||||
RightShoulder → RightArm → RightForeArm → RightHand
|
||||
LeftUpLeg → LeftLeg → LeftFoot → LeftToeBase
|
||||
RightUpLeg → RightLeg → RightFoot → RightToeBase
|
||||
```
|
||||
|
||||
Defined in `shared/skeleton/canonical.ts`. All auto-riggers map into these names.
|
||||
|
||||
### 10.3 Tools Folder Structure
|
||||
|
||||
```
|
||||
tools/
|
||||
├── src/
|
||||
│ ├── grok/ # Grok CLI bridge (image_gen / image_edit)
|
||||
│ ├── image/ # decode, segment, measure reference photos
|
||||
│ ├── mesh/ # code-drawn humanoid from measurements
|
||||
│ ├── rig/ # canonical skeleton + automatic skin weights
|
||||
│ ├── anim/ # procedural Idle / Walk / Run / QuickTurn
|
||||
│ ├── export/ # hand-rolled GLB writer
|
||||
│ ├── pipeline.ts # end-to-end photo → GLB
|
||||
│ └── cli.ts # `pnpm harness`
|
||||
└── fixtures/ # reference images for tests
|
||||
```
|
||||
|
||||
### 10.4 Key Interfaces (TypeScript)
|
||||
|
||||
```ts
|
||||
interface Img2MeshRequest {
|
||||
image: Buffer | string;
|
||||
style?: "realistic" | "lowpoly" | "psx";
|
||||
provider?: "meshy" | "triposr" | "img2threejs";
|
||||
}
|
||||
|
||||
interface RigResult {
|
||||
glbPath: string;
|
||||
boneMap: Record<string, string>;
|
||||
}
|
||||
|
||||
interface CharacterHarness {
|
||||
fromPhoto(image: Buffer | string, options?: Img2MeshRequest): Promise<{
|
||||
mesh: RigResult;
|
||||
defaultPose?: AnimationClipResult;
|
||||
}>;
|
||||
generateAnimation(from: "pose" | "video" | "procedural", input: any): Promise<AnimationClipResult>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. PSX Visual Pipeline
|
||||
|
||||
- Vertex snapping
|
||||
- Affine texture mapping
|
||||
- Ordered dithering
|
||||
- Color depth reduction
|
||||
- Low-resolution render target + nearest-neighbor upscale
|
||||
- Optional CRT / scanlines / fog
|
||||
- Implemented via material `onBeforeCompile` patches + EffectComposer passes
|
||||
|
||||
---
|
||||
|
||||
## 12. Demo Content (First Playable)
|
||||
|
||||
**Ashgrove Precinct — Level 1**: a procedural crime-investigation loop inside a
|
||||
classic fixed-camera survival-horror building. Traverse rooms, question living
|
||||
NPCs, gather evidence, and open locked wings with inventory puzzles.
|
||||
|
||||
- 9 connected rooms with fixed cameras (lobby, squad hall, office, interview,
|
||||
lounge, corridor, records, break room, evidence vault)
|
||||
- One dedicated room per ambient cast member + player Detective body
|
||||
- Question / examine dialogue on every NPC (Reyes, Ellis, Voss, Kane, Hale, Marrow)
|
||||
- Inventory + key items + crank/valve + brass/vault key progression
|
||||
- Evidence pickups (statement, badge case, photograph, journal)
|
||||
- 2-player co-op lobby
|
||||
- Cloud save / load
|
||||
- Harness-driven cast GLBs with Idle / Walk / Slumped poses
|
||||
|
||||
---
|
||||
|
||||
## 13. Roadmap / Milestones
|
||||
|
||||
**Milestone 1 – Core Runtime**
|
||||
- CameraManager + PlayerController (tank + camera-relative)
|
||||
- Basic room system + Box3D integration
|
||||
- Inventory + Interaction
|
||||
|
||||
**Milestone 2 – Multiplayer**
|
||||
- PeerJS lobby + host-authoritative movement & interactions
|
||||
- Neon auth + cloud saves
|
||||
|
||||
**Milestone 3 – Asset Harness**
|
||||
- Grok CLI bridge for reference generation (`image_gen`)
|
||||
- Silhouette analysis → measurements + palette
|
||||
- Code-drawn mesh on canonical skeleton + automatic skin weights
|
||||
- Procedural locomotion clips + GLB export into the runtime
|
||||
|
||||
**Milestone 4 – First Playable Demo**
|
||||
- Complete small campaign
|
||||
- Full 2-player co-op
|
||||
- Documentation & example workflows
|
||||
|
||||
**Later**
|
||||
- Expanded animation library
|
||||
- More advanced AI & combat
|
||||
- Additional camera modes
|
||||
- Modding / user-generated content hooks
|
||||
|
||||
---
|
||||
|
||||
## 14. Style Guide (PSX Aesthetic Rules)
|
||||
|
||||
- Prefer low polygon counts and limited texture resolution.
|
||||
- Embrace affine distortion and vertex jitter where it serves atmosphere.
|
||||
- Use strong silhouettes and readable shapes over fine detail.
|
||||
- Fog, limited draw distance, and carefully placed lights are primary tools for tension.
|
||||
- UI should feel period-appropriate (simple, slightly chunky, limited color palette).
|
||||
|
||||
---
|
||||
|
||||
## Appendix A — Canonical Skeleton Definition
|
||||
|
||||
Full TypeScript definition lives in `shared/skeleton/canonical.ts` (see section 10.2).
|
||||
|
||||
## Appendix B — Neon Schema
|
||||
|
||||
Full SQL is maintained in `server/db/schema.sql` (see section 9).
|
||||
|
||||
## Appendix C — Key Interfaces
|
||||
|
||||
See section 10.4 and the `tools/src/**/types.ts` files.
|
||||
|
||||
---
|
||||
|
||||
## Appendix D — Implementation Addendum (Milestone 1, July 2026)
|
||||
|
||||
Recorded as systems were implemented, per the note below that this is a living
|
||||
document. Nothing here changes the design intent; it records where the intent
|
||||
met the current state of the dependencies.
|
||||
|
||||
### D.1 Box3D capability gaps
|
||||
|
||||
`box3d-wasm@0.2.0` (published 2026-07-02) binds less of upstream Box3D than
|
||||
§6.2 and §7 assume. Verified by enumerating the embind prototypes on the built
|
||||
wasm module:
|
||||
|
||||
| Assumed in this document | Actual | Resolution |
|
||||
| --- | --- | --- |
|
||||
| Box3D character mover (§6.2, §7) | Not bound | Hand-rolled collide-and-slide capsule mover, `client/src/systems/CharacterMover.ts` |
|
||||
| Triangle-mesh room collision (§7) | No trimesh shape; only box, sphere, capsule, hull | Rooms are static compounds of boxes and convex hulls. `ColliderDef` in `shared` is deliberately narrowed to the four supported primitives so the asset pipeline cannot emit collision the runtime can't build |
|
||||
| Capsule sweeps for the mover | No shape cast or overlap query; `castRayClosest` and `shape.rayCast` only | A fan of ray probes (three heights × three lateral offsets) approximates the sweep |
|
||||
|
||||
Confirmed working and load-bearing: raycasts with filters, sensor begin/end
|
||||
events keyed by `userData`, kinematic bodies via `setTargetTransform`, convex
|
||||
hulls, contact and body events.
|
||||
|
||||
The package ships no TypeScript declarations. `client/src/types/box3d-wasm.d.ts`
|
||||
declares the API as observed at runtime rather than as documented — the README
|
||||
describes a slightly larger surface than is actually bound.
|
||||
|
||||
### D.2 Determinism claim in §7
|
||||
|
||||
§7 states determinism is leveraged for host-authoritative multiplayer. These are
|
||||
independent properties: host-authoritative simulation requires only that one
|
||||
peer owns the state and broadcasts it, which is what §8 actually describes.
|
||||
Determinism is a requirement of *lockstep* networking, which this project is not
|
||||
using. Milestone 2 should not be designed around a determinism guarantee that a
|
||||
0.2.0 wasm build has not demonstrated.
|
||||
|
||||
### D.3 Userdata tag collisions
|
||||
|
||||
box3d-wasm keeps separate auto-increment counters for body and shape
|
||||
`userData` tags, so an auto-assigned body tag and shape tag can be numerically
|
||||
equal. Since sensor and contact events reference *shape* tags, `PhysicsWorld`
|
||||
assigns every tag from a single counter of its own rather than relying on the
|
||||
auto-assigned values.
|
||||
|
||||
### D.4 Vite worker format
|
||||
|
||||
The threaded ("deluxe") physics build spawns emscripten pthread workers whose
|
||||
entry uses top-level await. Vite bundles workers as `iife` by default, which
|
||||
cannot express that, and the production build fails. `worker: { format: "es" }`
|
||||
in `client/vite.config.ts` is required, not optional.
|
||||
|
||||
### D.5 Affine texture mapping technique
|
||||
|
||||
GLSL ES 3.00 has no `noperspective` interpolation qualifier, so §11's affine
|
||||
mapping cannot be requested directly. It is instead produced arithmetically:
|
||||
the vertex shader emits `uv * w` and `w` as varyings and the fragment shader
|
||||
divides them. The interpolator's own `1/w` factors cancel, leaving
|
||||
screen-linear UVs. The patch is applied only to materials that carry a `map`,
|
||||
since without one three.js may not declare the `uv` attribute at all.
|
||||
|
||||
### D.6 WebRTC transport: neither PeerJS nor simple-peer
|
||||
|
||||
§4 and §8 name "PeerJS (or simple-peer) + custom WebSocket signaling". Those
|
||||
two halves are in tension, and neither library fits:
|
||||
|
||||
- **simple-peer** depends on `buffer` and `readable-stream`, Node polyfills that
|
||||
are a recurring source of Vite build breakage.
|
||||
- **PeerJS** is built around its own broker protocol. Using it would *replace*
|
||||
the custom WebSocket signalling §4 calls for, not complement it.
|
||||
|
||||
What both libraries would have provided is a data channel following an SDP
|
||||
exchange, which is a thin wrapper over `RTCPeerConnection`. `client/src/net/
|
||||
PeerLink.ts` is that wrapper, with no dependencies, and it matches §8's
|
||||
description exactly: data channels established after WebSocket signalling.
|
||||
|
||||
Each link carries **two** channels rather than one, because gameplay traffic has
|
||||
two delivery profiles: `state` (unreliable, unordered) for position snapshots,
|
||||
which are worthless once superseded, and `event` (reliable, ordered) for flags,
|
||||
despawns and inventory, where a single drop desynchronises the world.
|
||||
|
||||
### D.7 Co-op inventory is shared, not per-player
|
||||
|
||||
§6.3 and §8 do not say whether each peer carries their own inventory. In this
|
||||
implementation the party shares one, held by the host. It matches how the
|
||||
genre's co-op modes work, and it keeps a single authoritative copy rather than
|
||||
four that can disagree. Guests display a read-only mirror pushed by the host.
|
||||
|
||||
### D.8 Host reach checks for remote interactions
|
||||
|
||||
Interaction reach is detected with Box3D sensors, but sensors only ever track
|
||||
the *local* player's capsule. When a guest asks to interact, the host therefore
|
||||
re-derives proximity from that guest's authoritative mover position before
|
||||
running the interaction (`Game.handleGuestInteract`). Without this a guest could
|
||||
open any door in the room by naming its id.
|
||||
|
||||
### D.9 Key items and inventory capacity
|
||||
|
||||
§6.3 does not say whether key items consume inventory slots. They do not, in
|
||||
this implementation: a full inventory would otherwise be able to soft-lock a
|
||||
puzzle. They are held outside the slot array and serialised separately in
|
||||
`InventoryState.keyItems`.
|
||||
|
||||
---
|
||||
|
||||
*This document is the living design and technical specification for the PSX Adventure Engine reference project. It will be updated as systems are implemented and lessons are learned.*
|
||||
Reference in New Issue
Block a user