Skip to content

Latest commit

 

History

77 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Phase Shifter

A 3D voxel exploration game where you walk through three phases — Alpha, Beta, Gamma — and the world around you physically changes: blocks become solid in one phase, intangible in another. Built with Three.js + Vite.

Status

Phase What Status
0 Architectural decision (single engine) ✅ Done
1.1–1.7 Init, camera, world indexing, chunks, save/load ✅ Done
2.1–2.8 Phase shift, per-phase collision/place/break, Phase Lens, Resonance, Phase Anchor, audio cues ✅ Done
3.1–3.6 Biomes, Stabilizers, Echoes, Resonance Cores, Phase Glider, Tutorial Zone ✅ Done
4 Settings menu, data-driven minimap, full-state save, 30s autosave, code-splitting ✅ Done
5 3-Act Goals, HUD objective, compass, FOV breathing, reduced-motion accessibility ✅ Done
6 Focused test suite — unit + Playwright live debug API + smoke static-analysis ✅ Done
7 README rewrite, KNOWN_ISSUES, GitHub Actions CI ✅ Done
8 Tutorial skip, post-collapse invuln, audio restart, settings reset, compass distance, tutorial re-enter, footstep density ✅ Done
9 Firefox pointer-lock + audio fix, edge case hardening, browser-matrix docs ✅ Done
10 Gameplay mechanics pass (P0 + P1): energy rebalance, Phase Fuse, collapse=Echo loss, 36-Echo lore, Act 4 Convergence, per-biome signatures, phase erosion, energy danger states ✅ Done (P0+P1); P2 deferred
10 (cont.) Echo Hunter panel (§10.10) + New Game+ (§10.14) + Resonance charge-up (§10.13) + Phase shift preview (§10.12) + Wrong-phase Echoes (§10.11) ✅ Done (P2 fully)

Full status with per-phase test counts and commit hashes: Progress section in PROJECT_REMEDIATION_PLAN.md.

Quickstart

git clone https://github.com/klampatech/phaseshift.git
cd phaseshift
npm install
npm run dev          # http://localhost:5173

Open the page, click the blocker overlay (this is the user gesture that unlocks the AudioContext for the audio cues), and start playing.

Production build

npm run build        # produces dist/ (~570 KB / 149 KB gzipped, 36 KB main entry gz)
npm run preview      # serves dist/ on http://localhost:4173

Live deployment (GitHub Pages)

Every push to main automatically deploys the latest dist/ to GitHub Pages via the deploy job in .github/workflows/ci.yml. The deploy runs after the build-and-test job passes (tests run first; a red CI blocks the deploy).

Live URL: https://klampatech.github.io/phaseshift/

One-time setup: in the repo's Settings → Pages, set the source to "GitHub Actions" (the workflow uses the official actions/deploy-pages@v4 action, which requires this source).

The Vite base is set to './' in vite.config.js so the same build serves correctly at both / and /phaseshift/ paths.

Tested browsers

The 1.0 release + Phase 9 hardening pass was manually verified on the following browser / OS combos. The 8-scenario smoke test (loader → blocker → audio → WASD movement → place/break → T/Q/E → stabilizer → save/load → biome transition) passed on every entry.

OS Browser Version Date Reviewer
Linux Chrome 138+ 2026-08-10 Codex (Phase 9)
Linux Firefox 130+ 2026-08-10 Codex (Phase 9)
Linux Chromium (headless) via Playwright 2026-08-10 Code-level + smoke static-analysis

Coverage notes

  • Chromium path is exercised end-to-end by tests/gameplay.spec.js (the 23+ Playwright tests). Every debug hook (__phaseShifter__.*) is asserted on real Chromium via the vite --port 3002 web server.
  • Firefox path is gated on Firefox availability (npx playwright install --with-deps firefox). The pointer-lock + audio acceptance is exercised by tests/firefox-pointer-lock.spec.js (the deferred-resume + first-input fallback). The audio context state is asserted to be running after the first input event.
  • Safari path is documented but not exercised in CI (no Linux runner for Safari). Safari 16+ is the supported version per the KNOWN_ISSUES.md Platform section.

If you test on a new browser / OS combo, add a row to the table above and a corresponding entry in KNOWN_ISSUES.md if anything breaks.

Controls

Mouse: click the blocker to start, then move the mouse to look. Esc releases the pointer (and shows the pause menu).

