← Return to the Battlefield

The Architect's Grimoire

A Complete Technical Guide to Cube Walker

πŸ“ File Architecture

Cube Walker is built with a modular JavaScript architecture. The codebase is split across multiple files for maintainability, with each file responsible for a specific domain.

JavaScript Files

app.js Main application - game loop, core systems, Three.js scene, multiplayer, all game logic ~10,400 lines
units.js Unit classes, mesh types, unit state factory, team color helpers ~174 lines
ui.js Health bars, tile markers, move counters, route preview, overlays, victory/defeat screens ~839 lines
pure.js Pure utility functions with no side effects (stringify, easing, tile keys, MP ID generation) ~76 lines
dom.js DOM manipulation for multiplayer debug overlay (state-free DOM updates) ~220 lines
contextmenu.js Context menu system for unit interactions (healer healing, wizard cube creation) ~155 lines
cosmetics.js Decorative elements - grass, flowers, bushes placed at tile corners ~250 lines
altcubes.js Alternative cube meshes - trees and rocks that replace building cubes visually ~140 lines

Data Files

levels.json Level configurations - cube size, unit spawns, building cubes, items, merchant positions ~317 lines
index.html Entry point - loads Three.js, all modules, contains game container HTML ~166 lines
styles.css All CSS - UI elements, overlays, dialogs, merchant UI, fullscreen handling ~2,061 lines

Module Load Order

Modules must be loaded in dependency order. The index.html loads them as follows:

<!-- External Dependencies --> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <!-- Pure Utilities (no dependencies) --> <script src="pure.js"></script> <!-- Unit Definitions (no dependencies) --> <script src="units.js"></script> <!-- UI Components (depends on Three.js) --> <script src="ui.js"></script> <script src="contextmenu.js"></script> <!-- DOM Utilities (depends on pure.js) --> <script src="dom.js"></script> <!-- Visual Enhancements (depends on Three.js) --> <script src="altcubes.js"></script> <script src="cosmetics.js"></script> <!-- Main Application (depends on all above) --> <script src="app.js"></script>
Module Dependency Graph
graph TD THREE[Three.js r128] --> app.js THREE --> ui.js THREE --> cosmetics.js THREE --> altcubes.js pure.js --> dom.js pure.js --> app.js units.js --> app.js ui.js --> app.js contextmenu.js --> app.js dom.js --> app.js cosmetics.js --> app.js altcubes.js --> app.js levels.json -.->|fetch| app.js style app.js fill:#3399ff,color:#fff,stroke:#1a1a1a style THREE fill:#1a1a1a,color:#fff style levels.json fill:#f5f0e6,stroke:#1a1a1a
✦ ✦ ✦

🧩 Module Breakdown

units.js β€” Unit Definitions

Exports window.UnitModule with unit class definitions and factory functions.

Export Purpose
UnitClasses Enum of class objects (WIZARD, ROGUE, TANK, HEALER) with ability flags: canWalkOnCubes, canPushCubes, canShootProjectiles, canMeleeAttack, canCreateCubes, canHealAllies
UnitMeshTypes Visual appearance types: HEDGEY_WIZARD, MOUSE_WIZARD, ROGUE_PIG, RIZARD_ROGUE, BOARBARIAN, APE_TANK, MEDICAT
createUnitState() Factory function to create unit entity objects with position, HP, navigation state, timers
getUnitTeamColor() Returns hex color based on unit's class and team affiliation
isOnBlueTeam() Team membership check helper

ui.js β€” UI Components

Exports window.UIModule with Three.js-based UI elements that float above units.

Category Functions
Health Bars createHealthBar(), createHealthBars(), updateHealthBars()
Tile Markers createTileMarker(), createTileMarkers(), updateTileMarkers()
Move Counters createMoveCounter(), createMoveCounters(), updateMoveCounters()
Route Preview createRouteMarker(), ensureRouteMarkers(), updateRoutePreview(), clearRoutePreview()
Selection createSelectionAura(), updateSelectionAura()
Destinations updateDestinationMarker(), createMultiDestinationMarker()
Move Range showMoveRangeOverlays(), updateMoveRangeOverlays(), clearMoveRangeOverlays()
Overlays showLevelTransition(), showYourTurnOverlay(), showVictoryScreen()

