Skip to main content

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 .env files 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.example and 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=true on 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.sh starts Xvfb before PM2 to ensure DISPLAY is available
  • PM2 Environment: ecosystem.config.cjs explicitly forwards DISPLAY=:99 and DATABASE_URL through 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-swiftshader injection to prevent CPU software rendering
  • Health Check Timeouts: All curl commands use --max-time 10 to 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-webgpu or forceWebGL flags
  • 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:
  1. physx-js-webidl - PhysX WASM (takes longest, ~5-10 min first time)
  2. shared - Depends on physx-js-webidl
  3. All other packages - Depend on shared
The turbo.json configuration handles this automatically via dependsOn: [\"^build\"].
TODO(AUDIT-004): CIRCULAR DEPENDENCY - shared ↔ procgen There is a circular dependency between @hyperscape/shared and @hyperscape/procgen.
  • shared imports procgen for vegetation/terrain generation
  • procgen imports shared for TileCoord type in viewers
Current workaround: procgen build ignores TypeScript errors. Recommended fix: Extract shared types to @hyperscape/types package:
  • 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)
All game logic runs through systems, not entity methods. Entities are just data containers.

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: Design Principle (from development rules):
  • 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

NO any 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.ts files - don’t recreate them
  • Use import type for 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.ts variants
  • 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-*.js files

Testing Philosophy

NO MOCKS - Use real Hyperscape instances with Playwright. Every feature MUST have tests that:
  1. Start a real Hyperscape server
  2. Open a real browser with Playwright
  3. Execute actual gameplay actions
  4. Verify with screenshots + Three.js scene queries
  5. Save error logs to /logs/ folder
Visual testing uses colored cube proxies:
  • 🔴 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:
  1. Check packages/shared/src/systems/
  2. Look for similar patterns in existing code
  3. Use Hyperscape’s built-in features (ECS, networking, physics)
  4. Read entity/component definitions in types/ folders

Common Patterns

Getting Systems:
Entity Queries:
Event Handling:

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
Commands:

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 run bun run dev. Secret handling is non-negotiable:
  • Real private keys and API tokens must come from local untracked .env files
  • 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 .env or the deployment secret store immediately
Package-specific .env files: Each package has its own .env.example with deployment documentation: Common variables:
Split deployment (client and server on different hosts):
  • PUBLIC_PRIVY_APP_ID (client) must equal PRIVY_APP_ID (server)
  • PUBLIC_WS_URL and PUBLIC_API_URL must 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 (NOT npm install)
  • Run scripts: bun run <script> or bun <file>
  • Some commands use npm prefix 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)
  • 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 alpha tag (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 via shaw/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): three 0.183.20.184.0
  • PR #1196: @types/three 0.183.10.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): react 19.2.519.2.7
  • PR #1226 (root), PR #1205 (client): react-dom 19.2.519.2.7
  • Patch release with bug fixes and stability improvements.

uWebSockets.js v20.68.0 (server)

  • PR #1228 (root), PR #1227 (server): uWebSockets.js v20.64.0v20.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): uuid 13.0.214.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): msgpackr 1.11.122.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): immer 10.2.011.1.8
  • Major version bump. Review the immer changelog for breaking changes.

puppeteer 25.1.0 (Major Version — root)

  • PR #1224: puppeteer 24.43.125.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): jsdom 29.0.229.1.1
  • Minor version update with bug fixes for the test environment.

@pixiv/three-vrm 3.5.3 (shared)

  • PR #1215: @pixiv/three-vrm 3.5.13.5.3
  • Patch update for VRM avatar support.

commander 15.0.0 (Major Version — server)

  • PR #1219 (root), PR #1203 (server): commander 14.0.315.0.0
  • Major version bump for the CLI argument parsing library.

@fastify/multipart 10.0.0 (Major Version — shared)

  • PR #1211: @fastify/multipart 9.4.010.0.0 (shared package)
  • Major version bump. Review the @fastify/multipart changelog for breaking changes.

@fastify/static 9.1.2 (server)

  • PR #1183: @fastify/static 8.3.09.1.2 (server)
  • Major version bump. Review the @fastify/static changelog for breaking changes.

concurrently 10.0.3 (Major Version — asset-forge)

  • PR #1212: concurrently 9.2.110.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-hooks 5.2.07.1.1 (asset-forge)
  • Major version bump for the React hooks ESLint plugin.

