CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.Project Overview
Hyperscape is a RuneScape-style MMORPG built on a custom 3D multiplayer engine. The project features a real-time 3D metaverse engine (Hyperscape) in a persistent world.CRITICAL: Secrets and Private Keys
Never put private keys, seed phrases, API keys, tokens, RPC secrets, or wallet secrets into any tracked file.- ALWAYS use local untracked
.envfiles for real secrets during development - NEVER hardcode secrets in source, tests, docs, fixtures, scripts, config files, or GitHub workflow files
- NEVER place real credentials in
.env.example; placeholders only - Production and CI secrets must live in the platform secret manager, not in git
- If a new secret is required, add only the variable name to docs or
.env.exampleand load the real value from.env,.env.local, or deployment secrets
CRITICAL: WebGPU Required (NO WebGL)
Hyperscape requires WebGPU. WebGL WILL NOT WORK. This is a hard requirement due to our use of TSL (Three Shading Language) for all materials and post-processing effects. TSL only works with the WebGPU node material pipeline.Why WebGPU-Only?
- TSL Shaders: All materials use Three.js Shading Language (TSL) which requires WebGPU
- Post-Processing: Bloom, tone mapping, and other effects use TSL-based node materials
- No Fallback: There is NO WebGL fallback - the game will not render without WebGPU
Browser Requirements
- Chrome 113+ (recommended)
- Edge 113+
- Safari 18+ (macOS 15+) — Safari 17 support was removed
- Firefox (behind flag, not recommended)
Server/Streaming Requirements
For Vast.ai and other GPU servers running the streaming pipeline:- NVIDIA GPU with Display Driver REQUIRED: Must have
gpu_display_active=trueon Vast.ai - Display Driver vs Compute: WebGPU requires GPU display driver support, not just compute access
- Must run headful with Xorg or Xvfb (NOT headless Chrome)
- Chrome Beta Channel: Use
google-chrome-beta(Chrome Beta) for WebGPU streaming on Linux NVIDIA (best stability and WebGPU support) - ANGLE Backend: Use Vulkan ANGLE backend (
--use-angle=vulkan) on Linux NVIDIA for WebGPU stability - Xvfb Virtual Display:
scripts/deploy-vast.shstarts Xvfb before PM2 to ensure DISPLAY is available - PM2 Environment:
ecosystem.config.cjsexplicitly forwardsDISPLAY=:99andDATABASE_URLthrough PM2 - Capture Mode: Default to
STREAM_CAPTURE_MODE=cdp(Chrome DevTools Protocol) for reliable frame capture - FFmpeg: Prefer system ffmpeg over ffmpeg-static to avoid segfaults (resolution order:
/usr/bin/ffmpeg→/usr/local/bin/ffmpeg→ PATH → ffmpeg-static) - Playwright: Block
--enable-unsafe-swiftshaderinjection to prevent CPU software rendering - Health Check Timeouts: All curl commands use
--max-time 10to prevent indefinite hangs - If GPU cannot initialize WebGPU, deployment MUST FAIL (no soft fallbacks)
Development Rules for WebGPU
- NEVER add WebGL fallback code - it will not work with TSL shaders
- NEVER use
--disable-webgpuorforceWebGLflags - NEVER use headless Chrome modes that don’t support WebGPU
- All renderer code must assume WebGPU availability
- If WebGPU is unavailable, throw an error immediately
Essential Commands
Development Workflow
Package-Specific Commands
Testing
Mobile Development
Documentation
Architecture Overview
Monorepo Structure
This is a Turbo monorepo with packages:Build Dependency Graph
Critical: Packages must build in this order due to dependencies:- physx-js-webidl - PhysX WASM (takes longest, ~5-10 min first time)
- shared - Depends on physx-js-webidl
- All other packages - Depend on shared
turbo.json configuration handles this automatically via dependsOn: [\"^build\"].
TODO(AUDIT-004): CIRCULAR DEPENDENCY - shared ↔ procgen There is a circular dependency between@hyperscape/sharedand@hyperscape/procgen.Current workaround: procgen build ignores TypeScript errors. Recommended fix: Extract shared types to
- shared imports procgen for vegetation/terrain generation
- procgen imports shared for TileCoord type in viewers
@hyperscape/typespackage:
- Create new package with only type definitions (no runtime code)
- Both shared and procgen depend on types (no circular dep)
- Move TileCoord, Position3D, EntityData to types package
Entity Component System (ECS)
The RPG is built using Hyperscape’s ECS architecture:- Entities: Game objects (players, mobs, items, trees)
- Components: Data containers (position, health, inventory)
- Systems: Logic processors (combat, skills, movement)
RPG Implementation Architecture
Important: Despite references to “Hyperscape apps (.hyp)” in development rules,.hyp files do not currently exist. This is an aspirational architecture pattern for future development.
Current Implementation:
The RPG is built directly into packages/shared/src/ using:
- Entity Classes: PlayerEntity.ts, MobEntity.ts, ItemEntity.ts
- ECS Systems: Combat, inventory, skills, AI in src/systems/
- Components: Data containers for stats, health, equipment, etc.
- Keep RPG game logic conceptually isolated from core Hyperscape engine
- Use existing Hyperscape abstractions (ECS, networking, physics)
- Don’t reinvent systems that Hyperscape already provides
- Separation of concerns: core engine vs. game content
Critical Development Rules
TypeScript Strong Typing
NOany types are allowed - ESLint will reject them.
- Prefer classes over interfaces for type definitions
- Use type assertions when you know the type:
entity as Player - Share types from
types.tsfiles - don’t recreate them - Use
import typefor type-only imports - Make strong type assumptions based on context (don’t over-validate)
File Management
Don’t create new files unless absolutely necessary.- Revise existing files instead of creating
_v2.tsvariants - Delete old files when replacing them
- Update all imports when moving code
- Clean up test files immediately after use
- Don’t create temporary
check-*.ts,test-*.mjs,fix-*.jsfiles
Testing Philosophy
NO MOCKS - Use real Hyperscape instances with Playwright. Every feature MUST have tests that:- Start a real Hyperscape server
- Open a real browser with Playwright
- Execute actual gameplay actions
- Verify with screenshots + Three.js scene queries
- Save error logs to
/logs/folder
- 🔴 Players
- 🟢 Goblins
- 🔵 Items
- 🟡 Trees
- 🟣 Banks
Production Code Only
- No TODOs or “will fill this out later” - implement completely
- No hardcoded data - use JSON files and general systems
- No shortcuts or workarounds - fix root causes
- Build toward the general case (many items, players, mobs)
Separation of Concerns
- Data vs Logic: Never hardcode data into logic files
- RPG vs Engine: Keep RPG isolated from Hyperscape core
- Types: Define in
types.ts, import everywhere - Systems: Use existing Hyperscape systems before creating new ones
Working with the Codebase
Understanding Hyperscape Systems
Before creating new abstractions, research existing Hyperscape systems:- Check packages/shared/src/systems/
- Look for similar patterns in existing code
- Use Hyperscape’s built-in features (ECS, networking, physics)
- Read entity/component definitions in
types/folders
Common Patterns
Getting Systems:Development Server
The dev server provides:- Hot module replacement (HMR) for client
- Auto-rebuild and restart for server
- Watch mode for shared package
- Colored logs for debugging
Port Allocation
All services have unique default ports to avoid conflicts:Environment Variables
Zero-config local development: The defaults work out of the box. Just runbun run dev.
Secret handling is non-negotiable:
- Real private keys and API tokens must come from local untracked
.envfiles - Tracked files may only contain placeholders and variable names
- If you find a real credential in a tracked file, remove it and move it to
.envor the deployment secret store immediately
.env files: Each package has its own .env.example with deployment documentation:
Common variables:
PUBLIC_PRIVY_APP_ID(client) must equalPRIVY_APP_ID(server)PUBLIC_WS_URLandPUBLIC_API_URLmust point to your server- WebSocket port is 5556 (uWebSockets.js), not 5555 (HTTP)
Package Manager
This project uses Bun (v1.3.14+) as the package manager and runtime for client/build tasks. Server Runtime: Node.js 22+ (migrated from Bun in March 2026 for V8 incremental GC)- Install:
bun install(NOTnpm install) - Run scripts:
bun run <script>orbun <file> - Some commands use
npmprefix for Turbo workspace filtering
Tech Stack
- Runtime:
- Client/Build: Bun v1.3.14+ (Docker image:
oven/bun:1.3.14-alpine) - Server: Node.js 22+ (migrated from Bun for V8 incremental GC - March 2026)
- Client/Build: Bun v1.3.14+ (Docker image:
- Rendering: WebGPU ONLY (Three.js WebGPURenderer + TSL shaders) - NO WebGL
- Engine: Three.js 0.184.0 (shared/plugin-hyperia), Three.js 0.183.2 (client), PhysX (WASM)
- UI: React 19.2.7, Tailwind CSS 4.1.14 (with @tailwindcss/postcss), lucide-react 1.8.0
- Server: Fastify (HTTP), uWebSockets.js v20.68.0 (game WebSocket), LiveKit (voice)
- Server Plugins: @fastify/static 9.1.2, @fastify/multipart 10.0.0
- Database: PostgreSQL (production, connection pool: 20), Docker (local), sqlite3 6.0.1 (dev only)
- Testing: Vitest 4.1.8+, Jest 30.3.0, Playwright 25.1.0 (WebGPU-enabled browsers only), jsdom 29.1.1
- Build: Vite 8.0.0, @vitejs/plugin-react 6.0.1, Turbo, esbuild
- TypeScript: 6.0.3 (server, plugin-hyperia), 6.0.2 (shared, client)
- AI: ElizaOS
alphatag (aligned with latest alpha releases), @elizaos/plugin-goals 2.0.0-alpha.10 - Streaming: FFmpeg (system preferred over ffmpeg-static), Playwright Chromium, RTMP
- Mobile: Capacitor 8.2.0 (Android, iOS)
- Smart Contracts: Hardhat 3.1.11+, @nomicfoundation/hardhat-ethers 4.0.6 (ethers.js v6)
- Utilities: dotenv 17.4.2, msgpackr 2.0.2, uuid 14.0.0, immer 11.1.8, commander 15.0.0
- VRM Avatars: @pixiv/three-vrm 3.5.3
Recent Changes (June 2026)
Dependency Updates (June 3, 2026)
Change (PRs #1199–#1230, merged viashaw/dependabot-merge-main): Batch merge of 30+ Dependabot dependency updates.
Key Upgrades:
Three.js 0.184.0 (shared, plugin-hyperia)
- PR #1220 (shared), PR #1218 (client), PR #1195 (plugin-hyperia):
three0.183.2→0.184.0 - PR #1196:
@types/three0.183.1→0.184.1(plugin-hyperia) - Three.js 0.184.0 includes WebGPU renderer improvements and TSL shader enhancements.
React 19.2.7 (all packages)
- PR #1229 (root), PR #1222 (shared), PR #1221 (client), PR #1213 (asset-forge):
react19.2.5→19.2.7 - PR #1226 (root), PR #1205 (client):
react-dom19.2.5→19.2.7 - Patch release with bug fixes and stability improvements.
uWebSockets.js v20.68.0 (server)
- PR #1228 (root), PR #1227 (server):
uWebSockets.jsv20.64.0→v20.68.0 - Performance and stability improvements. Review the uWebSockets.js releases for details.
uuid 14.0.0 (Major Version — server)
- PR #1230 (root), PR #1204 (server):
uuid13.0.2→14.0.0 - Major version bump. Review the uuid changelog for breaking changes.
msgpackr 2.0.2 (Major Version — shared, client, server, plugin-hyperia)
- PR #1223 (shared), PR #1216 (client), PR #1207 (server), PR #1197 (plugin-hyperia):
msgpackr1.11.12→2.0.2 - Major version bump for the MessagePack serialization library used for network packets.
immer 11.1.8 (Major Version — root, asset-forge)
- PR #1217 (root), PR #1206 (asset-forge):
immer10.2.0→11.1.8 - Major version bump. Review the immer changelog for breaking changes.
puppeteer 25.1.0 (Major Version — root)
- PR #1224:
puppeteer24.43.1→25.1.0 - Major version bump for the browser automation library used in streaming.
jsdom 29.1.1 (shared, client)
- PR #1225 (root), PR #1214 (shared), PR #1208 (client):
jsdom29.0.2→29.1.1 - Minor version update with bug fixes for the test environment.
@pixiv/three-vrm 3.5.3 (shared)
- PR #1215:
@pixiv/three-vrm3.5.1→3.5.3 - Patch update for VRM avatar support.
commander 15.0.0 (Major Version — server)
- PR #1219 (root), PR #1203 (server):
commander14.0.3→15.0.0 - Major version bump for the CLI argument parsing library.
@fastify/multipart 10.0.0 (Major Version — shared)
- PR #1211:
@fastify/multipart9.4.0→10.0.0(shared package) - Major version bump. Review the @fastify/multipart changelog for breaking changes.
@fastify/static 9.1.2 (server)
- PR #1183:
@fastify/static8.3.0→9.1.2(server) - Major version bump. Review the @fastify/static changelog for breaking changes.
concurrently 10.0.3 (Major Version — asset-forge)
- PR #1212:
concurrently9.2.1→10.0.3(asset-forge) - Major version bump for the concurrent process runner.
eslint-plugin-react-hooks 7.1.1 (Major Version — asset-forge)
- PR #1209:
eslint-plugin-react-hooks5.2.0→7.1.1(asset-forge) - Major version bump for the React hooks ESLint plugin.
Bun Docker Image 1.3.14 (server Dockerfile)
- PR #1199:
oven/bunDocker image1.3.10-alpine→1.3.14-alpine - Applies to both builder and runtime stages in
packages/server/Dockerfile.
GitHub Actions Updates
- PR #1202:
softprops/action-gh-release2→3 - PR #1201:
actions/configure-pages5→6 - PR #1200:
actions/upload-pages-artifact3→5 - PR #1192:
android-actions/setup-android3→4 - PR #1191:
actions/github-script8→9 - PR #1190:
appleboy/ssh-action1.0.3→1.2.5
-
uuid 14.0.0: Review any code that uses
uuidfor API changes. Thev4()function signature is unchanged but other exports may differ. - msgpackr 2.0.2: Major version bump — if you use msgpackr directly (not through the game’s networking layer), review the msgpackr changelog for breaking changes.
- immer 11.x: If you use immer in custom code, review the immer v11 migration guide.
- @fastify/multipart 10.0.0: Check route handlers that use multipart uploads for API changes.
- commander 15.0.0: If you use commander in custom scripts, review the commander changelog for breaking changes.
Recent Changes (April 2026)
Vegetation Model Caching Fixes (April 10, 2026)
Change (PR #1144, Commits aca6e95, a5405da, 8af2566): Fixed mushroom disappearance and tree texture corruption on fresh GLTF load. Scope: 3 files changed, +230 additions, -60 deletions in shared package. Problems Fixed:-
Mushroom Disappearance: GLTF files often store geometry using
InterleavedBufferAttribute, where multiple attributes share one interleavedArrayBuffer.serializeScenewas calling.arrayon these attributes and passing the entire interleaved buffer (all attributes combined) as if it were a single attribute. On deserialization, vertex counts were fractional/NaN, bounding boxes were corrupted, andmodelBaseOffset = NaNcaused every instance to be rejected byaddInstanceToChunk. -
Tree Texture Corruption: Three.js WebGPU has two texture upload paths:
DataTexture→writeTexture(raw byte copy, no transformation)ImageBitmapTexture(fresh GLTF) →copyExternalImageToTexture(browser applies a color-space decode step)
copyExternalImageToTexturepath performs a browser-side sRGB decode during upload, which corrupts the stored values when the destination is anrgba8unorm-srgbtexture.DataTexture(from IndexedDB cache) useswriteTexturewhich copies bytes directly and renders correctly.
packages/shared/src/utils/rendering/ModelCache.ts):
extractAttr()helper: DeinterleavesInterleavedBufferAttributeby reading each component individually viagetComponent(), producing contiguous typed arrays. Matches source typed array constructor (e.g., Uint16Array for skinIndex) instead of hardcoding Float32Array.ensureDataTexture()helper: ConvertsImageBitmapTexturetoDataTextureso all textures use WebGPU’swriteTextureupload path (raw byte copy) instead ofcopyExternalImageToTexture(browser-side colorspace decode). ForwardsminFilter,magFilter,generateMipmaps,repeat,offsetto prevent mipmap/aliasing regression.- Fast DataTexture path:
textureToPixelData()now reads DataTexture pixel data directly without canvas round-trip.
packages/shared/src/systems/shared/world/GPUMaterials.ts):
- Smooth diffuse ramp: Replaced 4-band toon shading with continuous smoothstep diffuse ramp (warm highlights → cool shadows) plus narrow warm-tinted shadow terminator band.
- Softened rim light: Changed from binary step to smoothstep falloff for smoother edge highlights.
packages/shared/src/utils/rendering/ModelCache.ts— Interleaved buffer deinterleaving, ImageBitmapTexture → DataTexture conversionpackages/shared/src/systems/shared/world/GPUMaterials.ts— Smooth diffuse ramp tree shaderpackages/shared/src/systems/shared/world/VegetationSystem.ts— Consistent bracing, improved logging
- Mushrooms render correctly after cache clear (no more disappearing vegetation)
- Tree textures render consistently between fresh GLTF loads and cached loads
- Smoother tree lighting with continuous diffuse ramp instead of hard toon bands
- Proper mipmap filtering on all textures (no pixelation at distance)
Day/Night Cycle Duration Fix (April 10, 2026)
Change (PR #1143, Commit 740ee24): Restored day/night cycle duration and AO dark floor. Problem: Accidental changes during terrain refactor caused day/night cycle to run 3× too fast and tree AO to be too bright. Fix:DAY_CYCLE.DURATION_SECrestored to 240s (was 80s)AO_DARKfloor in tree shader restored to 0.4 (was accidentally changed)
- Day/night cycle runs at correct speed (4 minutes per full cycle)
- Tree ambient occlusion darkening restored to proper intensity
CI/Test Stability Fixes (April 10, 2026)
Change (Commits a16b67d, d185d6e, dc98a63, 7f8e438, 4167d16): Resolved lint, typecheck, and test failures across the codebase. Key Fixes: Shared Package:- Added
EntityOccupancyMapto barrel export (packages/shared/src/index.ts) - Fixed
TileMovementManagertest to align with actualprocessPlayerTickbehavior (path-follow + clear-on-arrival) - Updated
GPUMaterialstest expectations to match current LODConfig values (tree fade=1800, extended distances) - Fixed
BuildingTerrainInteractionWATER_THRESHOLD: 8.0 → 16 (actual value from TERRAIN_CONSTANTS) - Fixed
CookingCalculatorburn chance test to account for MAX_BURN_CHANCE=0.55 cap - Updated
LODQualitytree draw distance bound: 300 → 2000 - Fixed
DuelSystemejection test: lobby → starter area (0,0) per actual code
- Fixed
DuelCombatAI: added “prayer” to combatRole union - Fixed
connection-handler: Map value type, serialize return type, spectator position type - Fixed
StreamingDuelScheduler: replaceddb.querywithdb.selectpattern (avoids schema generic) - Fixed
streamingDuelEligibilityDb: column name to match schema (streamingDuelEnabled) - Exported
isActiveStreamingDuelContestantfromagentRecovery - Removed empty else block in
EmbeddedHyperscapeService(lint error) - Removed duplicate
getWorld()method inEmbeddedHyperscapeService
- Fixed
ShellPreviewViewerlint: captureref.currentin local vars for cleanup, add proper deps touseImperativeHandle - Fixed
TextureGeneratorTablint: remove unnecessarydetailLeveldep, rename unused loop vari→_i - Fixed
AgentViewportChatlint: remove 3 unused eslint-disable directives
- Fixed
AgentBehaviorEnginetest: unique characterIds per test to avoid module-level Map contamination, add missingstationPositionsfield, fix goal expectation - Fixed
ModelCachelint: castout.buffertoArrayBuffer(ArrayBufferLike not assignable in strict mode) - Removed unused eslint-disable directives in
dashboardInterop.ts
- Removed orphaned
.claude/worktreesgitlinks (were committed as mode 160000 submodules with no .gitmodules URL) - Added
.claude/worktrees/to.gitignore
- CI builds pass reliably
- All tests pass with correct expectations
- Lint and typecheck errors resolved
- Cleaner git history without orphaned submodule references
Armor Pipeline POC (April 5-8, 2026)
Change (PR #1142, Commits 3b265f3-4e7f4be): Complete armor generation pipeline for AssetForge with shell extraction, AI texturing, rigging, and publish-to-game workflow. Scope: 26 files changed, +12,109 additions, -8 deletions across asset-forge and shared packages. Core Features:1. Shell Extraction (POC-1)
Extracts body-fitting armor shells from VRM avatars by bone weight analysis with curvature-adaptive offset, boundary tapering, and constrained smooth. New Module:packages/asset-forge/src/services/armor-pipeline/ShellExtractionService.ts (2,058 lines)
Key Features:
- Bone Weight Analysis: Assigns vertices to equipment slots (helmet, body, legs, boots, gloves) based on VRM humanoid bone weights
- Marching Triangles: Splits boundary triangles at bone-weight isolines for smooth slot transitions (eliminates jagged edges)
- Curvature-Adaptive Offset: Clamps offset at high-curvature areas (armpits, groin) to prevent self-intersection
- Boundary Tapering: Gradual falloff at shell edges (0.5 → 1.0 over 3 rings) for smooth transitions
- Body-Constrained Laplacian Smooth: Enforces minimum distance from body surface while smoothing
- UV Seam Bridging: Averages normals at coincident vertices to prevent cracks at UV seams
- Bulk Classes: Four thickness presets (skin: 1mm, cloth: 5mm, leather: 12mm, plate: 30mm) + custom offset support
BULK_OFFSETS in types.ts):
2. AI Texturing (POC-2)
Meshy AI retexture integration with server-side shell hosting and polling-based task completion. New Modules:packages/asset-forge/server/services/armor-pipeline/ShellTextureService.ts(300 lines)packages/asset-forge/src/services/armor-pipeline/ArmorTextureService.ts(190 lines)
packages/asset-forge/server/routes/armor-pipeline.ts):
POST /api/armor-pipeline/texture-shell— Upload shell GLB + start AI texture generationPOST /api/armor-pipeline/texture-shell-batch— Batch retexture for multiple tiers (bronze → dragon)GET /api/armor-pipeline/texture-status/:taskId— Poll task statusGET /api/armor-pipeline/texture-download/:taskId— Download textured resultPOST /api/armor-pipeline/publish-to-game— Publish rigged GLB to game model directory + update manifest
- Base64 Data URI Upload: Sends shell GLB as base64 data URI to Meshy (no public URL/ngrok needed)
- Pre-Painting: Shells are pre-painted with target color (e.g., bronze #cd7f32) so Meshy sees “bronze metallic object” instead of “grey body shape”
- Shape-Override Prompts: Prefix prompts with “medieval plate armor, hard metallic surface, not skin, not clothing, not a body” to override Meshy’s body-shape semantic interpretation
- Detail Levels: Five ornamentation levels (plain → intricate) control AI texture complexity
- OSRS Tier Presets: Eight material tiers (bronze, iron, steel, black, mithril, adamant, rune, dragon) with hex codes for color accuracy
- Batch Generation: Generate all 8 tiers from one shell with staggered API calls (2s delay between requests)
- SSRF Protection: Download URLs validated against Meshy/Tripo domain allowlists
- Path Traversal Prevention:
path.basename()sanitization on all file paths - Localhost-Only Publish:
/publish-to-gamerestricted to localhost requests viaserver.requestIP()check - Content-Length Guards: 100MB max download size on Meshy/Tripo results
- Private IP Blocking:
isValidPublicUrl()blocks RFC 1918, link-local, loopback, CGN ranges
3. Tripo 3D Pipeline (Experimental)
Tripo 3D AI integration for segment → per-part texture pipeline and 3D attachment generation. New Modules:packages/asset-forge/server/services/armor-pipeline/TripoService.ts(757 lines)packages/asset-forge/src/services/armor-pipeline/ArmorTripoService.ts(306 lines)
packages/asset-forge/server/routes/tripo-pipeline.ts):
POST /api/tripo/upload-and-segment— Upload shell → import → segment → return part namesPOST /api/tripo/texture-part— Texture specific parts with custom promptsPOST /api/tripo/complete— Reassemble model after per-part texturingPOST /api/tripo/texture-shell— Upload shell → import → texture (whole model, no segments)POST /api/tripo/text-to-model— Generate 3D model from text promptGET /api/tripo/task/:taskId— Poll task statusGET /api/tripo/download/:taskId— Download result (proxied to avoid URL expiry)GET /api/tripo/balance— Check account balance
- STS Upload: Uses AWS Security Token Service for direct S3 upload (no server proxy)
- Mesh Segmentation: Automatically discovers armor parts (shoulders, chest, back, arms, waist, legs)
- Per-Part Texturing: Assign different prompts to each part (e.g., “ornate pauldrons” for shoulders, “engraved breastplate” for chest)
- Granular Retry: Texture chain state persists in localStorage so failures resume from last successful step (no credit waste)
- 3D Attachments: Generate rigid pieces (pauldrons, crests, guards) via text-to-model and parent to VRM bones
- Bone Attachment System: Position, rotate, and scale 3D pieces on specific bones with real-time preview
ATTACHMENT_SLOTS in constants.ts):
4. Shell Re-Rigging (POC-3)
Transfers bone weights from original shells to textured meshes with bounding box alignment for Meshy’s model normalization. New Module:packages/asset-forge/src/services/armor-pipeline/ShellRiggingService.ts (469 lines)
Key Features:
- Fast-Path Direct Copy: When vertex counts match (Meshy
enable_original_uv=true), directly copy skinIndex/skinWeight attributes - Nearest-Vertex Fallback: When vertex counts differ, find nearest source vertex for each destination vertex and copy weights
- Bounding Box Alignment: Scales and translates textured geometry to match original shell (handles Meshy centering/normalization)
- Full Skeleton Export: Exports complete VRM skeleton with original bone indices preserved for game’s simple skeleton swap
- Metadata Embedding: Embeds
userData.hyperscapewith bone attachment info for game’sEquipmentVisualSystem
5. Multi-Piece Armor Kit
Texture individual slots, accumulate in kit, rig all pieces onto same VRM skeleton with per-piece visibility toggles. New Components:packages/asset-forge/src/pages/ArmorPipelinePage.tsx(295 lines) — Main pipeline orchestratorpackages/asset-forge/src/components/ArmorPipeline/ShellGeneratorTab.tsx(538 lines) — Shell extraction UIpackages/asset-forge/src/components/ArmorPipeline/TextureGeneratorTab.tsx(1,566 lines) — Texture generation UIpackages/asset-forge/src/components/ArmorPipeline/TierGeneratorTab.tsx(786 lines) — Batch tier generation UIpackages/asset-forge/src/components/ArmorPipeline/TripoGeneratorTab.tsx(1,727 lines) — Tripo pipeline wizardpackages/asset-forge/src/components/ArmorPipeline/ArmorPreviewTab.tsx(806 lines) — Rigging + animation previewpackages/asset-forge/src/components/ArmorPipeline/ShellPreviewViewer.tsx(917 lines) — Three.js viewer with animation support
- Extract — Generate shells from VRM avatar (all slots × all bulk classes)
- Texture — Apply materials (solid color, AI texture, or batch tiers)
- Tiers — Batch-generate bronze → dragon variants from one shell
- Rig & Preview — Re-rig textured pieces onto animated VRM skeleton
- Publish — Export to game’s model directory + update armor manifest
6. Game Equipment Rendering Fixes
Fixed equipment rendering to work correctly with armor pipeline output. Key Changes (packages/shared/src/systems/client/EquipmentVisualHelpers.ts):
- Zero Metalness: Set
metalness = 0on all equipment materials (game has no environment map, so metallic PBR materials appear black) - RenderOrder Fix: Set
renderOrder = 100on equipment meshes to render on top of player silhouette (renderOrder 50) - DoubleSide Materials: Ensure all equipment materials use
THREE.DoubleSidefor correct rendering
- Armor pipeline GLBs render correctly in-game
- Equipment appears on top of player body (not underneath)
- Metallic materials show base color instead of appearing black
7. Environment Variables
New Variables (packages/asset-forge/.env.example):
packages/asset-forge/server/api-elysia.ts— Armor/Tripo route registration, temp-shells static serving, CORS defaultspackages/asset-forge/server/routes/armor-pipeline.ts— Meshy texture endpoints + publish-to-game (520 lines, new)packages/asset-forge/server/routes/tripo-pipeline.ts— Tripo segment/texture/complete endpoints (342 lines, new)packages/asset-forge/src/services/armor-pipeline/ShellExtractionService.ts— Shell extraction algorithm (2,058 lines, new)packages/asset-forge/src/services/armor-pipeline/ShellRiggingService.ts— Weight transfer + GLB export (469 lines, new)packages/asset-forge/src/services/armor-pipeline/ShellTextureService.ts— Meshy API client (300 lines, new)packages/asset-forge/src/services/armor-pipeline/TripoService.ts— Tripo API client with STS upload (757 lines, new)packages/asset-forge/src/services/armor-pipeline/ArmorTextureService.ts— Client-side Meshy wrapper (190 lines, new)packages/asset-forge/src/services/armor-pipeline/ArmorTripoService.ts— Client-side Tripo wrapper (306 lines, new)packages/asset-forge/src/services/armor-pipeline/types.ts— Shared types (137 lines, new)packages/asset-forge/src/services/armor-pipeline/constants.ts— Avatars, slots, tiers, attachments (270 lines, new)packages/asset-forge/src/pages/ArmorPipelinePage.tsx— Main pipeline UI (295 lines, new)packages/asset-forge/src/components/ArmorPipeline/*.tsx— Six new UI components (6,340 lines total)packages/shared/src/systems/client/EquipmentVisualHelpers.ts— Metalness fix + renderOrder
- Complete armor generation pipeline from VRM avatar to game-ready GLB
- No public URL/ngrok needed for local development (base64 data URI upload)
- Batch tier generation (8 OSRS tiers from one shell in ~5 minutes)
- Experimental Tripo pipeline for per-part texturing and 3D attachments
- Game-ready GLB export with full skeleton and metadata
- Publish-to-game workflow updates armor manifest automatically
- Path traversal prevention via
path.basename()andSAFE_PATH_REregex - SSRF domain allowlists for Meshy/Tripo/S3 downloads
- Localhost-only publish endpoint with
server.requestIP()validation - Content-Disposition header sanitization to prevent header injection
- Private IP blocking in
isValidPublicUrl()(RFC 1918, link-local, loopback, CGN) - Task ID format validation before URL interpolation
- Content-Length guards on all downloads (100MB max)
- Meshy retexture costs ~$0.20 per piece, takes 2-5 minutes
- Tripo segment + texture costs ~$0.40-0.80 per shell, takes 3-8 minutes
- Nearest-vertex weight transfer is O(n×m) brute-force (slow on high-poly meshes)
- No authentication on armor/tripo endpoints (localhost dev only)
Terrain & Tree Visual Overhaul (April 5-7, 2026)
Change (PR #1126, Commits 1bf2342-3bb9875): Complete rewrite of tree rendering system with vertex-color-driven shaders, terrain color tuning, and water shader improvements. Tree System Overhaul:-
Vertex-Color Shader (Commit 1bf2342):
- Trees now use vertex colors for material properties:
- R channel: Leaf mask (0 = bark, 1 = leaf) — drives wind animation and SSS
- G channel: Ambient occlusion — darkens crevices and modulates snow weight
- B channel: Unused (reserved for future features)
- 4-Band Toon Lighting: Quantized Ghibli-style lighting with hard-edged shadow/mid/bright bands
- Subsurface Scattering (SSS): Optional leaf translucency when backlit (controlled by
ENABLE_TREE_SSStoggle) - Fresnel Rim: Hard-edged rim highlights on leaves for silhouette definition
- Wind Animation: Vertex displacement driven by leaf mask, auto-scales to model height
- Snow System: Biome-driven snow coverage with cubic falloff at boundaries
- Trees now use vertex colors for material properties:
-
Per-Instance Frustum Culling (Commit a5cf0cb):
- Uses
BatchedMesh.setVisibleAt()for per-tree frustum + distance culling - Builds world-space bounding sphere per slot each frame (center at tree mid-height, radius from modelHeight/radius + 4m buffer)
- Marks off-screen or >1200m instances invisible
- Safe with
sortObjects=false—setVisibleAtmarks slots without removing them, so indirect drawIndex→instanceId mapping never shifts - Fixes tree-swap bug seen with
perObjectFrustumCulled=true
- Uses
-
Model Cache Fix (Commit 1bf2342):
- Fixed serialization to correctly slice typed-array views instead of copying entire underlying ArrayBuffer
- Store
colorItemSizefor RGBA vertex colors (was missing, causing corruption) - Bumped processed model cache version to invalidate corrupted entries
- Removed broken cache-busting in
resolveURL, use dev-appropriate Cache-Control headers instead
-
Tree Type Cleanup (Commits cb59c60, bdb086c):
- Removed unused Willow and Fir tree types (no GLB assets, no manifest entries, no biome configs)
- Updated biome allocations to match available tree models
- Clarified module-level doc comments with concrete file paths
-
Grass/Dirt Balance:
- Lowered
DIRT_THRESHOLDto show more dirt coverage on flat terrain - Updated
TERRAIN_BIOME_TEXTURES.dirtfallback sRGB to match newdirt.png(0.55, 0.48, 0.36) - Updated GPU non-textured
FOREST_DIRT/FOREST_DIRT_DARKlinear constants to match new texture - Updated CPU replica
_FOREST_DIRTto match new dirt.png average color
- Lowered
-
Grass Color Fix:
- Fixed hardcoded
_FOREST_DIRT/_FOREST_DIRT_DARKlinear values inGrassWorkerstring - Was old yellow-sandy
lin(0.82, 0.64, 0.34), now matches new dirt.png - This was the root cause of yellow grass roots on brown dirt terrain
- Fixed hardcoded
-
Grass Texture Remap (Commit 3bb9875):
- Remapped
grass.pngto olive-green hue (87.5°) matching reference screenshot - Reduced saturation and slightly darker value
- Synced
TerrainShader_FOREST_GRASSfallback andGrassWorkerpre-linearized constants to new grass texture average sRGB (0.39, 0.52, 0.24)
- Remapped
-
Biome Tuning (Commits 4eb855f, d3d9286):
- Reduced forest tree density and widened cluster spacing for less crowded forests
- Normalized scale variation across all biomes to [1.0, 1.2]
- Tuned forest/canyon grass configs:
maxSlope,minGrassWeight,heightScale,patchScale - Forest: reweighted general/oak/mahogany, added pine (high-altitude), palm+banana (water-affinity)
- Canyon: added maple and magic tree types
- Tightened forest
clusterSpacing200→100 for denser forest clusters
-
Flow-Mapped Normals (Commit 09f2399):
- Replaced fixed 4-layer scrolling normals with two-phase flow crossfade (FlowUVW technique from cloud-sea shader)
- Loads
waterNormal.pngandnoise28.pngtextures with procedural fallbacks - Organic, non-repeating water surface motion
-
Color Palette (Commits baeb870, 3bb9875):
- Shifted from bright blue to dark green-blue teal
- Shallow water: sRGB display (0.276, 0.541, 0.595)
- Deep water: darker teal with less grey/red, boosted green channel
- Updated foam color to match cooler teal tone
-
River Carving (Commit af4f07c):
- Removed hardcoded river carving from canyon height function
- Canyon water features now controlled purely by
rivers/lakes/lakesFalloffconfig params like other biomes
- Disabled color grading and depth blur effects (commented out in
createPostProcessingconfig) - Keeps post-processing pipeline wired up for future use
- Restored minimap accidentally hidden during frustum culling work
- WorkerPool: Track active tasks per worker and reject them on
terminate()so in-flight promises no longer dangle - GLBTreeBatchedInstancer: Guard against duplicate
addInstancefor same entityId; makeaddToPoolatomic - GLBTreeInstancer: Add
MAX_INSTANCEScapacity check; guard duplicate entityId; clone attributes increateSharedGeometry - ProcgenTreeInstancer: Use
tracked.presetinremoveInstance; add capacity guard inshowInMesh - GrassVisualManager: Add destroyed flag to guard async callbacks; cancel
workerInflight/pendingLodSwapon prune/destroy/invalidate/rebuild
packages/shared/src/systems/shared/world/GLBTreeBatchedInstancer.ts- Per-instance frustum culling, dissolve system, batch color channel layoutpackages/shared/src/systems/shared/world/GLBTreeInstancer.ts- Dissolve support for InstancedMesh treespackages/shared/src/systems/shared/world/DissolveAnimation.ts- Shared dissolve state machinepackages/shared/src/systems/shared/world/GPUMaterials.ts- Vertex-color tree shader with toon lighting, SSS, windpackages/shared/src/systems/shared/world/TerrainBiomeTypes.ts- Updated tree distributions, removed Willow/Firpackages/shared/src/systems/shared/world/TerrainShader.ts- Grass/dirt color updatespackages/shared/src/utils/workers/GrassWorker.ts- Fixed dirt color constantspackages/shared/src/systems/shared/world/WaterSystem.ts- Flow-mapped normals, teal color palettepackages/shared/src/utils/rendering/ModelCache.ts- Fixed typed-array serializationpackages/shared/src/constants/TreeTypes.ts- Removed Willow and Fir enum values
GPU_VEG_CONFIG in GPUMaterials.ts):
- Photorealistic tree rendering with toon-shaded foliage
- Smooth resource depletion/respawn feedback
- Improved terrain color accuracy matching reference screenshots
- Organic water motion without repetitive patterns
- Better performance via per-instance frustum culling (1200m max render distance)
- Eliminated tree type confusion (Willow/Fir had no assets)
- Fixed memory leaks in worker pools and instancer systems
- Consistent grass/dirt colors across GPU shader and CPU worker code
Client Runtime Environment Hydration (April 7, 2026)
Change (Commits 8753bb6, ebbb9ed): Fixed auth configuration to resolve from runtime environment. Problem: Client auth config was reading from build-time environment variables (import.meta.env.PUBLIC_PRIVY_APP_ID), causing auth failures in production when runtime env differed from build env. This made it impossible to deploy the same client bundle to multiple environments with different Privy App IDs.
Fix: Hydrate runtime environment before auth bootstrap. Auth config now resolves from window.__RUNTIME_ENV__ injected at runtime via public/env.js.
Implementation (packages/client/src/lib/api-config.ts):
packages/client/src/auth/PrivyAuthProvider.tsx):
packages/client/src/lib/api-config.tsnow reads from runtime env with fallback to build env- Auth bootstrap waits for runtime env hydration before initializing Privy
- Production deployments (Railway, Cloudflare) inject runtime config correctly via
/env.jsendpoint public/env.jsis generated at server startup with current environment variables
- Auth works correctly in production environments
- Runtime configuration overrides build-time defaults
- Fixes “Invalid Privy App ID” errors in deployed environments
- Same client bundle can be deployed to multiple environments (dev/staging/prod)
- No client rebuild needed to change auth provider configuration
Docker Runtime Migration (April 7, 2026)
Change (Commit 4fd1d44): Use Debian Trixie runtime for uWebSockets.js compatibility. Problem: uWebSockets.js requires GLIBC ≥ 2.38, which is not available in Debian Bookworm (GLIBC 2.36). Fix: Switched Docker runtime fromnode:22-bookworm-slim to node:22-trixie-slim to provide GLIBC 2.38+ for uWebSockets.js native bindings.
Impact:
- Production Docker images now support uWebSockets.js for high-performance WebSocket handling
- Enables 50+ concurrent players with 25+ AI agents (from March 2026 performance overhaul)
- Required for production deployment with uWS-based networking
Production Runtime Defaults (April 5-6, 2026)
Change (Commits ba7f6f4-bc647e3): Restored Railway deployment targets and production API defaults. Key Changes:- Production Defaults: Server defaults to
hyperscape.ggfor production runtime URLs - Railway Targets: Restored Railway deployment configuration for dev/prod environments
- Local WebSocket: Fixed local development to use correct WebSocket defaults (port 5556)
- Agent Runtime: ElizaOS agents use local Hyperscape uWS defaults for connection
- Simplified production deployment (fewer env vars needed)
- Better separation between local dev and production environments
- AI agents connect correctly to local game server during development
- Production deployments work out-of-the-box with hyperscape.gg
CI/CD Infrastructure Upgrades (April 6, 2026)
Change (Commits 15e62b9-9d45fae): Upgraded GitHub Actions workflows to Node.js 24 runners. Key Changes:- Updated all GitHub Actions to use
node24runners for improved performance - Fixed workflow token usage for Claude review automation
- Removed unused Foundry installations from CI pipeline to reduce build times
- Switched Docker builds to use real Node.js instead of Bun for Vite builds (better stability)
- Faster CI builds with latest GitHub runner infrastructure
- More reliable Docker image builds with Node.js-based Vite compilation
- Reduced CI complexity and build times
- Better automation workflow reliability
Docker Build Fixes (April 6, 2026)
Change (Commits fca9ffb-cb237b6): Fixed Docker build failures and CI pipeline issues. Key Changes:- Defensive Directory Creation: Added
mkdir -pforpackages/web3/node_modulesandpackages/client/node_modulesto prevent COPY failures when Bun hoists deps without materializing per-package node_modules - Empty Downloads Handling: Fixed CI pipeline to handle empty download artifacts gracefully
- Railway Auth Drift: Resolved Railway authentication drift issues in deployment pipeline
- Node.js for Vite: Switched Docker builds to use real Node.js for Vite builds instead of Bun’s Node compatibility shim
Dockerfile.server):
- Reliable Docker image builds across all environments
- No more missing node_modules directory errors
- Improved CI/CD stability
- Production deployments work consistently
Tailwind CSS Stabilization (April 2026)
Change (PR #1105, subsequent updates): Tailwind CSS build pipeline stabilization. Timeline:- April 4: Temporarily rolled back to Tailwind v3.4.19 due to production artifact issues with v4
- Current: Stable on Tailwind v3.4.19 with standard PostCSS pipeline
- Uses standard PostCSS pipeline with
tailwindcssplugin - Stable CSS generation across all build environments
- Consistent auth and character screen styling in production Docker images
- All critical utilities (inset-0, gap-2, p-6, bg-black/80, shadow-2xl) reliably generated
packages/client/postcss.config.js):
- Consistent CSS output across development and Docker production builds
- No more missing utility classes in production
- Stable build pipeline for deployment
- Reliable auth and character screen styling
Recent Changes (March 2026)
UI Panel Tooltip System (March 27, 2026)
Change (PR #1102): Unified tooltip styling across all UI panels. Features: Centralized tooltip style utilities for consistent appearance across inventory, equipment, bank, spells, prayer, skills, trade, store, and loot panels. New Module:packages/client/src/ui/core/tooltip/tooltipStyles.ts
Key Functions:
- Consistent tooltip appearance across all UI panels
- Eliminated ~500 lines of duplicated styling code
- Better visual hierarchy and readability
- Easier to maintain and update tooltip styles globally
Tree Dissolve Transparency (March 27, 2026)
Change (PR #1101): Added screen-door dithered dissolve for depleted trees. Features: Depleted trees become ~70% transparent instantly on depletion and animate back to full opacity over 0.3s on respawn. New Module:packages/shared/src/systems/shared/world/DissolveAnimation.ts
Key APIs:
GPU_VEG_CONFIG in GPUMaterials.ts):
- Encoding: Blue channel of batch color encodes
1.0 - dissolveVal(BatchedMesh), or dedicatedinstanceDissolveattribute (InstancedMesh) - Dithering: Uses Bayer 4×4 screen-door dithering in
alphaTestNodeto discard fragments - Opaque Pass: Trees stay in opaque render pass (no transparency sorting overhead)
- LOD Preservation: Dissolve state carries over during LOD transitions to prevent visual pops
- Atomic Initial State:
initialDissolveparameter onaddInstance()prevents 1-frame flash
- Visual feedback for resource depletion/respawn
- Stays in opaque render pass (no transparency sorting overhead)
- Smooth LOD transitions without visual pops
- Eliminates ~60 lines of duplication between instancer files
Tree Collision Proxy (March 27, 2026)
Change (PR #1100): Use LOD2 model geometry for tree collision instead of oversized cylinder. Problem: Cylinder hitbox (0.4 radius factor) was too large, intercepting ground clicks near trees. Fix: Use actual LOD2 mesh geometry for pixel-accurate collision. Falls back to tighter cylinder (0.25 radius) if LOD unavailable. New APIs:- Cache merged+scaled proxy geometry per
(sourceGeometries, scale)tuple - Avoids redundant merge/clone/scale work for trees sharing same model variant and scale
- Cache cleared on world teardown via
clearProxyGeometryCache()
- Clicks only register on visible tree silhouette
- Ground clicks near trees work correctly
- Cached geometry reduces CPU overhead
- Memory-efficient (shared geometry references)
Resource Respawn System (March 27, 2026)
Change (PR #1099): Made resource respawn purely tick-based, use manifestdepleteChance for mining.
Problem: setTimeout-based respawn was non-deterministic. Mining used hardcoded MINING_DEPLETE_CHANCE instead of manifest values.
Fix: Remove setTimeout entirely. Respawn handled by ResourceSystem.processRespawns() via tick counting. Mining reads depleteChance from manifest.
Key Changes:
- Removed
MINING_DEPLETE_CHANCEandMINING_REDWOOD_DEPLETE_CHANCEconstants - Resources with
depleteChance: 0never deplete (rune essence rocks) - Deterministic tick-based respawn timing
- OSRS-accurate resource mechanics
- Rune essence rocks work correctly (never deplete)
- Predictable respawn timing
Troubleshooting
Build Issues
PhysX Build Fails
PhysX is pre-built and committed. If it needs rebuilding:Port Conflicts
Tests Failing
- Ensure server is not running before tests
- Check
/logs/folder for error details - Tests spawn their own Hyperscape instances
- Visual tests require WebGPU support (headful browser with GPU access)
-
TileMovementManager test fails: Test expectations must match actual
processPlayerTickbehavior (path-follow + clear-on-arrival). If test expects path to persist after arrival, update to expect cleared path. -
GPUMaterials test fails: LODConfig values change frequently. Update test expectations to match current values in
LODConfig.ts(e.g., tree fade=1800, not 180). -
CookingCalculator burn chance test fails: Account for
MAX_BURN_CHANCE=0.55cap when testing burn probabilities. - DuelSystem ejection test fails: Players are ejected to starter area (0,0), not duel lobby. Update test expectations accordingly.
-
AgentBehaviorEngine test contamination: Use unique
characterIdper test to avoid module-level Map contamination between tests. Add missing required fields likestationPositions.
CI/Lint Failures
Common Issues:-
Missing barrel exports: If you add a new class/type to shared package, add it to
packages/shared/src/index.tsbarrel export. -
Unused eslint-disable directives: Remove
// eslint-disable-next-linecomments that are no longer needed after fixing the underlying issue. -
Empty else blocks: ESLint rejects empty
else {}blocks with--max-warnings 0. Either add logic or remove the else block. -
Duplicate methods: Check for duplicate method definitions in classes (e.g., multiple
getWorld()methods). -
Type mismatches:
- Use
db.select()pattern instead ofdb.query()to avoid schema generic issues - Cast
ArrayBufferLiketoArrayBufferin strict mode when needed - Ensure union types include all possible values (e.g., “prayer” in combatRole union)
- Use
Vegetation Rendering Issues
Mushrooms disappearing after cache clear:- Fixed in April 2026 (PR #1144)
- If you see this on older commits, update to latest main
- Root cause:
InterleavedBufferAttributeserialization was copying entire interleaved buffer instead of deinterleaving
- Fixed in April 2026 (PR #1144)
- If you see this on older commits, update to latest main
- Root cause:
ImageBitmapTextureusescopyExternalImageToTexturewhich applies browser-side sRGB decode, corrupting colors - Solution: All textures now converted to
DataTexturefor consistentwriteTextureupload path
Docker Build Failures
Symptoms:COPY failed: file not found errors for packages/*/node_modules directories.
Cause: Bun may hoist workspace dependencies without materializing per-package node_modules directories.
Fix: The Dockerfile.server now includes defensive mkdir -p commands to create all required directories before COPY operations.
Verification:
Tailwind CSS Missing Utilities
Symptoms: Auth screen or character screen appears unstyled in production Docker builds. Cause: Tailwind v4 had issues with utility generation in linux/amd64 Docker builds. Current State: Project uses stable Tailwind v3.4.19 with standard PostCSS pipeline. Verification:Additional Resources
- README.md - Full project documentation
- AGENTS.md - AI coding assistant instructions
- .cursor/rules/ - Detailed development rules
- packages/shared/ - Core engine source
- Game Design Document: See
.cursor/rules/gdd.mdc