pure.js β€” Pure Utilities

Exports window.Pure with side-effect-free helper functions.

Function Purpose
safeStringify(arg) JSON stringify with circular reference protection for debug logging
easeInOutQuad(t) Smooth animation easing curve (0β†’1 input/output)
tileKey(face, u, v) Create consistent string key for tile lookup: "+Z|0|1"
parseTileKey(key) Parse tile key back to {face, u, v} object
generateMpGameId() Generate unique multiplayer game ID starting with "cw"

dom.js β€” DOM Manipulation

Exports window.DOM with multiplayer debug overlay functions.

Function Purpose
setMpState(state) Update internal MP state from app.js (connection status, game ID, etc.)
mpDebugLog(level, ...args) Log to debug overlay with timestamp and level (log/warn/error)
setupMpDebugOverlay() Create the debug overlay DOM elements
updateMpDebugOverlay() Refresh overlay with current connection status and logs

contextmenu.js β€” Context Menus

Exports window.ContextMenuModule for unit interaction menus.

Function Purpose
init() Create context menu DOM element
show(x, y, options, context) Display menu at screen position with options array
close() Hide the context menu
setActionHandler(fn) Register callback for menu item selection
isOpen() Check if menu is currently visible

cosmetics.js β€” Decorative Elements

Exports window.CosmeticElements for visual-only scene decoration.

Function Purpose
populate(cubeGroup, ...) Add grass, flowers, bushes to all cube faces
clear(cubeGroup) Remove all cosmetic meshes (for level cleanup)
createGrassClump() Create multi-blade grass cluster mesh
createFlower() Create tiny flower with stem and petals
createBush() Create clustered sphere bush mesh

altcubes.js β€” Alternative Cube Meshes

Exports window.AltCubes for visual variety in building cubes.

Function Purpose
shouldReplace() Random chance (50%) to replace cube with alt mesh
getRandomType() Returns 'tree' or 'rock' randomly
createTreeMesh() Create tree with trunk and foliage
createRockMesh() Create rock boulder mesh
createAltCube(face, u, v, ...) Create positioned alt cube for building system
✦ ✦ ✦

βš™οΈ app.js Core Systems

The main app.js file contains the game engine organized into major systems. Here's a map of the key sections:

System Map (by line ranges)

System Lines Key Functions
Sound Effects ~38-155 SoundManager object with playShoot(), playMelee(), playFlip(), playVictory(), etc.
Level Config ~157-400 Constants, FACES definition, EDGE_MAP, unit creation from config
Navigation State ~401-450 Global navigation object (System A), debug flags, camera watchdog
Turn-Based Mode ~466-680 Step tracking, action tracking, turn switching, multi-select state
Multiplayer (GunDB) ~475-650, 2569-3500 initGunDB(), exportMultiplayerSnapshot(), applyMultiplayerSnapshot()
Level Complete Dialog ~584-620, 1230-2000 startLevelCompleteSequence(), dialog balloon system, gather animations
Character Control ~667-850 switchCharacter(), updateSwitchButtonState(), selection aura
Three.js Setup ~921-990 Scene, camera, renderer, cubeGroup initialization
Init & Loading ~2185-2250 init() - loads levels.json, textures, creates scene, starts animate loop
Fullscreen/Landscape ~2259-2425 setupFullscreen(), setupLandscapeMode()
Projectile System ~3717-4185 createProjectile(), updateProjectiles(), damage dealing
Movement Range BFS ~4187-4400 computeReachableTiles(), BFS pathfinding within movement limit
Item System ~4509-4800 createRandomItems(), updateItems(), coin/book/potion collection
Merchant System ~4934-5630 createMerchant(), updateMerchant(), dialog UI, NAI merchant
Unit Updates ~5630-6200 updateUnit() - AI navigation, edge handling (System B)
Controlled Unit ~6195-6500 updatePlayerControlledUnit() - hop animation, System A navigation
Melee/Push Actions ~6900-7000 performMeleeAttack(), pushCube(), slash effects
Mesh Updates ~6998-7070 updateUnitMesh() - position, rotation, bobbing, hop arc
Keyboard Movement ~7070-7200 handleKeyboardMovement() - WASD/arrow key input
Tile Click Handling ~7200-8000 handleTileClick() - raycasting, action dispatch, pathfinding
Multi-Select System ~7985-8430 startMultiUnitNavigation(), pumpMultiOrders(), order dispatcher
Camera System ~8435-8600 updateCamera() - smooth follow, cube-face awareness
Game Loop ~8683-8820 animate() - main loop, update ordering, render call
Input Setup ~8820-9150 Keyboard, touch, cube rotation, pinch zoom handlers
Unit Footer ~9150-9300 createUnitFooter(), updateUnitFooterHP()
CPU Turn AI ~9400-9900 executeSingleCPUCharacterTurn(), AI action selection
✦ ✦ ✦