Bun Docker Image 1.3.14 (server Dockerfile)

  • PR #1199: oven/bun Docker image 1.3.10-alpine1.3.14-alpine
  • Applies to both builder and runtime stages in packages/server/Dockerfile.

GitHub Actions Updates

  • PR #1202: softprops/action-gh-release 23
  • PR #1201: actions/configure-pages 56
  • PR #1200: actions/upload-pages-artifact 35
  • PR #1192: android-actions/setup-android 34
  • PR #1191: actions/github-script 89
  • PR #1190: appleboy/ssh-action 1.0.31.2.5
Migration Notes: If you encounter issues after these updates:
  1. uuid 14.0.0: Review any code that uses uuid for API changes. The v4() function signature is unchanged but other exports may differ.
  2. 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.
  3. immer 11.x: If you use immer in custom code, review the immer v11 migration guide.
  4. @fastify/multipart 10.0.0: Check route handlers that use multipart uploads for API changes.
  5. 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:
  1. Mushroom Disappearance: GLTF files often store geometry using InterleavedBufferAttribute, where multiple attributes share one interleaved ArrayBuffer. serializeScene was calling .array on 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, and modelBaseOffset = NaN caused every instance to be rejected by addInstanceToChunk.
  2. Tree Texture Corruption: Three.js WebGPU has two texture upload paths:
    • DataTexturewriteTexture (raw byte copy, no transformation)
    • ImageBitmapTexture (fresh GLTF) → copyExternalImageToTexture (browser applies a color-space decode step)
    The copyExternalImageToTexture path performs a browser-side sRGB decode during upload, which corrupts the stored values when the destination is an rgba8unorm-srgb texture. DataTexture (from IndexedDB cache) uses writeTexture which copies bytes directly and renders correctly.
Fixes: ModelCache.ts (packages/shared/src/utils/rendering/ModelCache.ts):
  • extractAttr() helper: Deinterleaves InterleavedBufferAttribute by reading each component individually via getComponent(), producing contiguous typed arrays. Matches source typed array constructor (e.g., Uint16Array for skinIndex) instead of hardcoding Float32Array.
  • ensureDataTexture() helper: Converts ImageBitmapTexture to DataTexture so all textures use WebGPU’s writeTexture upload path (raw byte copy) instead of copyExternalImageToTexture (browser-side colorspace decode). Forwards minFilter, magFilter, generateMipmaps, repeat, offset to prevent mipmap/aliasing regression.
  • Fast DataTexture path: textureToPixelData() now reads DataTexture pixel data directly without canvas round-trip.
