Under the hood

How Hollow Wake works

Skills, monsters, items, and passive bonuses are defined as data that a shared game engine reads. This makes it easier to combine familiar mechanics in new ways and keep their rules consistent.

Curious about how damage bonuses stack, how minions use skills, or where to find a mechanic in the source? This page covers the basics. For a look at what you can play, start with the game overview.

The modifier engine

How your stat bonuses stack

The modifier system combines bonuses in a set order. Flat values add to the base. Increased percentages add together. Each more modifier multiplies the result. An override, when present, replaces that result. These rules apply to bonuses from equipment, passives, and enemy traits.

1

flat

Adds to the base value. +35 Maximum Life

2

increased

Adds together with other increased bonuses. +47% increased melee damage

3

more

Multiplies separately from other more bonuses. 25% more damage

4

override

Sets a fixed result instead of the calculated value.

value = override ?? ( (base + Σflat) × (1 + Σincreased) × Π(1 + more) ) // then clamped to the stat's min/max, filtered by tags

Tags determine which bonuses apply. For example, 10% increased damage [melee] improves a melee attack but does not improve a spell unless it also qualifies as melee.

Actors

Players and enemies share combat rules

Players, enemies, and minions use the same skill system: World.useSkill(). Their stats and skill choices differ, but attacks use common rules for targeting, damage, and effects. This also lets summoned creatures use skills from the monster roster.

Build with the gems you find

Find active skills and support gems as loot, spend essence to improve them, and fuse duplicates into higher-rarity skill gems at the Font. Branching skill trees and compatible supports let you specialize a skill, while character levels grant points for the shared passive tree.

Reuse mechanics to add content

Most content starts with an entry under src/data/. A new skill can combine existing attack types and effects. A new monster can reuse those skills with a different set of behaviors. New mechanics still need engine support.

Repository map

Where it all lives

One engine, content as data, a Canvas 2D renderer, and an Electron shell around it.

src/engine/
Combat and world simulation: the game loop & useSkill, the layered modifier engine, damage, status, the one actor model, composable AI, occlusion & pathing, procedural generation.
  • world.ts
  • stats.ts
  • damage.ts
  • actor.ts
  • ai · brain
  • levelgen
src/data/
The content, as data: skills, supports, monsters, passives, classes, zones, tilesets, procs, plus the whole item layer (bases, affixes, uniques, vestiges).
  • skills.ts
  • monsters.ts
  • passives.ts
  • uniques.ts
  • tilesets.ts
src/render/
Draws the game with Canvas 2D: creatures, scenery, materials, lighting, and weather. Cached artwork helps keep busy scenes responsive.
  • vis/materials
  • doodad painters
  • light layer
src/packages/
Defines world events such as Warbands, Breach, Contagion, and Demon Invasion, including how they appear and affect a run.
  • Event definitions and progression
src/sim/
Runs repeatable combat scenarios without drawing the game. Used to compare builds, measure damage and survival, and check balance changes.
  • arena · builds
  • metric taps
src/ui · net · meta · launcher
DOM panels, the co-op transport, the account / save / permadeath meta-layer, and the Electron desktop shell (update flow, loopback save server, launcher window).
  • ui panels
  • meta / saves
  • launcher

Entry point: index.html → src/main.ts. The working conventions and a deeper subsystem tour live in CLAUDE.md and the docs.

Correctness

Testing a new build

Type checks and regression probes catch many errors before a change lands. Other checks run combat scenarios, generate maps, and measure frame times in the desktop game. Playtesting is still essential for finding awkward controls, unreadable fights, and balance problems.

npm run sim

Compares skills and builds at matched investment, checks support interactions, and measures rewards and item drops.

npm run perf

Runs the desktop game and measures frame times across different environments, using town as a baseline.

npm run genqa

Generates maps across the biome and layout roster, checking that routes, doors, and required destinations remain valid.

npm run eventqa

Checks world event definitions, saved state, and ownership rules against the event tools used during development.

Source available

Read it, run it, extend it

Read the code, try a local change, or investigate the monster that ended your last run. The repository is available under the PolyForm Noncommercial license; see the repository license for its terms.