🎲 The Cube World

Cube Walker takes place on a magical cube floating in space. Units walk on all six faces of the cube, with gravity always pulling them toward the cube's center. The cube's size varies by level, from 2Γ—2 tiles per face (Level 0) up to 7Γ—7 tiles (Level 5+).

Face Definitions

The cube has six faces, each defined by a normal vector and local UV coordinate axes:

The Six Faces of the Cube
graph TB subgraph Cube["🎲 The Cube"] PZ["+Z (Front)"] NZ["-Z (Back)"] PX["+X (Right)"] NX["-X (Left)"] PY["+Y (Top)"] NY["-Y (Bottom)"] end PZ --> |"+u edge"| PX PZ --> |"-u edge"| NX PZ --> |"+v edge"| PY PZ --> |"-v edge"| NY style PZ fill:#4a7a4d,color:#fff style NZ fill:#8b4513,color:#fff style PX fill:#3399ff,color:#fff style NX fill:#cc6633,color:#fff style PY fill:#6666cc,color:#fff style NY fill:#996633,color:#fff
πŸ“ app.js Lines 227-235 β€” FACES object definition

Tile Coordinate System

Each face has a grid of tiles. For a cube of size N:

Edge Transitions

When a unit walks off an edge, the EDGE_MAP defines which face they transition to and how their UV coordinates are remapped. This is one of the trickiest parts of the codebase!

⚠️ Edge Crossing Complexity

Edge transitions involve coordinate remapping that can be confusing. The simulateEdgeCrossing() function handles this, but bugs in the +Y/-Y face transitions have caused units to teleport to wrong positions.

πŸ“ app.js Lines 237-303 β€” EDGE_MAP and updateEdgeMap()
✦ ✦ ✦

🧭 The Two Navigation Systems

Cube Walker has TWO separate navigation systems. Understanding when each is used is critical for avoiding bugs.

🚨 Critical Architecture Note

Mixing System A and System B fields causes units to jitter, freeze, or teleport. Always use the correct system for each context!

System A: Global Navigation (Player-Controlled Unit)

Used for: The single currently-controlled unit when moving via click or keyboard.

State object: Global navigation object

Stepped by: updatePlayerControlledUnit()

const navigation = { isNavigating: false, path: [], currentPathIndex: 0, speed: 2.5, isHopping: false, hopProgress: 0, hopStartU, hopStartV, hopStartFace, hopEndU, hopEndV, hopEndFace, isCrossingEdge: false };

System B: Per-Entity Navigation (AI & Multi-Select)

Used for: AI units, and when multi-select commands multiple units.

State location: Per-entity fields on the entity object

Stepped by: updateUnit() per-entity path follower

// Per-entity navigation fields entity.isNavigating entity.navigationPath entity.navigationPathIndex entity.isHopping entity.hopStart / hopEnd / hopT
Navigation System Decision Flow
flowchart TD A[Unit Needs to Move] --> B{Is this the controlled unit?} B -->|Yes| C{Is multi-select active?} B -->|No| D[Use System B
Per-Entity Navigation] C -->|No, single unit| E[Use System A
Global navigation object] C -->|Yes, multiple units| D E --> F[updatePlayerControlledUnit] D --> G[updateUnit per-entity path] style E fill:#3399ff,color:#fff style D fill:#cc3333,color:#fff
πŸ“ app.js Lines 1-35 β€” System Map documentation header
πŸ“ app.js Lines 401-420 β€” Global navigation object
πŸ“ app.js Lines 5761-6195 β€” updateUnit() function (System B)
πŸ“ app.js Lines 6195-6500 β€” updatePlayerControlledUnit() (System A)
✦ ✦ ✦

