This project is a browser 3D voxel game engine and a ROM-based content pipeline.
A ROM is a .zip package containing all gameplay/config/map/assets data.
The repository ships three main entry points:
index.html: menu/launcher (pick ROM, then open play or editor)play.html: game runtimeeditor.html: full ROM/map/content editor
The project is built around a ROM-first workflow:
- The runtime can load game data from
?rom=some.zipor?romref=<indexeddb-token>. - The editor can import a ROM zip, edit everything, and export a new ROM zip.
- Config/data are JavaScript/JSON modules inside the ROM (
config/*.js,maps/*.json,images/*,audio/*).
There are fallback files in src/ for local development, but the intended flow is ROM-driven.
Use a static HTTP server (not file://), because ES modules and MIME types are required.
Examples:
# Python
python3 -m http.server 3000
# Node (if installed)
npx serve -p 3000Then open:
http://127.0.0.1:3000/index.html
If no ROM is specified, runtime/editor try data.zip by default.
?rom=data.zip?rom=/path/to/rom.zip?romref=<token>(ROM blob stored in IndexedDB by launcher)?map=map-name.json(runtime map selection from ROM)?rom=0forces built-in/fallback mode
index.html: ROM picker and launch linksplay.html: game page shell (crosshair/interactions + loadssrc/main.js)editor.html: complete editor app (map/content/ROM)
Engine modules:
src/main.js: boot, ROM loading, input, UI overlays, mode switching, loopsrc/world.js: world state, block/entity lifecycle, terrain meshing orchestrationsrc/entity.js: entity behavior, pathfinding, AI/faction combat, movement logicsrc/bullet.js: projectile spawning, hit detection, effectssrc/item.js: dropped items, pickup, item usesrc/collision.js: AABB collision and ground probessrc/surfaceNets.js: terrain mesh generation algorithmsrc/terrainWorker.js: worker offload for terrain meshingsrc/audio.js: FMOD integrationsrc/inspector.js: popup object inspectorsrc/menus.js: default exclusive menu definitions
ROM config loader modules:
src/rom-config/rom_loader.jssrc/rom-config/config.jssrc/rom-config/blocks.jssrc/rom-config/items.jssrc/rom-config/npcs.jssrc/rom-config/textures.jssrc/rom-config/factions.jssrc/rom-config/menus.js
High-level (src/main.js):
- Imports config/content modules from
src/rom-config/*. - Creates Three.js scene/camera/renderer.
- Detects mode (
editororgame/integrated). - Loads ROM at boot (
data.zipor URL param). - Loads textures from ROM into
world._internal.blockTextures. - Builds world/map/entities.
- Starts animation loop.
During loading:
- A black loading overlay is shown.
- Renderer stays hidden until initialization completes.
Title behavior:
- In play mode, document title uses ROM/config game name.
loadRomFromZipFile(file) in src/main.js does:
- Parse
config/blocks.js,items.js,npcs.js,textures.js,factions.js. - Replace in-memory registries (
BLOCK_TYPES,ITEMS,NPC_TYPES, etc.). - Resolve texture files in zip, create object URLs, map texture key -> blob URL.
- Load maps from
maps/*.json(multi-map support). - Set default map payload (
default.jsonpreferred). - Optionally bind FMOD banks from ROM audio files.
Path resolution rules are flexible:
- Supports
config/...anddata/config/...style paths. - Supports
maps/...anddata/maps/...style paths.
Main arrays:
world.blocksworld.entitiesworld.projectilesworld.itemsworld.messages
Core internals:
- Spatial hash by exact block coordinate
- Chunk map (
CHUNK_SIZE = 8) - Dirty chunk set for incremental terrain rebuild
- Terrain root group + per-chunk mesh groups
Export/import shape (exportMap / applyMap):
versionplayer: position, yaw/pitch, name, portrait texture key, inventoriesblocks:{x,y,z,typeId,isFloor}items: drop entries (kind, ids, amount, position)entities: NPC entries (type, faction, hp, inventories, position)
config/config.js: game constants (movement, gravity, AI ranges, etc.)config/blocks.js: block definitionsconfig/items.js: items/weapons/consumablesconfig/npcs.js: creature templatesconfig/factions.js: factions and relationsconfig/textures.js: texture catalog (key->url)config/menus.js: exclusive menu definitionsconfig/inventory-presets.json: preset inventories (editor)config/ui.json: UI aliases + player portrait/name (editor)
This project does not render every solid block as visible cube geometry in normal mode. It builds smooth-ish terrain meshes from block occupancy using a Surface Nets-like method.
- Orchestration:
src/world.js(rebuildTerrainMesh, chunk management) - Algorithm:
src/surfaceNets.js(buildSurfaceNetGeometryData) - Optional worker offload:
src/terrainWorker.js
For each dirty chunk:
- Collect chunk + neighboring blocks (context margin).
- Group blocks by
blockType.id. - For each type group, generate mesh data (
positions,normals,uvs). - Build Three.js
BufferGeometryand assign material by block type texture. - Replace only rebuilt chunk groups.
This keeps updates incremental and fast for edits.
In buildSurfaceNetGeometryData:
- Blocks are converted to voxel coordinates using
subdivisions. - Field is a binary scalar grid (
Float32Array) with occupancy 0/1. - Optional shape modifiers:
dilationfillInset- block damage influence (
dmgshrinks fill)
For each grid cell:
- Read 8 corner scalar values.
- Skip fully empty/full cells.
- Find intersected edges among 12 cube edges.
- Interpolate edge crossing point at
isoLevel. - Average all crossing points to place one cell vertex.
That is the key Surface Nets idea: one representative vertex per active cell.
The algorithm then scans axis-aligned transitions (x/y/z directions):
- Detect sign changes in scalar field between neighboring samples.
- Build quads using adjacent cell vertices.
- Split each quad into 2 triangles (
emitTriangle). - Flip winding based on inside/outside sign to keep normals consistent.
- Triangle normal: cross product of edges.
- Same normal duplicated per triangle vertex.
- UVs use triplanar-like projection by dominant normal axis:
- top-like faces use XZ
- side-like faces use ZY or XY
- Separate scaling for top vs side UV density.
In world.rebuildTerrainMesh() options vary by mode:
subdivisions: higher in editorpadding: neighborhood marginisoLeveluvScaleTop,uvScaleSide(U/V)dilation
- Worker failure falls back to main-thread generation.
- Geometry inputs are validated before mesh creation.
- Bounding volumes are computed for chunk meshes.
src/collision.js uses AABB checks:
- Entity box vs nearby solid block boxes.
- Radius-based horizontal collision + dynamic height (crouch/stand).
updatePlayerControlled in src/entity.js performs extra head-level point probes using checkPointCollision around eye position before allowing movement.
This prevents camera penetration into walls (including corner cases).
- Gravity from config.
- Ground snap on collision.
- Fall damage based on fall distance threshold/multiplier.
findPath:
- Grid-based A* over integer coordinates.
- Heuristic: Manhattan distance (
|dx|+|dy|+|dz|). - Neighbor generation includes:
- walk
- crouch path
- jumps
- short gap jumps
- controlled drops
Costs are weighted (normal/crouch/jump/drop penalties).
- FOV cone check (
VISION_FOV_DEG) - Distance range check
- Raycast line-of-sight against solid block meshes
- Proximity detection for non-crouching targets
- Per-entity faction id
- Hostility from
getFactionRelation(a,b) - AI targets visible hostile entities only
- NPCs can select best available weapon from inventories.
- Projectile systems support item and block projectiles.
- Cooldowns and range logic.
- Three.js scene + fog + directional/ambient light.
- Terrain mesh for solids.
- Cross-plane rendering for vegetation-like blocks (
render: 'cross').
HUD is HTML overlay (ensureHud / updateHud) with:
- player portrait
- name
- hp/item info
- selected item preview
#hand-item shows held item texture.
Fallback order includes:
- explicit hand texture
images/empty-hand.png(ROM)- generated emoji texture fallback
When missing, UI icons can be generated via canvas/emoji for keys like:
ui_player_face- sensed/faction status icons
- damage state icons
- empty hand
Implemented in src/main.js + default definitions in src/menus.js.
When menu is open:
- Game renderer is hidden.
- Gameplay HUD is hidden.
- Simulation is paused.
- Menu receives mouse input.
- ESC opens configured pause menu (
defaultEscapeMenu). - ESC closes/goes back menu stack.
- Pointer-lock ESC fallback also opens pause menu.
- Optional
startMenuauto-opens at boot.
buttoninputtext
Each element supports:
x,y,width,heightrotationflipX,flipYflatColor,textColorspriteKeyfontSize
noneclose_menuback_menuopen_menu+targetMenuscript
action: 'script' executes custom JS with:
api.closeMenu()api.backMenu()api.openMenu(id)api.getVar(key)api.setVar(key,value)api.exportMap()api.importMap()api.menuId
Text supports templating: {{varName}}, plus built-ins like {{gameName}}.
Movement/view:
W A S D: move- Mouse: look
Space: jumpC: crouch (game mode)- Mouse wheel: cycle selected slot
Actions:
- Left click:
- game: shoot
- editor: remove target
- Right click:
- game: use/interact/place
- editor: place/spawn
Q: drop selected block
Mode/debug:
F8: toggle game/editor modeF9: toggle terrain mesh render path
Map import/export shortcuts:
O: export map JSONI: import map JSON
Quick block select:
1..6: direct block hotselect (STONE,GRASS,WOOD,GOLD,DOOR,SAND)
Menu:
Esc: open/close/back exclusive menu
setupMobileControls creates touch overlays:
- Left pad: WASD
- Right pad: Shoot / Action / Drop / Jump / Down
- Top bar: Prev / Next / Mode / Menu / Export / Import
- Full-screen look pad for touch camera control
editor.html is a full ROM editor with top-level section dropdown.
- Import ROM zip
- Export ROM zip
- Status logs
- Load
default.json - Import/export map JSON
- Clear map
- Multi-map manager:
- select map
- new/rename/delete map
- Set player spawn interactively (
Set player pos) - Edit player HUD portrait texture key + upload
- Edit player display name
- Tool mode:
- block paint
- creature place
- item/weapon drop place
- erase
- Y layer editing
- Brush or rectangle paint
- Zoom and catalog search
- Layer utilities:
- copy current Y
- paste to current Y
- clear current Y
- Search-filtered palette with texture thumbnails
- Contextual list based on active tool mode
Visual entity inventory editor:
- choose target entity/player
- choose category (blocks/items)
- add/remove/clear quantities
- apply inventory preset
- full block CRUD
- fields: id, name, HP, break dmg, opacity, solid/floor/droppable
- texture key + image upload + preview
onUsescript editor:- none
- presets (
door_toggle,gold_flash) - custom JS function
- full item/weapon CRUD
- consumable and heal fields
- damage/projectile speed/projectile damage
- projectile visual controls:
- projectile texture key + upload + preview
- projectile size
- projectile gravity scale
- projectile drag
- texture roles synchronized (
texture/ui/drop/hand)
- creature CRUD
- dimensions, hp, hostility, interactability
- faction assignment
- dialogue
- texture key/upload/preview
- faction CRUD
- full relation matrix editor (
friendly/neutral/hostile)
- texture catalog CRUD
- key/url edit
- image upload + preview
- key rename refactor updates references across blocks/items/npcs
- UI texture alias editor (
alias -> source texture key) - upload for alias
- preview
Visual exclusive-menu editor:
- menu CRUD
- set default ESC menu
- set start menu
- per-menu background color
- element CRUD (
button/input/text) - drag to move on stage
- resize using handle
- rotate with mouse wheel on stage
- flip X/Y
- text/placeholder/bind editing
- sprite key assignment
- action presets + custom script
- direct JSON editor for
config.jsobject - save/reset in memory
Visual inventory preset manager:
- preset CRUD
- add/remove quantities by catalog (not raw JSON only)
- real-time preset content list
Editor export writes:
data/config/blocks.jsdata/config/items.jsdata/config/npcs.jsdata/config/config.jsdata/config/textures.jsdata/config/factions.jsdata/config/menus.jsdata/config/inventory-presets.jsondata/config/ui.jsondata/maps/*.json(multi-map)- embedded image binaries for uploaded textures
editor-manifest.json
Canonical expected structure:
<rom>.zip
config/
config.js
blocks.js
items.js
npcs.js
textures.js
factions.js
menus.js
inventory-presets.json
ui.json
maps/
default.json
*.json
images/
... png/jpg/webp
audio/
Master.bank
Master.strings.bank
Compatibility:
- Loader also accepts
data/config,data/maps,data/images,data/audioprefixes.
- FMOD is initialized lazily on first user gesture.
- Runtime tries to load:
audio/Master.bankaudio/Master.strings.bank
- If FMOD is unavailable or bank load fails, game continues without audio.
In editor mode (runtime), middle-click opens context menu with operations such as:
- control selected entity
- inspect entity/block/world in popup inspector
- edit entity inventory
- clone entity as spawn preset
- command NPC movement/look
src/inspector.js provides a tree editor popup with optional hot-reload apply behavior.
Main performance systems:
- Chunked terrain rebuild (dirty chunks only)
- Worker-based meshing when available
- Spatial hash lookup for blocks
- Conditional simulation pause during loading/exclusive menus
Editor mode rebuild policy is more immediate; game mode can delay rebuild (terrainBuildDelay) for smoother runtime.
- Use HTTP server with correct module MIME types.
- If textures fail, check that
textures.jskeys point to files present in ROM. romrefdepends on IndexedDB storage from launcher.- Exclusive menu definitions in ROM override default
src/menus.js.
From config/config.js (ROM), common ones include:
- movement:
MOVE_SPEED,CROUCH_SPEED_MULTIPLIER,LOOK_SPEED - physics:
GRAVITY,JUMP_FORCE,ENTITY_HEIGHT,ENTITY_RADIUS - world:
WORLD_MAX_RADIUS,WORLD_MIN_Y - interaction:
INTERACTION_RANGE,PLACEMENT_RANGE,ITEM_PICKUP_RANGE - AI/path:
PATH_UPDATE_INTERVAL,MAX_PATH_ITERATIONS - combat:
HOSTILE_*,VISION_*,MELEE_* - damage:
FALL_DAMAGE_*
- Add entry in blocks editor (or
config/blocks.js). - Assign texture key.
- Add texture entry in textures catalog and image file.
- Optionally add
onUsefunction script.
- Create item with weapon enabled.
- Set projectile fields (
projectileDamage,projectileSpeed, etc.). - Set projectile texture key and upload image.
- Open Menus editor tab.
- Create menu and elements.
- Add button actions (
open_menu,script, etc.). - Set
startMenuordefaultEscapeMenu. - Export ROM.
Several systems intentionally evaluate user-authored JS from ROM/editor data:
- block
onUsecustom functions - menu button
scriptaction - module parsing via transformed
export defaultcode
Treat ROM files as trusted content. Do not load untrusted ROMs in sensitive environments.
After editing/exporting a ROM, verify:
- Play boot with
?rom=...or launcher-selected ROM. - Terrain renders correctly and textures appear.
- NPC factions/relations behave as expected.
- Weapons fire with configured projectile visuals.
- Exclusive start and pause menus work (
Esc, start flow). - Map import/export and multi-map selection are correct.
- Editor re-import of exported ROM preserves data.