Key Action
W / A / S / D Move
Space Jump
Shift + Space Phase Shift (costs 15 energy; auto-respawns at nearest Stabilizer)
Q Resonance — swap phase presence in a 3×3×3 around you
E (hold) Phase Lens — highlights blocks that differ from the current phase (drains 0.5 energy / sec)
F (hold 3s) Phase Fuse — permanently swap a block's phase presence (costs 30 energy; persists across save/load)
Shift + LMB Place Phase Anchor (yellow outline, 10s lifetime, auto-snaps player on shift)
T Cycle phase (Alpha → Beta → Gamma → Alpha)
R Place Stabilizer (free, in-phase)
1 / 2 / 3 Jump directly to Alpha / Beta / Gamma
RMB Cycle phase (legacy alias for T)
Ctrl Crouch
M Toggle minimap
I Toggle inventory
F5 Save game (manual; 30s autosave runs by default)
F9 Load game
Esc Pause menu

The #phase-name indicator (top-left), #biome-info label (top-left under the phase name), #block-hint (top-center), #objective (top, the current Act), and #compass-arrow (top, points at the nearest unfinished marker) are the live HUD readouts.

Gameplay

The three phases — Alpha, Beta, Gamma — each see a different subset of blocks as solid:

  • Alpha (cyan): default. Most terrain is solid.
  • Beta (magenta): Obsidian + Void are solid; Stone is air. Useful for crossing gaps blocked by Stone.
  • Gamma (gold): Stone + Obsidian are solid; Crystal Cavern crystals are air.

You start in Alpha with full energy (100). Each phase shift costs 15 energy. Energy flows in real time: 2.0/sec regen in Alpha, 0.5/sec drain in Beta, 1.0/sec drain in Gamma — a 15-energy shift recovers in 7.5s of Alpha rest. If you run out of energy in Beta or Gamma, you collapse — the world sucks you down, then teleports you to the nearest Stabilizer (if any) with 30 energy restored. Place Stabilizers freely (free, no energy cost) to build safe respawn points. Without one, you respawn at the original spawn with a "No Stabilizer nearby" warning.

Three Acts guide your exploration:

  1. Act 1 — Find your first Echo (collectible lore crystals in the Ruins).
  2. Act 2 — Reach the Phase Nexus (the central hub biome).
  3. Act 3 — Master all phases (collect all 3 Resonance Core amplifiers + place a Stabilizer).

The compass arrow points at the nearest unfinished marker (Echo → Stabilizer → Resonance Core). After all 3 Acts, you're free to explore.

Architecture

index.html → main.js
              ├─ src/core/{world, phase, physics, constants}.js
              ├─ src/render/{renderer, checkpoint, scan, anchor, echo, resonanceCore, resonancePulse, tutorialOverlay}.js
              ├─ src/input/{controls, placeBlock}.js
              ├─ src/scan/lens.js            (Phase 2.5 — Phase Lens)
              ├─ src/resonance/{resonate, core}.js (Phase 2.6 + 3.4)
              ├─ src/anchor/anchor.js        (Phase 2.7)
              ├─ src/audio/{manager, footsteps}.js (Phase 2.8)
              ├─ src/world/{biome, stabilizer}.js  (Phase 3.1 + 3.2)
              ├─ src/collapse/collapse.js   (Phase 3.2)
              ├─ src/collect/echo.js         (Phase 3.3)
              ├─ src/inventory/inventory.js  (Phase 3.3 + 3.4)
              ├─ src/phase/{lock, glider}.js  (Phase 3.5)
              ├─ src/tutorial/tutorial.js    (Phase 3.6)
              ├─ src/progression/goals.js    (Phase 5)
              ├─ src/ui/{hud, minimap}.js    (Phase 4.1 + 4.3)
              ├─ src/settings/menu.js        (Phase 4.2 + 4.5 + 5.5)
              ├─ src/save/system.js          (Phase 1.6 + 2.4 + 2.7 + 4.4)
              └─ src/progression/goals.js    (Phase 5)

The orphan GameEngine modules under src/core/{game, player, phaseManager, phaseLockManager, particles}.js are kept as reference-only for incremental feature ports; they are not imported by the active path. Each carries a REFERENCE IMPLEMENTATION — DO NOT IMPORT banner at the top.

The pure modules (src/anchor/anchor.js, src/audio/footsteps.js, src/scan/lens.js, src/resonance/resonate.js, src/world/biome.js, src/world/stabilizer.js, src/collapse/collapse.js, src/collect/echo.js, src/tutorial/tutorial.js, src/progression/goals.js) follow the same pattern: no Three.js imports, no globals, no scene access. They're tested in isolation and consumed by main.js + the renderer.

Code-splitting

The Vite config (vite.config.js) splits the bundle into 4 chunks:

  • three (480 KB / 121 KB gz) — Three.js library
  • index (122 KB / 36 KB gz) — main entry, all gameplay systems
  • gameplay (8 KB / 3 KB gz) — small dynamic import for the gameplay-only path
  • audio (6 KB / 2 KB gz) — lazy-loaded audio module (the AudioContext requires a user gesture)