πŸ”„ The Game Loop

The animate() function runs every frame and orchestrates all game updates.

Frame Update Pipeline
flowchart TB A["0) Check game mode selected"] --> B B["1) Handle keyboard input"] --> C C["1.5) Pump multi-orders dispatcher"] --> D D["2) Update all units
updateUnit() per entity"] --> E E["2.5) Check multiplayer replay"] --> F F["3) Update projectiles, items, merchants"] --> G G["4) Update UI: markers, health bars, overlays"] --> H H["5) Update camera position"] --> I I["6) Render scene"] style D fill:#ff9966,stroke:#333

Key Update Functions Called Each Frame

πŸ“ app.js Lines 8683-8820 β€” animate() main loop
✦ ✦ ✦

βš”οΈ Characters & Classes

Every unit in Cube Walker belongs to one of four classes that define their abilities. The mesh type determines their visual appearance, but gameplay is driven by class.

The Four Classes

Ability πŸ§™ Wizard πŸ—‘οΈ Rogue πŸ›‘οΈ Tank πŸ’š Healer
Walk on Cubes βœ— βœ“ βœ— βœ—
Push Cubes βœ— βœ— βœ“ βœ—
Shoot Projectiles βœ“ (beam) βœ“ (arrow) βœ— βœ—
Melee Attack βœ— βœ— βœ“ βœ—
Create Cubes βœ“ (double-click) βœ— βœ— βœ—
Heal Allies βœ— βœ— βœ— βœ“
Movement Range 7 tiles 9 tiles 12 tiles 12 tiles
πŸ“ units.js Lines 8-56 β€” UnitClasses definition

Meet the Characters

Hedgey Wizard
Hedgey Wizard
Wizard Class β€’ Blue Team Default
HP:60
Attack:30
Key:hedgeyWizard
Shoot Create Cubes
Mouse Wizard
Mouse Wizard
Wizard Class
HP:70
Attack:25
Key:mouseWizard
Shoot Create Cubes
Rogue Pig
Rogue Pig
Rogue Class
HP:90
Attack:20
Key:roguePig
Shoot (Arrow) Walk on Cubes
Rizard Rogue
Rizard Rogue
Rogue Class
HP:90
Attack:20
Key:rizardRogue
Shoot (Arrow) Walk on Cubes
Boarbarian
Boarbarian
Tank Class
HP:120
Attack:35
Key:boarbarian
Melee Push Cubes
Ape Tank
Ape Tank
Tank Class
HP:120
Attack:35
Key:apeTank
Melee Push Cubes
Medicat
Medicat
Healer Class
HP:80
Attack:15
Key:medicat
Heal Allies
πŸ“ units.js Lines 59-68 β€” UnitMeshTypes enumeration
✦ ✦ ✦

πŸ—ΊοΈ Pathfinding Architecture

The game uses BFS pathfinding with special handling for cube obstacles and cross-face navigation.

Key Functions

computeReachableTiles()

BFS flood-fill from unit position up to movement limit. Returns Map of reachable tile keys to their costs.

findPathAvoidingCubes()

Cross-face navigation wrapper. Finds optimal edge to cross and chains together per-face paths.

isBlockedForEntity()

Checks if a tile is blocked considering the entity's class abilities:

function isBlockedForEntity(entity, face, u, v, entityKey) { if (isTileOccupiedBySprite(face, u, v, entityKey)) return true; if (hasBuildingCube(face, u, v) && !entity.unitClass.canWalkOnCubes) return true; return false; }
Pathfinding Call Hierarchy
flowchart TD A[Player clicks tile] --> B{Same face?} B -->|Yes| C[BFS within face] B -->|No| D[findPathAvoidingCubes] D --> E[Find optimal edge crossing] E --> F[BFS to edge] E --> G[BFS from landing] F --> H[Combine paths] G --> H C --> I[Navigate path] H --> I
πŸ’‘ allowCubes Parameter

All pathfinding functions accept an allowCubes boolean. This MUST be set to entity.unitClass.canWalkOnCubes for proper Rogue behavior. Forgetting this causes Rogues to path around cubes they should climb!

