A first-person backrooms explorer that runs inside a VSCode webview. Endless yellow-wallpapered halls under humming fluorescent panels, drop-tile ceilings, and worn office carpet, filmed through the shake and grain of a 1990-era handheld camcorder. Somewhere past the fog, something else walks the same halls.
BackViews is built on cmd-backedges, the procedural infinite maze engine that serves as the base for this project: every wall, room, and prop is a pure function of the seed, so the same seed always rebuilds the same rooms, forever, in every direction.
As for the name: a VSCode extension is expected to contribute a view or two. These views just do not end.
- Installation
- Browser Demo
- Getting Started
- Game Play
- Settings
- Copilot Ghost-Writer
- API Quick Overview
- Architecture
Prerequisites:
- VSCode 1.85 or newer
- Node.js 18 or newer
- cmd-backedges checked out next to this folder (the build bundles its TypeScript source directly):
your-workspace/
cmd-backedges/ <- maze generation API
vscode-backviews/ <- this extension
Build the extension:
cd vscode-backviews
npm install
npm run build
# Package to Install from VSIX
npm run pre:publishOutput:
dist/extension.js extension host bundle
media/webview.js in-panel game bundle
To rebuild continuously while developing, use npm run watch. To regenerate media/icon.png
from the vector mark, use npm run icons.
A standalone browser version is available at isocialpractice.github.io/vscode-backviews.
To run the browser demo locally:
- Build the webview bundle (as described in Installation above)
- Serve the project directory with any web server:
# Using PHP php -S localhost:9090 # Using Python python -m http.server 9090 # Using Node.js http-server npx http-server -p 9090
- Open
http://localhost:9090in your browser - Click the view to capture the mouse and start exploring
The browser version automatically deploys via GitHub Actions whenever changes are pushed to the main branch. The workflow:
- Checks out both
vscode-backviewsandcmd-backedgesrepositories - Runs
npm install && npm run buildto generatemedia/webview.js - Deploys
index.html,media/, andmaterials/folders to GitHub Pages
- Open the
vscode-backviewsfolder in VSCode. - Press
F5(Run Extension). A new Extension Development Host window opens. - In that window, open the Command Palette (
Ctrl+Shift+P) and run BackViews: Enter the Backrooms. - Click the panel to capture the mouse, and start walking.
BackViews: Relocate (New Seed) drops you into a fresh random seed when the current halls start feeling too familiar. The same action is available from the in-game menu.
You wake up in an endless office interior. There is no exit; there is only further.
| Input | Action |
|---|---|
W / S or Up / Down |
Walk forward / back |
A / D |
Strafe left / right |
Left / Right or Q / E |
Turn |
Shift |
Hurry |
| Mouse (after clicking the view) | Look around |
M or Esc |
Open the menu |
Movement is camera-relative, and each axis (turn, strafe, forward/back) can be inverted from the settings if your instincts run the other way.
Things to watch for while wandering:
- Rooms, halls, and atriums. Corridors occasionally open into blob-shaped rooms, rare rectangular halls with props, and very rare wide-open atriums with taller ceilings.
- Random furniture. Filing cabinets, abandoned desks, couches, moving boxes, and structural columns appear where the generator places props.
- Wallpaper zones (off by default; enable
backviews.wallpaperShifts). Randomly shaped enclosed regions where the wallpaper shifts tint or pattern: an algorithm anchors wobbled radial blobs on a coarse lattice, and every cell inside a blob adopts that zone's palette. Crossing a boundary is how you know you have gone somewhere. - Doorways with depth. Walls are slabs, not planes: openings show jamb faces on both sides and carry a header with a visible underside where a passage punches through a wall line.
- Dead and flickering lights. Some ceiling panels are out. Some should be.
- The monster. Sometime inside its spawn window it appears beyond the fog and starts closing in. It obeys the same walls you do, pathfinding through open passages only, so corners and long detours are survival tools. If it reaches you, the tape cuts to static and picks back up at the starting cell. It does not stop hunting; it only starts over. Turn it off in the settings for a quieter walk, or pick its body plan: spider-like, human-like, cloud-like, or a different form each spawn.
The in-game menu (press M) has Resume, Relocate, Settings, and Help, plus live stats:
current seed, cells visited, and generator cache size.
All settings live under backviews.* in VSCode settings and are also editable live from the
in-game menu (menu changes persist back to your user settings).
| Setting | Default | Description |
|---|---|---|
backviews.seed |
0 |
Maze seed; 0 picks a random seed per panel open |
backviews.moveSpeed |
2.2 |
Walking speed in cells per second |
backviews.renderDistance |
14 |
Meshed/drawn radius in cells |
backviews.cameraShake |
true |
Handheld sway and walking head-bob |
backviews.filmGrain |
true |
Grain, vignette, and tracking-noise overlay |
backviews.vhsHud |
true |
REC dot, tape counter, battery, timestamp |
backviews.furniture |
true |
Render generator-placed props |
backviews.wallpaperShifts |
false |
Wallpaper zone tint/pattern changes |
backviews.mouseLook |
true |
Pointer-lock mouse look on click |
backviews.invertTurn |
false |
Flip turn direction (mouse and keys) |
backviews.invertStrafe |
false |
Flip strafe left/right |
backviews.invertForward |
false |
Flip forward/back |
backviews.materialPreset |
classic |
Wall material set: classic, office, pool, concrete, panel |
backviews.materialHueShift |
0 |
Wall hue rotation in degrees, -180 to 180 |
backviews.materialBrightness |
1 |
Wall brightness multiplier, 0.6 to 1.4 |
backviews.monsterEnabled |
true |
Something else walks these halls |
backviews.monsterSpeed |
2.6 |
Monster speed in cells per second |
backviews.monsterSpawnMin |
1 |
Earliest spawn, minutes after the world starts |
backviews.monsterSpawnMax |
5 |
Latest spawn, minutes after the world starts |
backviews.monsterForm |
random |
Body plan: spider, humanoid, cloud, or random per spawn |
backviews.copilotGhostWriter |
true |
Ghost-write Copilot chat and job status on the walls, plus a HUD token counter |
The material preset picks a pre-built wall set with one click, and the two sliders adjust its adjustable elements: hue rotates the material's color cast around the wheel, and brightness scales it lighter or darker.
The materials/ folder ships three photo textures that are tiled into the atlas when the
panel opens:
| File | Surface |
|---|---|
materials/wallpaper.jpg |
Walls (the classic preset papers every wall with it) |
materials/ceiling.jpg |
Ceiling tiles (the drop-ceiling grid is drawn back on top) |
materials/carpet.jpg |
Floor |
Swap any of these files to re-skin the game; if a file is missing or fails to load, that surface falls back to its procedural texture. Wallpaper zones still recolor the photo wallpaper and switch it to a darkened variant, and the hue/brightness sliders apply on top.
When Copilot is working in the same workspace, the halls start writing back. Its chat
responses and job status are scrawled onto the walls in a messy ink script, and a
camcorder-style token counter runs under the HUD battery. The whole feature lives behind a
single toggle, backviews.copilotGhostWriter (on by default, and switchable live from the
in-game menu under Copilot). Turning it off wipes any writing already on the walls.
There are two surfaces and three ways the game learns what Copilot is doing:
- The walls. Older chat exchanges are stamped statically on walls around you, one wall per exchange. The newest response is ghost-written: characters materialize onto the wall ahead as the answer streams in, and the text flows onto the next free wall as you walk and as each wall fills. Each writing gets its own handwriting jitter, ink drips, splatter, and marker sheen, seeded so already-revealed characters stay put while new ones appear.
- The HUD. The token count of the current response drives a
TKN 000000odometer under the battery icon that rolls toward the live value, with a blinking access mark while the job runs and a short linger after it finishes.
The status feeds, in order of precedence when a panel opens:
- Chat session mirror (automatic). The extension reads VSCode's own chat session store for the workspace and mirrors the most recent session onto the walls. Nothing to wire up: open a panel while a chat is streaming and the response ghost-writes itself.
- Language model tool
backviews_reportJob. An agent can call this tool as it works to push astatus,tokens, anddoneflag. Reference it from a.github/copilot-instructions.mdso the agent reports each step. - Status file / command. Write
.copilot/backviews-job.jsonin the workspace root ({ "working": true, "status": "...", "tokens": 1234 }), or call thebackviews.reportJobcommand programmatically from a task, hook, or another extension. A status file that stops updating goes stale and reads as idle.
All three routes are best-effort and fault-tolerant: a missing file, an unparseable session, or an absent language-model API simply leaves the walls quiet.
The world is powered entirely by cmd-backedges. The pieces this extension consumes:
Deterministic, infinite, memoized cell generation.
import { MazeGenerator } from 'cmd-backedges';
const generator = new MazeGenerator({ seed: 1234, depth: { min: 1.1, max: 1.6 } });
const cell = generator.getCell(0, 0); // MazeCell: bounds, edges, dimensions, feature
generator.isPassable(0, 0, 'east'); // authoritative movement testgetCell(cx, cy)returns the cell's footprintbounds, fouredges(eachsolidor open, with start/end line segments),dimensions(including extrusiondepth, used here as ceiling height), and an optionalfeatureprop (column or furniture).- Each edge carries
metadatafor 3D extrusion:surfaceType(mapped to wall materials),materialSeed(stable per-edge wear variation), heights, and thickness. isPassable(cx, cy, direction)drives the collision system, and doubles as the movement test for the monster's breadth-first pathfinding, so player and monster obey identical walls.
Discrete player tracking with events.
import { MazeSession } from 'cmd-backedges';
const session = new MazeSession(generator);
session.on('enterCell', (cell) => console.log('first visit', cell.cx, cell.cy));
session.move('east');The renderer moves continuously, and keeps the session in sync cell-by-cell so move,
blocked, and enterCell events stay meaningful (visited-cell stats in the menu come from
enterCell).
hashCoords, unitFromHash, and mulberry32 are reused for every deterministic visual the
renderer adds on top of the structure: light placement and health, wallpaper zone shapes and
palettes, furniture archetypes, and procedural texture noise.
Note: the
cmd-backedgespackagemainfield currently points atdist/index.js, but its compiled output lands atdist/src/index.js. This project sidesteps that by bundling from the library's TypeScript source via an esbuild alias (seebuild.mjs).
Two bundles from one TypeScript tree:
src/
extension.ts Extension host: panel lifecycle, CSP, settings
persistence, and the Copilot job/chat status mirror
shared/
settings.ts Settings shape + host/webview message protocol
chatlog.ts Parser for VSCode's chat session store into a
wall-ready snapshot (json + jsonl set-op formats)
webview/
main.ts Frame loop, camera shake, flicker, host messaging
world.ts cmd-backedges -> geometry: chunk meshes, collision,
wallpaper zones, lights, furniture, material presets
monster.ts Spawn scheduling, BFS stalking, body-plan meshes
renderer.ts Raw WebGL: one shader, atlas texturing, fog, chunk
draws, one dynamic mesh for the monster, plus an
alpha-blended decal pass for wall writing
graffiti.ts Copilot ghost-writer: static history stamps and the
live streaming response written onto wall decals
textures.ts Procedural canvas texture atlas, patched at runtime
with the photo materials from materials/
input.ts Keyboard + pointer-lock mouse
menu.ts Pause menu / settings / help overlay
film.ts 1990 camcorder overlay: grain, vignette, tears, HUD,
the HUD token counter, and the signal-lost static burst
Flow:
extension.tsopens a webview panel, injectsmedia/webview.jsunder a strict CSP, and exchanges typed messages:config,relocate,jobStatus, andchatSessioninbound,readyandupdateSettingoutbound. While the panel is open it polls the Copilot job status file and the workspace chat session store, forwarding changes asjobStatusandchatSessionmessages.world.tswraps aMazeGenerator+MazeSessionpair. As the player walks, 4x4-cell chunks inside the render distance are meshed (floor, ceiling, light panels, extruded solid edges, header bands where ceiling heights step down, and props) and uploaded; chunks out of range are disposed.- Everything decorative is a pure function of the seed and coordinates, matching the library's own philosophy: revisit any corridor and it looks exactly as you left it.
renderer.tsdraws all chunks with a single shader: atlas tiles repeated viafract(), per-vertex baked lighting from nearby ceiling panels, exponential fog, and a global fluorescent flicker uniform. Emissive light-panel vertices skip scene lighting.monster.tssleeps until a randomized moment inside the spawn window, then walks the same passability grid as the player (breadth-first search toward the player's cell, recomputed as they move) and is rebuilt into the renderer's single dynamic mesh every frame.film.tslayers the camcorder look on a 2D canvas so the 3D pass stays simple, cuts the whole picture to static for a moment when the monster connects, and rolls the HUD token counter from the job/chat snapshots.graffiti.tsturns thejobStatusandchatSessionsnapshots into wall writing. Ray casts along the player's gaze find free walls; history exchanges are stamped statically while the current response ghost-writes character by character into a shared canvas atlas, uploaded to the renderer's decal pass. The whole subsystem is gated bybackviews.copilotGhostWriter.
Since the maze, lights, zones, and props are all deterministic, the only state worth saving is the seed and the player's position, which the webview persists across panel hides and reloads.
MIT
