NES Live
A Windows x64 VS Code extension for live NES development. The workbench stacks the Mesen game preview, cartridge usage timeline and live memory debugger in one column. The cog opens a larger cartridge layout editor.
NES Live supports C, assembly-only, and mixed projects. It runs the project's build command and loads the resulting .nes ROM. The build can use ca65/ld65, another assembler, a Makefile, or a custom script. Symbol and cartridge-layout integration currently expects cc65/ld65-compatible .cfg and .dbg metadata.
Requirements and bundled components
- Windows x64 and VS Code 1.106 or newer.
- Python 3.10+, available as
python or set through nesLive.pythonPath.
- A Direct3D 11 GPU with hardware H.264 encoding exposed through Windows Media Foundation. The preview also uses WebCodecs in the VS Code renderer.
- The project's own assembler/compiler and build tools. For example, an assembly-only ca65 project needs ca65, ld65, and Make if its build uses a Makefile. These tools must be available through the project's configured paths or
PATH.
- A standalone Mesen executable if you want to double-click the preview and inspect the captured state in Mesen's full debugger.
The extension bundles the Mesen emulation core, native shader renderer, and ShaderGlass shader library. Project toolchains and build commands remain external. Other users can use the extension with their own projects once those requirements and project paths are configured.
Install and open
- For a first installation in VS Code 1.106+, use Extensions: Install from VSIX… with
dist/nes-live.vsix.
- Open an NES project, or the parent
NES/projects folder. Run NES Live: Open Workbench.
- NES Live has its own persistent tab in the Secondary Side Bar, alongside Codex. No manual view move is required.
- Save a source file to compile and run, or use NES Live: Build and Run.
For local development, open NES Live's extension details page → gear menu → Update, then use Restart Extensions in that menu. Update reads the local tools/nes-live source folder (or nesLive.localSourcePath), replaces only changed files in the existing installation, and keeps its version and settings. These actions are outside the NES workbench. No package build or VSIX installation is needed for subsequent changes. If your installed copy predates these menu actions, double-click Update.cmd in this source folder once, then run Developer: Reload Window to register the extension page menu. It updates the existing installed copy in place without making another extension package.
Python 3.10+ must be available as python, or configured with nesLive.pythonPath. The native Mesen core and shader renderer are bundled. The local NES tools folder is detected beside projects; you can set nesLive.toolsPath explicitly. cc65 and your project's build command remain external. Standalone handoff uses Mesen/bin-strict/Mesen.exe, then bin-edn8, or the mesen path in the project file.
Live video requires a Direct3D 11 GPU with a hardware H.264 encoder exposed through Windows Media Foundation. The native path requires a hardware encoder; it does not fall back to software image encoding. The webview requests hardware video decoding through WebCodecs.
The preview toolbar stays on one row above 540 CSS pixels and uses two compact rows in narrower panels. The speaker icon controls Mesen's native master volume; mute is remembered across rebuilds and restarts. Build and settings live in the same toolbar, without a separate Preview/Mesen Core header.
The included examples/hello project builds a controller test ROM using the local cc65 tools. It includes source, .cfg, symbols, and a small original test font. No commercial game ROMs are included.
Project configuration
NES Live: Configure Project selects a project and opens nes-live.json. Existing projects with a Makefile are detected automatically, including the three projects in this workspace. The existing Makefile remains the build entry point.
{
"name": "My NES game",
"build": { "command": "C:/w64devkit/bin/mingw32-make.exe", "args": [] },
"rom": "bin/game.nes",
"cfg": "mmc3.cfg",
"debug": "bin/game.dbg",
"map": "bin/game.map"
}
Paths are relative to the project directory. build.cwd and build.env are optional; commands run directly with argument arrays. NES_TOOLS is passed to builds. .dbg files come from ld65 --dbgfile; they provide actual segment sizes, file offsets, and symbols. The ROM header provides mapper and cartridge capacity.
Set nesLive.projectFile when your project is nested more deeply. For a prebuilt ROM, omit build and provide rom; emulation and memory inspection work, while layout saves require a build command.
Build command detection
NES Live chooses the build configuration in this order:
nes-live.json, when present. Its build command and output paths are authoritative; omitting build selects a prebuilt-ROM workflow.
- A
Makefile in the project root. The extension runs Make's default target.
main.c, using a basic cl65 command from the configured NES tools directory.
- An existing
.nes file in the project root, for a project without one of the build configurations above.
Without nes-live.json, build output defaults to bin/game.nes, with bin/game.dbg and bin/game.map as metadata paths. The linker configuration is detected from mmc3.cfg or another root-level .cfg file. Set explicit paths when your project uses a different layout.
Assembly-only projects
Assembly-only projects work through a Makefile or an explicit build command. A directory containing only main.s does not currently get an automatically generated assembler/linker command; the automatic single-source fallback looks for main.c.
For example, an assembly project whose Makefile produces files in build/ can use:
{
"name": "Assembly NES game",
"build": { "command": "make", "args": [] },
"rom": "build/game.nes",
"cfg": "nes.cfg",
"debug": "build/game.dbg",
"map": "build/game.map"
}
Use the command name or full executable path for your installed Make, such as mingw32-make.exe on Windows. The Makefile owns the source list, assembler flags, object order, libraries, and linker command. For ca65/ld65 projects, assemble with debug information (ca65 -g) and generate symbols with ld65 --dbgfile build/game.dbg; -m build/game.map produces the map file. A custom build script can be configured in place of Make.
ROM playback and raw memory inspection are available without linker metadata. Providing .cfg and .dbg files enables the cartridge layout, measured allocation data, symbol search, and bank-aware inspection. Layout editing requires an ld65-compatible configuration and a working build command.
When builds run
The default nesLive.autoBuild = onSave triggers a build when you save a watched file inside the selected project. Watched files are .c, .h, .s, .asm, .inc, .cfg, .chr, .bin, .spr, .json, and Makefile. The extension waits 450 ms after the latest matching event; nesLive.buildDelay controls that delay.
The extension requests a build on those file events. The project's build system decides what actually needs recompiling, using its dependencies and timestamps. For the local Summer Island project, the Makefile runs cc65 to compile C, ca65 to assemble, and ld65 to link the ROM.
onSave builds the saved project files.
onType builds a temporary project copy containing unsaved editor buffers, leaving the original buffers unsaved.
off leaves builds to NES Live: Build and Run or the build button.
On first opening the workbench, an existing configured ROM is loaded directly. If the ROM is missing and a build command is available, NES Live builds it. If another build is requested while one is running, the extension queues a new build and skips loading the superseded result.
A build result is loaded when the command exits successfully, the configured ROM exists, its iNES/NES 2.0 header is accepted, and its mapper/submapper matches the running game. Failed builds keep the current game running. A successful rebuild reloads the game and reapplies its saved debugger settings and sticky memory overrides.
Preview and CRT
- Actual Mesen emulation, audio and controller input. Click the preview to focus it. Arrows move, Z/X are A/B, Enter is Start, Shift is Select. Losing focus releases all buttons.
- The display toggle selects square pixels (256×240, 16:15 display ratio) or 4:3 CRT. Both width and height are fitted explicitly and the choice persists. The native shader viewport uses the same ratio.
- NES safety guides match the supplied 60 Hz reference: danger in the top/bottom 8 lines; action safe
(0,8)–(255,231); post-1985 safe (8,16)–(247,227); title safe (16,24)–(239,215). The guides are composited into the source image before the CRT shader, so curvature, bloom, masks and scanlines affect them. Select all guides, an individual area, or off.
- All 2,147 ShaderGlass presets are imported, including textures, parameter overrides and original credit excerpts. Search by preset or category. Click the star to save a shader. Favorites is the first section in the selector and is stored globally across projects. The parameter button exposes adjustable uniforms, Reset to the selected shader's defaults, and named Save / Load parameter presets. Saved parameter presets are specific to each shader and retained across projects and restarts; saving the same name updates it. Current parameter values also survive automatic game rebuilds.
- Shaders execute through the copied ShaderGlass Direct3D 11 pass implementation, with viewport scaling, samplers, aliases, LUT textures, mipmaps, history and feedback resources. This is the native library, not CSS scanline approximations.
- The live path is D3D shader texture → GPU color conversion → hardware H.264 encoder → authenticated loopback binary stream → WebCodecs worker → OffscreenCanvas. The extension host only carries commands and metadata. Rendered frames are never mapped back to CPU memory on the live path; there is no PNG/base64 transport or per-frame VS Code message/acknowledgement. Native screenshot/readback functions are reserved for explicit offline shader tests. Stream and decoder queues are bounded; stale dependent frames trigger a fresh keyframe. Hidden views disconnect the video stream.
- The footer reports presented preview FPS from the worker. Its tooltip distinguishes emulation rate, encoded rate and hardware encoder name.
- Double click the preview to create a Mesen
.mss state and copy the matching ROM and symbols into a dedicated handoff folder. Mesen starts with a one-shot Lua loader, restores the state after ROM loading, and breaks for inspection. This also works for unsaved-source builds. Lua filesystem permissions are unnecessary.
Cartridge banks and configurator
The cartridge has Overview / Banks tabs, with All / RAM / PRG / CHR filters shared between both views. Each view fills the cartridge area, and the selected view is remembered. RAM is amber, PRG green and CHR blue throughout. Click a bank in Overview to open it in Banks. The summary shows all capacities, for example 512 KiB PRG / 256 KiB CHR / 8 KiB WRAM. WRAM is confirmed from Mesen's actual work/save RAM sizes while running, with explicit ROM-header metadata used beforehand; unspecified legacy header capacity is shown as unknown. A combined linker CHARS region is divided for display into individual physical banks: 1 KiB for MMC3, otherwise explicit 8 KiB pages. Each bank reports its unit size in the agent data; live PPU-window mapping remains separate. The original linker region stays intact for editing. Show all regions sequentially or select an individual bank. The Banks zoom slider expands the address timeline. Ctrl + wheel zooms around the address under the pointer. Click a segment or bank background to inspect its physical memory; equal CPU addresses in separate banks remain distinct.
The cog opens Cartridge Layout. Drag segments between compatible regions, or edit load/run regions, starts, sizes and alignment precisely. The mapper stays locked for every existing project. The editor cannot change header symbols, ROM order, segment type, or mapper settings.
Saves preserve comments, untouched whitespace, linker symbols and expressions. Structural checks reject overflows, new RAM collisions and RAM/ROM domain moves. The extension then builds a separate candidate project and verifies the mapper before atomically replacing the original .cfg. A failed candidate leaves the original configuration intact. Source-defined layout contracts still run through the project's build.
Every successful save keeps a checksum-protected backup in undo/nes-live/. Disk undo survives VS Code restarts and refuses to overwrite later external changes.
Debugging
Pause/resume, reset, instruction step into/over/out, reverse instruction step and frame advance call the Mesen debugger. CPU registers, cycle count and frame count update live. The inspector supports CPU bus, internal RAM, cartridge RAM, save RAM, physical PRG/CHR, PPU, OAM and palette memory. An exact variable search jumps to its address and highlights its bytes in light blue; choose a result for partial or ambiguous names. ROM symbols show the bank and physical offset. In-flight reads from the previous page cannot overwrite a symbol jump.
Drag over bytes for a contiguous selection; this is an inspector selection rather than operating-system text selection. Double-click, press Enter, or choose Edit to change a byte or selected range while the game runs. Enter one hex byte to fill the range, or one byte per selected address. ROM edits patch the running core's ROM buffer and do not alter the ROM file.
Hold a byte or selected range for 600 ms, or use Make sticky / Remove sticky in its right-click menu. Sticky overrides have a purple marker, are saved per project, and are restored after reset, automatic rebuild, savestate load, and extension restart. Editing an already-sticky value updates its stored override. Clear sticky removes all overrides. CPU RAM uses Mesen's native write freeze, including internal RAM mirrors; other graphics memory is reapplied at frame intervals. Selections and total sticky overrides are limited to 4096 bytes.
Watches use Mesen expressions (A, PC, [address], {address}). Read, write and execute breakpoints can include conditions. Watch and breakpoint lists persist with the webview. ROM rebuilds reload the core and reapply breakpoints and the selected shader. Failed builds retain the current game and report cc65 diagnostics in Problems.
nesLive.autoBuild defaults to onSave. onType builds an isolated disk snapshot including dirty editor buffers; it does not save the buffers into the project. off leaves builds manual. Rebuilds reset the game; this version does not attempt to transfer an old savestate into a changed ROM.
Build and validation
No npm dependencies are required for the extension host or webview.
python scripts/build_native.py
python scripts/import_shaders.py
node --test --test-isolation=none test/*.test.js
python examples/hello/build.py
python scripts/smoke_host.py examples/hello/bin/demo.nes
python scripts/test_shaders.py
python scripts/test_guides.py
python scripts/test_gpu_video.py
python scripts/benchmark_stream.py
python scripts/test_handoff.py
python scripts/test_pipeline.py
python scripts/package.py
python scripts/check_vsix.py
The native build requires MinGW g++ with C++20 and Direct3D/WIC headers. Source imports use the sibling Mesen and ShaderGlass checkouts. F5 launches the included VS Code development configuration. scripts/test_vscode.py runs the extension integration test in an isolated VS Code profile, including staged layout save and disk undo.
Validated here: native compilation; hardware H.264 streaming with the NVIDIA Encoder MFT and an instrumented zero rendered-frame readback count; Mesen CPU stepping/reverse stepping, RAM writes and state save/load; original Summer Island ROM loading; .cfg/.dbg parsing for the large MMC3 project; standalone Mesen handoff; single/multi-pass shaders, textures and feedback; exact source-pixel guide bounds and guide processing by the CRT shader; candidate linking, failed-candidate isolation and disk undo; dirty-buffer snapshots; aspect fitting, shader favorites, CHR navigation, video-worker backpressure and agent request/response handling in automated tests. Native-stream measurements cover real Mesen output through GPU shaders, hardware encoding and binary socket receipt; they do not measure browser presentation. Windows/browser-control restrictions prevented an actual VS Code visual check here. The runnable VS Code integration test remains included.
This first release targets Windows x64 and NES/iNES/NES 2.0 with cc65/ld65 layout metadata. Full Mesen tool windows (PPU viewers, event viewer, profiler, trace, source debugger) remain available through the Mesen handoff; they are not embedded in this version. Mapper selection and new-project mapper templates are not implemented.
Public distribution status
Other developers can use NES Live for C, assembly-only, or mixed projects when their system and build setup meet the requirements above. Alternative assemblers can produce ROMs for playback; the current symbol/layout features require compatible linker metadata.
The current Update / Restart Extensions actions are intended for local-source development and require a local NES Live source checkout. Public-release preparation still includes a clear external-toolchain setup process and a published extension update channel that works independently of a developer checkout. Installation and tool paths need to be documented for the recipient's machine. This remains a Windows x64 development release with the validation limits described above.
Publishing to Visual Studio Marketplace
Packaging, checking a local installation, and publishing are separate commands. package.py and check_vsix.py do not upload anything.
Sign in to Marketplace publisher management and create or select your publisher. The package currently uses publisher ID ninjadynamics; that ID must belong to your Marketplace account. If you choose a different publisher ID, update the extension identity and its related development updater/menu references before publishing.
Choose the release version in package.json. Keep win32-x64 as the package target because the extension bundles Windows native libraries. Confirm the public-release requirements above are covered for recipients.
From the nes-live source folder, build and check the package:
python scripts/package.py
python scripts/validate_vsix.py
python scripts/check_vsix.py
Upload from a terminal with python scripts/publish.py. It reads VSCE_PAT from the process or Windows user environment, or asks for a token with hidden input in an interactive terminal. Use a short-lived Marketplace publishing token with Marketplace → Manage scope, owned by an account authorized for ninjadynamics. Credentials are never saved by this script or included in the VSIX. Keep them out of chat and source files. Microsoft is retiring global PATs on December 1, 2026; use the Trusted Publishing route below for ongoing automated releases.
The upload command creates a listing or updates an existing one, avoids uploading an already-present version/target, and waits for server validation and anonymous availability. Only PUBLISHED means the version passed those checks. AWAITING_VALIDATION means it was uploaded but is still being processed; inspect the reported validation message and run python scripts/publish.py --status to check again. HTTP failures include the server's message and activity ID when available. --dry-run validates the archive and reports credential presence without network access. --non-interactive uses configured credentials without prompting.
Alternatively, in the publisher portal choose New extension → Visual Studio Code and upload dist/nes-live.vsix. The package contains the README, icon, native runtime, shaders, agent instructions, licenses, and corresponding bundled source. Offline package validation checks the archive metadata and assets; check_vsix.py additionally installs it into an isolated local VS Code profile. Neither replaces Marketplace server validation.
For subsequent public releases, increase the version in package.json, rebuild the same dist/nes-live.vsix filename, and upload it as an update to the existing listing. Marketplace versions must advance; local development updates can keep their fixed version.
The listing uses media/icon.png, a 512 × 512 PNG configured by the manifest's icon field. Its dark Marketplace banner is configured through galleryBanner; the custom VSIX packager includes the matching icon and banner metadata.
For automated releases, Microsoft's current recommended route is GitHub Actions Trusted Publishing with OIDC. After configuring a publisher policy and building the VSIX, use vsce publish --packagePath dist/nes-live.vsix --oidc to upload that package. The Python uploader supports local token authentication; OIDC releases use the official CLI. See the official publishing guide for publisher registration, uploads, platform targets, authentication, and version updates. The offline packager matches the gallery flags, properties and content-type conventions in Microsoft's packager; the uploader uses the Gallery API routes used by vsce.
Agent interface
NES Live automatically exposes an authenticated HTTP JSON API on 127.0.0.1 for the selected project. Agents query current bank usage, capacities, free ranges, symbols, live memory, registers, and mapper windows directly. No export button, context snapshots, or filesystem request queues are required. The { } button opens the agent instructions.
Read AGENT_API.md for complete instructions and a working Python client. The same instructions are created automatically at .nes-live/AGENTS.md in the selected project. .nes-live/api.json supplies the current endpoint, token, project root, and session ID. Send POST to that endpoint with a bearer token and a body containing projectRoot from the discovery file and one query:
{"op":"banks","kind":"CHR"}
{"op":"memory","memory":"NesMemory","start":"$0300","length":128}
{"op":"symbol","name":"_player_x","length":1}
{"op":"status"}
{"op":"shaders","favoritesOnly":true}
VS Code extensions can call nesLive.query(query) directly; other agents use HTTP. Status requests sample Mesen on demand. Memory reads are limited to 4096 bytes and require the selected game to be running or paused. Duplicate symbol names return candidates with physical bank addresses instead of guessing. The API rejects mutations, browser-origin requests, unauthenticated calls, and queries targeting a different selected project. Responses include timestamps and a session ID; re-read discovery after restarts. Older context/status exports and filesystem request/response files are obsolete.
Source and credits
NES Live's original code is MIT licensed, copyright NinjaDynamics; see LICENSE, packaged as LICENSE.txt in the VSIX. Bundled third-party code and binaries retain their own licenses, including GPL licensing for Mesen and ShaderGlass; the entire bundle is not exclusively MIT licensed. Mesen core/configuration types: Sour and contributors, Mesen2. Native shader passes and library: mausimus and contributors, ShaderGlass, with the imported RetroArch author credits preserved in shaders/catalog.json and full generated headers in shaders/ShaderGlass-sources.zip. Library metadata and embedded bytecode are copied without shader translation. See the bundled third-party licenses. native/vendor/json.hpp retains its original MIT license notice. bin/MesenCore-sources.zip contains the local core source used by the bundled DLL.
The direct-stream and worker/canvas separation follows the local HyperSolar DualView implementation; its source is unchanged. NES Live's transport and GPU encoding code are separate implementations. The webview uses nonce-scoped scripts, a content security policy limited to packaged resources and the authenticated loopback video stream, and native execution restricted to trusted workspaces. See VS Code webview guidance and Microsoft's Direct3D-aware Media Foundation interface.