πŸ“ app.js Lines 4187-4370 β€” computeReachableTiles() and BFS
✦ ✦ ✦

🌐 Multiplayer Architecture

Multiplayer uses GunDB for real-time state synchronization between two players.

Connection Flow

Multiplayer Join Flow
sequenceDiagram participant Host participant GunDB participant Joiner Host->>GunDB: Create game (cwXXXX) Host->>Host: Display join URL Joiner->>GunDB: Subscribe to cwXXXX GunDB->>Joiner: Initial snapshot Joiner->>Joiner: Apply snapshot loop Each Turn Host->>GunDB: Export snapshot + end turn GunDB->>Joiner: Snapshot update Joiner->>Joiner: Apply, execute turn Joiner->>GunDB: Export snapshot + end turn GunDB->>Host: Snapshot update end

Snapshot Structure

The multiplayer snapshot contains complete game state:

{ seq: 123, // Sequence number level: 1, // Current level isBluesTurn: true, // Whose turn units: { ... }, // All unit states buildingCubes: [ ... ], // Cube positions items: [ ... ], // Item states stepsTakenThisTurn: { ... }, actionTakenThisTurn: { ... } }
πŸ“ app.js Lines 2799-2850 β€” initGunDB()
πŸ“ app.js Lines 3090-3145 β€” exportMultiplayerSnapshot()
πŸ“ app.js Lines 3147-3385 β€” applyMultiplayerSnapshot()
✦ ✦ ✦

✨ Adding New Content

Adding a New Character

Add the Mesh Type (units.js)
const UnitMeshTypes = { // ... existing types ... MY_NEW_UNIT: 'my_new_unit', };
Add Key Mapping (units.js)

Update generateUnitKey():

const keyMap = { [UnitMeshTypes.MY_NEW_UNIT]: 'myNewUnit', };
Add Texture URL (app.js)

In createAllUnits() textureUrls:

[UnitMeshTypes.MY_NEW_UNIT]: 'https://your-cdn.com/sprite.webp',
Add to Level Config (levels.json)
{ "meshType": "MY_NEW_UNIT", "unitClass": "WIZARD", "face": "+Z", "u": 0, "v": 1, "maxHP": 80, "attackPower": 25 }

Adding a New Level

Add a new object to levels.json with:

βœ… Level Added!

New levels are automatically loaded. Update maxLevel in app.js if needed.

✦ ✦ ✦

πŸ› Known Issues & Edge Cases

Rogue Pathing on Cubes

Sometimes Rogues don't path correctly onto cube tops. Ensure allowCubes = entity.unitClass.canWalkOnCubes is passed to ALL pathfinding calls.

+Y/-Y Face Transitions

The top and bottom face edge mappings in EDGE_MAP are particularly tricky. Test thoroughly when modifying.

Multi-Select Order Persistence

Orders in multiOrders Map persist until arrival. If a unit gets stuck, the order fuse (MAX_ORDER_FAILS) will eventually clear it.

Multiplayer Snapshot Timing

Snapshots are exported after committing all movement. If a unit is mid-hop during export, commitAllMovement() snaps them to destination.

πŸ“ diagnosis.html β€” Detailed bug analysis for battle freeze issues
✦ ✦ ✦

πŸ”§ Refactoring Recommendations

The codebase has grown organically and could benefit from these improvements:

Priority 1: Unify Navigation Systems

Current Problem

Two navigation systems (global navigation vs per-entity fields) are confusing and error-prone.

Solution: Move ALL navigation state to per-entity fields. The "controlled unit" can simply be identified by a flag, not by using a different data structure.

Priority 2: Further Module Extraction

Split the remaining 10,000+ line app.js into:

Priority 3: State Machine for Unit Behavior

Replace timer-based AI with explicit state machines:

const UnitStates = { IDLE: 'idle', MOVING: 'moving', ATTACKING: 'attacking', HOPPING: 'hopping', WAITING: 'waiting' };

Priority 4: Type Annotations

Add JSDoc comments for better IDE support:

/** * @typedef {Object} UnitEntity * @property {string} face - Current face (+X, -X, etc) * @property {number} u - U coordinate * @property {number} v - V coordinate * @property {UnitClass} unitClass - The unit's class * @property {boolean} isBlueTeam - Team affiliation */