GPUMaterials.ts (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.
Key Files Changed:
  • packages/shared/src/utils/rendering/ModelCache.ts — Interleaved buffer deinterleaving, ImageBitmapTexture → DataTexture conversion
  • packages/shared/src/systems/shared/world/GPUMaterials.ts — Smooth diffuse ramp tree shader
  • packages/shared/src/systems/shared/world/VegetationSystem.ts — Consistent bracing, improved logging
Impact:
  • 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_SEC restored to 240s (was 80s)
  • AO_DARK floor in tree shader restored to 0.4 (was accidentally changed)
Impact:
  • 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 EntityOccupancyMap to barrel export (packages/shared/src/index.ts)
  • Fixed TileMovementManager test to align with actual processPlayerTick behavior (path-follow + clear-on-arrival)
  • Updated GPUMaterials test expectations to match current LODConfig values (tree fade=1800, extended distances)
  • Fixed BuildingTerrainInteraction WATER_THRESHOLD: 8.0 → 16 (actual value from TERRAIN_CONSTANTS)
  • Fixed CookingCalculator burn chance test to account for MAX_BURN_CHANCE=0.55 cap
  • Updated LODQuality tree draw distance bound: 300 → 2000
  • Fixed DuelSystem ejection test: lobby → starter area (0,0) per actual code
Server Package:
  • Fixed DuelCombatAI: added “prayer” to combatRole union
  • Fixed connection-handler: Map value type, serialize return type, spectator position type
  • Fixed StreamingDuelScheduler: replaced db.query with db.select pattern (avoids schema generic)
  • Fixed streamingDuelEligibilityDb: column name to match schema (streamingDuelEnabled)
  • Exported isActiveStreamingDuelContestant from agentRecovery
  • Removed empty else block in EmbeddedHyperscapeService (lint error)
  • Removed duplicate getWorld() method in EmbeddedHyperscapeService
Asset-Forge Package:
  • Fixed ShellPreviewViewer lint: capture ref.current in local vars for cleanup, add proper deps to useImperativeHandle
  • Fixed TextureGeneratorTab lint: remove unnecessary detailLevel dep, rename unused loop var i_i
  • Fixed AgentViewportChat lint: remove 3 unused eslint-disable directives
Test Fixes:
  • Fixed AgentBehaviorEngine test: unique characterIds per test to avoid module-level Map contamination, add missing stationPositions field, fix goal expectation
  • Fixed ModelCache lint: cast out.buffer to ArrayBuffer (ArrayBufferLike not assignable in strict mode)
  • Removed unused eslint-disable directives in dashboardInterop.ts
Submodule Cleanup:
  • Removed orphaned .claude/worktrees gitlinks (were committed as mode 160000 submodules with no .gitmodules URL)
  • Added .claude/worktrees/ to .gitignore
Impact:
  • 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
API:
Configuration (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)
Server Routes (packages/asset-forge/server/routes/armor-pipeline.ts):
  • POST /api/armor-pipeline/texture-shell — Upload shell GLB + start AI texture generation
  • POST /api/armor-pipeline/texture-shell-batch — Batch retexture for multiple tiers (bronze → dragon)
  • GET /api/armor-pipeline/texture-status/:taskId — Poll task status
  • GET /api/armor-pipeline/texture-download/:taskId — Download textured result
  • POST /api/armor-pipeline/publish-to-game — Publish rigged GLB to game model directory + update manifest
Key Features:
  • 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)
Security:
  • 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-game restricted to localhost requests via server.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
API:

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)
Server Routes (packages/asset-forge/server/routes/tripo-pipeline.ts):
  • POST /api/tripo/upload-and-segment — Upload shell → import → segment → return part names
  • POST /api/tripo/texture-part — Texture specific parts with custom prompts
  • POST /api/tripo/complete — Reassemble model after per-part texturing
  • POST /api/tripo/texture-shell — Upload shell → import → texture (whole model, no segments)
  • POST /api/tripo/text-to-model — Generate 3D model from text prompt
  • GET /api/tripo/task/:taskId — Poll task status
  • GET /api/tripo/download/:taskId — Download result (proxied to avoid URL expiry)
  • GET /api/tripo/balance — Check account balance
Key Features:
  • 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 (ATTACHMENT_SLOTS in constants.ts):
API:

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.hyperscape with bone attachment info for game’s EquipmentVisualSystem
API:

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 orchestrator
  • packages/asset-forge/src/components/ArmorPipeline/ShellGeneratorTab.tsx (538 lines) — Shell extraction UI
  • packages/asset-forge/src/components/ArmorPipeline/TextureGeneratorTab.tsx (1,566 lines) — Texture generation UI
  • packages/asset-forge/src/components/ArmorPipeline/TierGeneratorTab.tsx (786 lines) — Batch tier generation UI
  • packages/asset-forge/src/components/ArmorPipeline/TripoGeneratorTab.tsx (1,727 lines) — Tripo pipeline wizard
  • packages/asset-forge/src/components/ArmorPipeline/ArmorPreviewTab.tsx (806 lines) — Rigging + animation preview
  • packages/asset-forge/src/components/ArmorPipeline/ShellPreviewViewer.tsx (917 lines) — Three.js viewer with animation support
Workflow:
  1. Extract — Generate shells from VRM avatar (all slots × all bulk classes)
  2. Texture — Apply materials (solid color, AI texture, or batch tiers)
  3. Tiers — Batch-generate bronze → dragon variants from one shell
  4. Rig & Preview — Re-rig textured pieces onto animated VRM skeleton
  5. Publish — Export to game’s model directory + update armor manifest
Shared Extraction Cache: Single extraction result shared across Shell, Texture, and Tier tabs to avoid re-extracting the same avatar multiple times. Animation Preview: Mixamo walk/run retargeted to VRM via normalized bone pipeline, driving both ghost avatar and all armor pieces simultaneously.

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 = 0 on all equipment materials (game has no environment map, so metallic PBR materials appear black)
  • RenderOrder Fix: Set renderOrder = 100 on equipment meshes to render on top of player silhouette (renderOrder 50)
  • DoubleSide Materials: Ensure all equipment materials use THREE.DoubleSide for correct rendering