The initial main entry is 36 KB gzipped, well under the 200 KB target.

Tests

Headless (no browser, no WebGL — runs anywhere)

Pure-Node unit tests cover the pure modules + the World API. 38 files, 1903 checks total (all green — 9 pre-existing test regexes fixed; see commit "Test debt: fix 9 pre-existing headless test failures").

node tests/headless/test-phase12.cjs   #  17 checks
node tests/headless/test-phase13.cjs   #   7
node tests/headless/test-phase14.cjs   #  22
node tests/headless/test-phase15.cjs   #  12
node tests/headless/test-phase16.cjs   #  21
node tests/headless/test-phase17.cjs   #  26
node tests/headless/test-phase22.cjs   #  35
node tests/headless/test-phase23.cjs   #  51
node tests/headless/test-phase24.cjs   #  46
node tests/headless/test-phase25.cjs   #  70
node tests/headless/test-phase26.cjs   #  71
node tests/headless/test-phase27.cjs   # 107
node tests/headless/test-phase28.cjs   #  87
node tests/headless/test-phase31.cjs   #  95
node tests/headless/test-phase32.cjs   # 101 (Stabilizers + Collapse state machine)
node tests/headless/test-phase33.cjs   # 131 (Echoes + Inventory)
node tests/headless/test-phase34.cjs   #  63 (Resonance Cores + Amplifiers)
node tests/headless/test-phase35.cjs   #  95 (Phase Lock + Phase Glider)
node tests/headless/test-phase36.cjs   #  59 (Tutorial Zone)
node tests/headless/test-phase4.cjs    #  82 (Polish: HUD/Settings/Minimap/Save/Splitting)
node tests/headless/test-phase5.cjs    #  58 (Goals + Compass + FOV breathing + a11y)
node tests/headless/test-phase6.cjs    #  15 (Focused suite: boot + World.index round-trip + cyclePhase + BDD seed)

A static-analysis regression lock (boots Chromium via Playwright, screenshots the page, asserts source contracts):

npm run build
node tests/headless/smoke.cjs   # exits 0 on green, 1 on regression

The smoke test runs ~400 static-analysis regex checks against the source code (covering every Phase's key exports + __phaseShifter__ debug hooks) + the live init_recovered_when_webgl_failed assertion. WebGL fails in the Codex sandbox, so on a real machine all 5 pre-existing WebGL-related checks pass; in the sandbox the §6 acceptance is "0 new failures" (we currently see 5 unrelated pre-existing failures).

Playwright (browser-required)

npx playwright install        # one-time — downloads Chromium
npm test                      # runs tests/gameplay.spec.js on http://localhost:3002

The Playwright config (playwright.config.js) auto-starts vite --port 3002 for the test session. WebGL fails in headless Chromium without a GPU, so the Playwright tests assert at the API surface (the __phaseShifter__ debug hooks), not the visible pixels.

Continuous Integration

A GitHub Actions workflow (.github/workflows/ci.yml) runs npm run build + npm test on every PR. See the Actions tab for the latest run.

Sandbox quirks (for this dev environment)

  • The git working tree is at /home/kyle/Development/phaseshift but the git directory is at /tmp/phaseshift-git (a shared bare mirror). Use GIT_DIR=/tmp/phaseshift-git GIT_WORK_TREE=/home/kyle/Development/phaseshift git ... for git operations.

  • node needs sudo -E -n for DNS resolution in this sandbox (drop the prefix on a regular machine).

  • WebGL fails in headless Chromium (no GPU) — the smoke test asserts init_recovered_when_webgl_failed: true rather than failing outright.

  • The push remote is https://github.com/klampatech/phaseshift.git with the OAuth token read from ~/.config/gh/hosts.yml. The canonical commit + push pattern is:

    export GIT_DIR=/tmp/phaseshift-git GIT_WORK_TREE=/home/kyle/Development/phaseshift
    cd /home/kyle/Development/phaseshift
    git add -A
    git commit -m "..."
    TOKEN=$(grep oauth_token ~/.config/gh/hosts.yml | tail -1 | awk '{print $2}')
    git remote set-url origin https://x-access-token:${TOKEN}@github.com/klampatech/phaseshift.git
    git push origin main
    git remote set-url origin https://github.com/klampatech/phaseshift.git

License

MIT — see LICENSE (TBD if absent).

About

Phase Shifter — a voxel exploration game with phase-shifting mechanics (Alpha / Beta / Gamma phases).

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages