JT
  • Home
  • Projects
  • Extras
  • Contact

© 2026 Joshua Tjhie. All rights reserved.

GitHubLinkedInEmail
Back to Projects
  1. Home
  2. Projects
  3. Copperfall (Placeholder)
2024-05-10

Copperfall (Placeholder)

A custom 2D JavaScript Canvas game engine and adventure prototype.

From-scratch 2D engine featuring a fixed-timestep ECS loop, parallax renderer, Web Audio reactive soundtrack, and a narrative-driven prototype built on top.

JavaScriptCanvas APIGame Dev
Copperfall (Placeholder)

Quick Stats

Role: Solo Developer
Duration: Ongoing
JavaScript (ES2023)Canvas 2DEntity Component SystemWeb Audio API

Links

Source RepoPlayable Build

Copperfall Engine

Copperfall is in active development. This write-up covers the engine architecture and the vertical-slice prototype — a second pass will land once the first full chapter is done.

Copperfall started as a weekend experiment: could I build a 2D game engine from scratch in vanilla JavaScript without reaching for a framework? Fourteen months later it has a renderer, a physics layer, a Web Audio reactive soundtrack, a dialogue runner, and the skeleton of a short adventure game.

Why build an engine?

Existing engines are powerful but opaque. Every bug feels like archaeology — stepping through someone else's assumptions to find your own mistake. Writing Copperfall gave me direct ownership of every abstraction, which makes debugging fast and architectural decisions intentional.

The constraint also teaches. When you can't import Physics from 'matter-js', you learn what physics actually costs.

Architecture

The ECS loop

ECS architecture diagram
Entity-component-system flow: entities hold component data, systems operate on component queries each tick.

The simulation runs on a fixed timestep to keep physics deterministic regardless of frame rate. A variable-rate render step interpolates between the last two simulation states to avoid judder.


Renderer

The Canvas 2D renderer handles:

  • Sprite batching with a dirty-rect system to skip unchanged regions.
  • Parallax layers with configurable depth ratios — background scrolls at 0.2× world speed.
  • Screen shake via a decaying offset accumulator applied before the camera transform.
  • A lightweight post-processing pass using ctx.filter for chromatic aberration on hit frames.

Component queries

Components are stored in typed arrays, not objects, so iteration stays cache-friendly. Systems declare their component signature as a bitmask and the world returns only matching entities.

  1. TransformComponent — position, rotation, scale.
  2. SpriteComponent — texture, frame index, animation state.
  3. ColliderComponent — AABB with optional offset.
  4. ScriptComponent — update function attached directly to the entity.

Web Audio layer

The soundtrack reacts to game state using the Web Audio API's GainNode graph. Each music layer — melody, bass, percussion — has its own gain controlled by the current zone and tension level.

Copperfall — main theme preview (adaptive layers)Listen
Your browser does not support the audio element.

The reactive system works by:

  • Zone entry — crossfade gains over 2 s to the target zone mix.
  • Combat — spike percussion gain to 1.0, mute ambient pads.
  • Dialogue — duck all gains by 60 % and bring up the lead melody.

Dialogue runner

The narrative layer reads from a simple YAML-based script format and drives on-screen text via a coroutine-style async iterator. Characters have portraits, voice variants, and timed pauses baked in.

- speaker: Asha
  line: "You came back."
  pause: 0.6
- speaker: Asha
  line: "I wasn't sure you would."
  portrait: asha-uncertain

The entry point is dialogue.play(scriptId) — returns a Promise that resolves when the final line is dismissed.

Dialogue runner in action — portrait transitions, typewriter effect, and branch selection.

Roadmap

  • Finish level-streaming approach for large maps without hitches.
  • Add save/load via IndexedDB for persistent run state.
  • Write the second chapter of the prototype game.
  • Publish the engine separately as an open-source library.

The constraint of "no frameworks" isn't masochism — it's the fastest path to understanding what frameworks actually solve.

Follow Development

Have thoughts?

Curious what others see or think

Feel free to reach out or leave feedback

Share Feedback

Prefer email? joshuatjhie@pm.me