WORKAROUND Comment:
Impact:
  • 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):
Updated Variables:
Key Files Changed:
  • packages/asset-forge/server/api-elysia.ts — Armor/Tripo route registration, temp-shells static serving, CORS defaults
  • packages/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
Impact:
  • 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
Security Hardening (7 rounds of fixes):
  • Path traversal prevention via path.basename() and SAFE_PATH_RE regex
  • 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)
Known Limitations:
  • 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:
  1. 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_SSS toggle)
    • 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
  2. 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=falsesetVisibleAt marks slots without removing them, so indirect drawIndex→instanceId mapping never shifts
    • Fixes tree-swap bug seen with perObjectFrustumCulled=true
  3. Model Cache Fix (Commit 1bf2342):
    • Fixed serialization to correctly slice typed-array views instead of copying entire underlying ArrayBuffer
    • Store colorItemSize for 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
  4. 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
Terrain Shader Updates (Commits 4eb855f, 3bb9875):
  1. Grass/Dirt Balance:
    • Lowered DIRT_THRESHOLD to show more dirt coverage on flat terrain
    • Updated TERRAIN_BIOME_TEXTURES.dirt fallback sRGB to match new dirt.png (0.55, 0.48, 0.36)
    • Updated GPU non-textured FOREST_DIRT/FOREST_DIRT_DARK linear constants to match new texture
    • Updated CPU replica _FOREST_DIRT to match new dirt.png average color
  2. Grass Color Fix:
    • Fixed hardcoded _FOREST_DIRT/_FOREST_DIRT_DARK linear values in GrassWorker string
    • 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
  3. Grass Texture Remap (Commit 3bb9875):
    • Remapped grass.png to olive-green hue (87.5°) matching reference screenshot
    • Reduced saturation and slightly darker value
    • Synced TerrainShader _FOREST_GRASS fallback and GrassWorker pre-linearized constants to new grass texture average sRGB (0.39, 0.52, 0.24)
  4. 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 clusterSpacing 200→100 for denser forest clusters
Water Shader Improvements (Commits 09f2399, baeb870, 3bb9875):
  1. Flow-Mapped Normals (Commit 09f2399):
    • Replaced fixed 4-layer scrolling normals with two-phase flow crossfade (FlowUVW technique from cloud-sea shader)
    • Loads waterNormal.png and noise28.png textures with procedural fallbacks
    • Organic, non-repeating water surface motion
  2. 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
  3. River Carving (Commit af4f07c):
    • Removed hardcoded river carving from canyon height function
    • Canyon water features now controlled purely by rivers/lakes/lakesFalloff config params like other biomes
Post-Processing (Commit e00b380):
  • Disabled color grading and depth blur effects (commented out in createPostProcessing config)
  • Keeps post-processing pipeline wired up for future use
UI Fixes (Commit ecbd30c):
  • Restored minimap accidentally hidden during frustum culling work
Memory Leak Fixes (Commit c72f0d1):
  • WorkerPool: Track active tasks per worker and reject them on terminate() so in-flight promises no longer dangle
  • GLBTreeBatchedInstancer: Guard against duplicate addInstance for same entityId; make addToPool atomic
  • GLBTreeInstancer: Add MAX_INSTANCES capacity check; guard duplicate entityId; clone attributes in createSharedGeometry
  • ProcgenTreeInstancer: Use tracked.preset in removeInstance; add capacity guard in showInMesh
  • GrassVisualManager: Add destroyed flag to guard async callbacks; cancel workerInflight/pendingLodSwap on prune/destroy/invalidate/rebuild
Key Files Changed:
  • packages/shared/src/systems/shared/world/GLBTreeBatchedInstancer.ts - Per-instance frustum culling, dissolve system, batch color channel layout
  • packages/shared/src/systems/shared/world/GLBTreeInstancer.ts - Dissolve support for InstancedMesh trees
  • packages/shared/src/systems/shared/world/DissolveAnimation.ts - Shared dissolve state machine
  • packages/shared/src/systems/shared/world/GPUMaterials.ts - Vertex-color tree shader with toon lighting, SSS, wind
  • packages/shared/src/systems/shared/world/TerrainBiomeTypes.ts - Updated tree distributions, removed Willow/Fir
  • packages/shared/src/systems/shared/world/TerrainShader.ts - Grass/dirt color updates
  • packages/shared/src/utils/workers/GrassWorker.ts - Fixed dirt color constants
  • packages/shared/src/systems/shared/world/WaterSystem.ts - Flow-mapped normals, teal color palette
  • packages/shared/src/utils/rendering/ModelCache.ts - Fixed typed-array serialization
  • packages/shared/src/constants/TreeTypes.ts - Removed Willow and Fir enum values
Configuration (GPU_VEG_CONFIG in GPUMaterials.ts):
Batch Color Channel Layout (BatchedMesh trees):
Tree Vertex Color Convention:
Impact:
  • 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):
Auth Bootstrap (packages/client/src/auth/PrivyAuthProvider.tsx):
Key Changes:
  • packages/client/src/lib/api-config.ts now 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.js endpoint
  • public/env.js is generated at server startup with current environment variables
Impact:
  • 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 from node: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
Configuration:

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.gg for 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
Impact:
  • 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 node24 runners 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)
Impact:
  • 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 -p for packages/web3/node_modules and packages/client/node_modules to 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
Implementation (Dockerfile.server):
Impact:
  • 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
Current State (Tailwind v3.4.19):
  • Uses standard PostCSS pipeline with tailwindcss plugin
  • 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
Configuration (packages/client/postcss.config.js):
Impact:
  • 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:
Usage Example:
Impact:
  • 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:
Configuration (GPU_VEG_CONFIG in GPUMaterials.ts):
Implementation Details:
  • Encoding: Blue channel of batch color encodes 1.0 - dissolveVal (BatchedMesh), or dedicated instanceDissolve attribute (InstancedMesh)
  • Dithering: Uses Bayer 4×4 screen-door dithering in alphaTestNode to 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: initialDissolve parameter on addInstance() prevents 1-frame flash
Impact:
  • 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:
Implementation:
Caching Strategy:
  • 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()
Impact:
  • 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 manifest depleteChance 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_CHANCE and MINING_REDWOOD_DEPLETE_CHANCE constants
  • Resources with depleteChance: 0 never deplete (rune essence rocks)
  • Deterministic tick-based respawn timing
Impact:
  • 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

See Port Allocation section for full port list.

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)
Common Test Failures:
  1. TileMovementManager test fails: Test expectations must match actual processPlayerTick behavior (path-follow + clear-on-arrival). If test expects path to persist after arrival, update to expect cleared path.
  2. GPUMaterials test fails: LODConfig values change frequently. Update test expectations to match current values in LODConfig.ts (e.g., tree fade=1800, not 180).
  3. CookingCalculator burn chance test fails: Account for MAX_BURN_CHANCE=0.55 cap when testing burn probabilities.
  4. DuelSystem ejection test fails: Players are ejected to starter area (0,0), not duel lobby. Update test expectations accordingly.
  5. AgentBehaviorEngine test contamination: Use unique characterId per test to avoid module-level Map contamination between tests. Add missing required fields like stationPositions.

CI/Lint Failures

Common Issues:
  1. Missing barrel exports: If you add a new class/type to shared package, add it to packages/shared/src/index.ts barrel export.
  2. Unused eslint-disable directives: Remove // eslint-disable-next-line comments that are no longer needed after fixing the underlying issue.
  3. Empty else blocks: ESLint rejects empty else {} blocks with --max-warnings 0. Either add logic or remove the else block.
  4. Duplicate methods: Check for duplicate method definitions in classes (e.g., multiple getWorld() methods).
  5. Type mismatches:
    • Use db.select() pattern instead of db.query() to avoid schema generic issues
    • Cast ArrayBufferLike to ArrayBuffer in strict mode when needed
    • Ensure union types include all possible values (e.g., “prayer” in combatRole union)

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: InterleavedBufferAttribute serialization was copying entire interleaved buffer instead of deinterleaving
Tree textures look wrong on first load:
  • Fixed in April 2026 (PR #1144)
  • If you see this on older commits, update to latest main
  • Root cause: ImageBitmapTexture uses copyExternalImageToTexture which applies browser-side sRGB decode, corrupting colors
  • Solution: All textures now converted to DataTexture for consistent writeTexture upload 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