RetroSharp.Tool 0.0.1231

There is a newer version of this package available.
See the version list below for details.
dotnet tool install --global RetroSharp.Tool --version 0.0.1231
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local RetroSharp.Tool --version 0.0.1231
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=RetroSharp.Tool&version=0.0.1231
                    
nuke :add-package RetroSharp.Tool --version 0.0.1231
                    

RetroSharp

RetroSharp is a C#-inspired language, compiler, and zero-cost 2D framework for 8-bit game systems.

I'm making this in my free time to learn about compilers and old-school game hardware. It was supposed to make me happier; it has also managed to reach the shaved-head milestone.

Documentation

  • Getting started — install the CLI, create a game, and build Game Boy and NES ROMs.
  • Language reference — types, declarations, operators, control flow, functions, arrays, and zero-cost abstractions.
  • Portable2D SDK reference — frames, input, worlds, cameras, sprites, animation, audio, actors, projectiles, and effects.
  • Documentation home — browse guides, target references, tooling, architecture, and contributor documentation.
  • Samples — runnable programs organized by feature and implementation layer.

Start without installing .NET with the standalone CLI guide. For a conceptual introduction, read the project overview.

Project shape

RetroSharp has three layers that should stay separate:

  • Language: target-neutral syntax, types, storage, constants, helpers, control flow, and zero-cost source ergonomics.
  • Portable 2D SDK: capability-checked game APIs for frames, input, worlds, cameras, sprites, palettes, music, animation, and actors.
  • Target intrinsics and lowering: Game Boy, NES, and future hardware-specific behavior.

The language should not know what a sprite, camera, tilemap, joypad, or palette register is. Those concepts belong in the SDK or in explicit target intrinsics.

Compilation paths

RetroSharp parses and prepares source through a shared target-neutral frontend, then lowers it directly through one of three cartridge targets. The CLI requires an explicit --target for standalone source files and manifests that do not declare target or targets:

  • --target nes: emits an iNES mapper 0 ROM for static background/tile drawing plus tick-based input, logical sprites, horizontal camera streaming, four-screen 2-axis camera movement, Tiled World.Load(...), runtime animation helpers, camera-relative runner collision, and VGM/VGZ-sourced 2A03 BGM playback. HUD and generic world-space collision are still not implemented.

  • --target gb: emits a 32 KiB ROM-only Game Boy cartridge when the program fits, or an MBC1 banked ROM when large music assets need more space. It supports static background/map setup and a first runtime sprite loop subset with local byte-backed variables, assignment, if/else if/else, while, for, half-open range for, Video.WaitVBlank(), tick-based input polling, Scroll.Set(...), position-based camera X/Y scrolling with runtime row/column streaming, Sprite.Set(...), simple source-map tile queries for collision, joypad button queries, .gbapu/.gbapu.json APU trace playback, and VGM/VGZ-sourced DMG playback through the same compact on-ROM trace repack. Bank selection for banked BGM data is emitted by the runtime; source code keeps using Music.Asset(...), Music.Play(...), and Audio.Update().

  • --target sms: in development. Emits a 32 KiB Master System ROM for pure-language programs with static background drawing (Palette.Set, Tilemap.Set, Tilemap.Fill), sprites (Sprite.Asset, Sprite.Draw), World.Map worlds with Camera.* scrolling on both axes, music and sound effects from SN76489 VGM logs, or transcoded from NES and Game Boy ones (Music.*, Sfx.*, Audio.*), Video.Init(), Video.Present(), Video.WaitVBlank() and portable Input.* on the two-button pad; every other SDK operation fails with a capability diagnostic. See docs/MasterSystemTarget.md.

  • --target gg: in development. Builds the Master System target for the Game Gear and writes a .gg ROM: the program sees the 160x144 LCD window, colours are 12-bit, and Start is a held button. The camera scrolls inside the window. See docs/MasterSystemTarget.md (Game Gear).

The NES and Game Boy targets accept --runtime-abi-out <path> for a deterministic, versioned JSON projection of their target-owned runtime layout and compiled user-local symbols, bound to the emitted ROM content. It does not change ROM bytes. tools/gameboy/runtime_abi.py and tools/nes/runtime_abi.py validate the matching target contract, layout fingerprint, and ROM identity before diagnostics consume an address. Game Boy runtime regions are fixed in GameBoyRuntimeMemoryLayout, so its runtimeRegions collection is empty; NES adds the ROM-specific regions allocated by its build.

Both cartridge targets accept --symbols-out <path> for a deterministic UTF-8 debugger symbol file. Each line has the MCP-compatible form AAAA name and names may contain spaces. The export includes the target's runtime-memory ranges and named addresses, compiled variables, and any ROM-specific runtime regions reported by that target. It intentionally excludes ROM code labels, fixed linker symbols, and banked segments in this first version. Because one symbol file describes one target build, the option requires a single target and cannot be combined with --world-budget-report.

Every target accepts --build-report-out <path> for a deterministic JSON build report (retrosharp.build-report v1): the ROM size and SHA-256, the bytes each owner placed in each bank, the code and data labels with their bank and address, the static per-frame CPU estimate, and, in runtimeArithmetic, every runtime multiply, divide and shift loop per function with its worst-case cost in the target's CPU unit (docs/RetroSharp.Language.md §5), and, in compilerStorage, every local the compiler declares to hold a construct, with its function, bytes, address, source position and reason. The Master System and Game Gear reports carry only the ROM size and hash, the runtime arithmetic and the compiler storage so far. The report is a diagnostic for comparing two builds with tools/roms/diff_roms.py, not a stable contract, and it requires a single target.

When a program has runtime arithmetic, the build also prints one line such as Runtime arithmetic: 7 loops, worst case 6,070 cpu-cycles (Average 2 loops, 2,360; Main 5 loops, 3,710). The report has the detail by function, operation and operator; for example, with jq:

jq -r '.runtimeArithmetic.functions[] | .function as $f | .operations[]
  | "\($f): \(.count) x \(.operation) (\(.operator)) = \(.worstCaseTotal)"' game.build-report.json

Loops are attributed to functions, not to source lines: a function with two divisions lists two divide-16 loops without saying which line holds each. The totals price each loop once; how often it runs per frame is up to the code around it.

For example, compile the runner and export both diagnostic sidecars:

dotnet run --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- \
  --target gb \
  --out /tmp/runner.gb \
  --runtime-abi-out /tmp/runner.gb.runtime-abi.json \
  --symbols-out /tmp/runner.gb.sym \
  games/runner/runner.retrosharp.json

dotnet run --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- \
  --target nes \
  --out /tmp/runner.nes \
  --runtime-abi-out /tmp/runner.nes.runtime-abi.json \
  --symbols-out /tmp/runner.nes.sym \
  games/runner/runner.retrosharp.json

In the matching Game Boy or NES debug MCP, call load_rom with the generated ROM, load_symbols with its .sym file, then resolve_symbol or read_symbol with a canonical name such as camera.XLow, camera.X, packed camera.FramePending, WorldPack.VisualSlot0, or player.x.

The actor acceptance slices live in samples/actor-framework and samples/actor-contact-lifecycle. The former fills an ActorSlots pool of the game's own union Enemy(Goomba, Bat, Koopa) from two Tiled object layers, with runtime camera-window activation, defeat and tile contact. The latter proves transient top-contact capture through Contact, conditional defeat, preserved state, a three-step defeated lifetime, and the freed slot on Game Boy, NES and Master System. Both lower without heap allocation or runtime dispatch. The current public surface is in docs/Portable2DSdkV1.md, and ADR 0014 records the move from the V1 actor framework. The measured generated-code scaling work and CPU-budget plan live in docs/history/GeneratedCodePerformanceRoadmap.md.

Language surface

Right now, RetroSharp can compile simple programs with:

  • Basic arithmetic and logic operations
  • Variables and assignments
  • Decimal, hexadecimal (0x2A), binary (0b1010_0000), _-separated, and width-suffixed (255u8, 0x1234u16) integer literals
  • Type aliases normalized at compile time
  • Compound assignment with +=, -=, &=, |=, and ^=, plus statement-only ++ and --
  • Top-level and block-local constants with optional type annotations folded at compile time, including constant boolean and conditional expressions
  • sizeof(type) folded at compile time for primitive, reserved internal pointer-size marker, enum, and plain struct types
  • offsetof(type, field) folded at compile time for direct fields of plain struct types
  • countof(array) folded at compile time for fixed-size local arrays
  • Top-level enums with qualified members folded at compile time
  • Plain local structs with named and shorthand initializer lists plus field access in the current cartridge targets
  • Restricted static classes with fixed-layout fields, instance methods lowered to receiver helpers, and static constants/methods lowered without heap allocation or dispatch
  • Fixed-size local byte arrays with initializer lists, initializer-inferred lengths, constant index access, and byte-backed runtime index access in the current cartridge targets
  • Function calls, including inline helper calls with parameters, named arguments, default parameter values, single-return expression helpers, and => expression-bodied helpers in the current cartridge targets
  • Control flow (if/else if/else, no-fallthrough switch with multi-value and half-open range cases, while, do while, for, half-open range for, break, continue)
  • Half-open range membership expressions such as tile in 1..4, lowered like tile >= 1 && tile < 4
  • Short-circuit &&/|| and unary ! conditions, plus byte-backed 0/1 logical value expressions, in the current cartridge targets
  • Conditional value expressions (condition ? whenTrue : whenFalse) lowered to direct branches in the current cartridge targets
  • Byte-backed bitwise &, |, and ^, plus folded constant ~ masks, in the current cartridge targets
  • Explicit casts such as (u8)expr as validated zero-cost expression markers in the current cartridge targets
  • Canonical fixed-width types (u8, i8, u16, i16, bool) plus source aliases where the current target accepts them
  • Immutable let locals with compile-time width inference: i16/u16 fields, locals, helper results, and arithmetic keep two-byte storage; expressions proven byte-valued keep one byte
  • Public gameplay code stays at the SDK/resource level; raw buffers, hardware addresses, pointer member access, and ptr<T> APIs remain reserved for internal SDK/backend addressability policy

let never narrows a statically word-valued expression. A width suffix on a single literal remains authoritative, and an unsuffixed literal keeps the existing u8 default. For non-literal expressions, an unambiguous i16 or u16 operand makes the result use that same word type; otherwise a proven byte-valued expression remains byte-backed. Mixing signed and unsigned word values, or using an expression whose scalar type cannot be proven, is a compile-time error; an explicit cast disambiguates the declaration. This rule changes storage selection only and adds no runtime type metadata or dispatch.

Example program:

i16 Main()
{
    return 2 * 3 * 4;
}

Framework surface

The portable 2D SDK gives game code a common vocabulary for tile-and-sprite machines while keeping each target's limits explicit. Published projects pin RetroSharp.Portable2D in manifest sdkDependencies; checkout-local projects can select the bundled copy through libraries. Game code then uses frame/input calls, Tiled World.Load(...) with native tileset animation and a runtime background-animation toggle, collision flags, camera-relative AABB queries, logical palettes, logical sprites, animation, music declarations, and game-owned ActorSlots pools with Tiled spawns. Standalone source files can still declare import RetroSharp.Portable2D;; unknown imports are rejected. Generic compiler and frontend APIs start with an empty SdkLibraryRegistry: a direct C# host must compose every source library it wants to expose, including Portable2D. The CLI is that host for normal command-line use: it supplies its bundled Portable2D package alongside any --lib-path libraries. Pass --no-default-libraries to build a source file or project without that bundle; then use --lib-path and explicit imports for every library required. A library package is a directory with retrosharp-library.json and one or more RetroSharp source files:

{
  "import": "Acme.Wait",
  "rootNamespace": "Acme.Wait",
  "sourceRoot": "src",
  "namespaceMode": "physical",
  "sources": [
    "src/api.rs",
    "src/timing/rules.rs"
  ],
  "targets": [ "gb", "nes" ]
}

--lib-path may point at one package directory or at a directory containing package subdirectories. Local paths remain the fastest editing workflow.

Versioned source libraries and SDKs

Projects can also restore source libraries and SDKs from one isolated NuGet feed. Versions in packageDependencies are direct, exact versions; the CLI writes the complete direct-and-transitive graph to the portable retrosharp.lock.json and extracts packages under .retrosharp/packages. The same graph is prepared once before a multi-target GB/NES build. A package must contain the ADR's retrosharp-package.json with kind: "source-library" and its library manifest at retrosharp/retrosharp-library.json.

An SDK package uses kind: "sdk-plugin" and includes that source manifest plus its descriptor assembly under plugin/net10.0/. Declare it once to restore and activate both parts:

{
  "targets": ["gb", "nes"],
  "sources": ["src/main.rs"],
  "packageSources": ["./local-feed"],
  "sdkDependencies": { "Acme.Probe": "1.0.0" }
}

packageDependencies remains for source-only packages. A transitively restored SDK remains passive; declare it directly in sdkDependencies before importing and using it. The host checks package identity/version and the source import against the descriptor. An explicit RetroSharp.Portable2D package replaces both bundled pieces. --no-default-libraries removes the bundle while retaining explicit packages.

{
  "targets": ["gb", "nes"],
  "sources": ["src/main.rs"],
  "packageSources": ["./local-feed"],
  "packageDependencies": { "Acme.Timing": "1.2.3" }
}

The normal build only invokes restore if the local material is absent and never rewrites an existing lock. Use --restore to create or intentionally update a lock, --locked-mode to require it, and --no-restore or --offline to fail without contacting the feed when material is absent. One feed needs no extra mapping; multiple feeds must declare packageSourceMappings from each source entry to its package-id patterns, so a failed feed cannot silently fall back to another. Restore inspects archive paths and rejects build content and undeclared executables. It never loads package DLLs, props, targets, tools, or scripts. SDK entry points run only during explicitly activated builds.

For local development, use --lib-path and --sdk-plugin-path <retrosharp-package.json-or-dll> together to replace one product. External DLLs need package metadata; bare DLLs are invalid. A complete local pair overrides a restored or bundled SDK; a partial override fails. Sample and smoke builds that use the CLI bundle therefore pass neither path; external SDK tests pass both paths with --no-default-libraries.

Source-only libraries cannot add new compiler semantics. When a feature needs a new namespaced operation, resource, capability, validator, or per-target lowering hook, the host registers a static in-process SDK plugin instead. Pass an explicit trusted local plugin with repeatable --sdk-plugin-path <dll-or-retrosharp-package.json>. External DLLs require declarative package metadata; bare DLLs are rejected. --sdk-plugin <id> and project plugin ids have no production registry. Plugin paths work with source and project builds, resolve relative to the current working directory, and are intentionally not stored in manifests. A loaded assembly exposes exactly one public ISdkPluginEntryPoint with a public parameterless constructor. Unknown ids, invalid assemblies, incompatible contracts, and duplicate ids are rejected before compiling. Packaged plugins are checked through metadata and PE references before loading; descriptor coherence is checked after construction. Use --sdk-plugin-diagnostics to include technical exception details. A registered plugin is enforced per target: a target lowers a plugin operation only when it provides a matching lowering hook and grants the operation's required capabilities, otherwise the build fails before lowering with a diagnostic naming the plugin feature and target id. The experimental reference plugin is RetroSharp.Platformer2D (Platformer.GroundProbe()), which lowers on Game Boy and is unsupported on NES. Path-loaded plugins execute arbitrary trusted code inside the compiler process; there is no sandbox, implicit discovery, package resolution, unloading, or stable cross-version binary compatibility promise. See docs/SdkPluginBoundary.md.

Static plugins can also import declarative project resources at compile time. The project names the importer, asset identity, and path explicitly:

{
  "plugins": ["RetroSharp.PushBox"],
  "resources": [
    {
      "name": "levels",
      "importer": "RetroSharp.PushBox.Levels",
      "path": "assets/levels.sok"
    }
  ]
}

Resource paths are resolved relative to the project manifest. The importer runs inside the registered host plugin and contributes deterministic immutable byte tables plus static u8(u8) lookup functions; Game Boy and NES retain ownership of ROM placement and lookup emission. This is not a pre-build script or a runtime parser.

When a library package opts into namespaceMode: "physical", folder names under sourceRoot become compile-time namespaces just like project sources. Root source files commonly act as the package's public facade after import, while helper files under folders can reuse names without colliding with the game or with other packages.

Portability is capability-based, not magic. A shared source file is portable only when each selected target can support the requested scrolling mode, sprite budget, palette slots, audio format, runtime tile writes, and asset shape. See docs/Portable2DSdkV1.md for the current API reference.

Project objectives

  • Keep the language small, explicit, and independent from any one machine.
  • Let the framework make small real 2D games practical without hiding hardware budgets.
  • Keep raw machine access available through target intrinsics.
  • Make Game Boy and NES the first compatibility pair for portable SDK work.
  • Prefer zero-cost ergonomics over managed-runtime features: no heap, GC, RTTI, boxing, virtual dispatch, closures, delegates, or hidden object identity.

Which platforms does it compile for?

The active targets are Game Boy and NES, implemented under src/RetroSharp.GameBoy and src/RetroSharp.NES. A Master System target is in development under src/RetroSharp.MasterSystem; see docs/MasterSystemTarget.md. Runnable samples live under samples/; only samples marked portable-sdk in samples/manifest.json are cross-target SDK evidence.

The retired Z80 compiler path, its exact recovery commit, and the two external forks it used are recorded in docs/history/LegacyZ80Compiler.md.

Installation

RetroSharp is distributed as a .NET tool:

dotnet tool install --global RetroSharp.Tool

Then use it to compile your programs:

retrosharp --target gb --out myprogram.gb myprogram.rs

Build the Game Boy runner from source:

dotnet run --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- \
  --target gb \
  --out games/runner/bin/runner.gb \
  games/runner/runner.retrosharp.json

For games split across several source files, use a RetroSharp project manifest. It is a small JSON file owned by RetroSharp, not an MSBuild project:

{
  "targets": [ "gb", "nes" ],
  "outputs": {
    "gb": "bin/runner.gb",
    "nes": "bin/runner.nes"
  },
  "rootNamespace": "Runner",
  "sourceRoot": "src",
  "namespaceMode": "physical",
  "sources": [
    "src/Program.rs",
    "src/player/State.rs",
    "src/camera/State.rs"
  ],
  "libraryPaths": [
    "lib"
  ],
  "libraries": [
    "RetroSharp.Portable2D"
  ]
}

Then build the project file directly:

dotnet run --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- retrosharp.json

Inspect a Tiled world's deterministic Game Boy or NES WorldPack and target budgets without building or writing a ROM by opting into --world-budget-report:

dotnet run --no-launch-profile --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- \
  --target gb \
  --world-budget-report \
  samples/tiled-cross-target-2d-scroll/cross-target-2d-scroll.tmj

The option accepts one importer-ready .tmx or .tmj, requires --target gb or --target nes, cannot be combined with --out, writes one deterministic retrosharp.world-budget/v1 JSON object to stdout, and writes nothing to stderr on success. Without this option, successful GB/NES stdout and stderr remain unchanged. --no-launch-profile prevents the .NET launcher from prepending its own status line to the CLI's JSON stdout.

The JSON reports source and hardware dimensions, metatile placements and unique metatiles, chunk count, the exact visual/collision stored bytes from the real WorldPack chunk directory, total serialized bytes, generated target tiles, staging RAM, ROM/PRG bytes and required banks, and the bounded VBlank tile/attribute commit shape. Diagnostics are always ordered as addressing, rom-prg, chr-tile-count, staging-ram, and vblank; each carries measured usage, the evaluated limit/profile, and an actionable remedy when it overflows. selectedProfile is deliberately null: the report names current and accepted-future profiles but never selects a banker or mapper.

stagingRam.usedBytes is the pack's actual fixed-slot shape; stagingRam.limitBytes is the accepted WorldPack v1 maximum (554 bytes on GB, 594 on NES), while physicalRamCapacityBytes separately names the machine's total RAM capacity. Addressing permits a 32,768-pixel extent because its last coordinate is 32,767.

NES keeps nes-mapper-0-current separate from the accepted but unimplemented nes-mmc3-tvrom-v1-accepted-future requirements. The report likewise keeps currentPhysicalChrCapacityBytes, acceptedFuturePhysicalChrCapacityBytes, the 8 KiB resident CHR limit, and the 256-entry tile-index budget as separate facts. Game Boy cartridge.romPrgBytesUsed is measured content; allocatedRomBytes stays null, so a padded output ROM length is never misreported as bytes used.

See how full a cartridge already is, how much user-local RAM is left and what fills it, and where the next change will land, by adding --capacity-report to a normal build:

dotnet run --no-launch-profile --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- \
  --target nes \
  --capacity-report \
  --out games/runner/bin/runner.nes \
  games/runner/runner.retrosharp.json

The option takes a single target and cannot be combined with --world-budget-report, which inspects a world without building. It writes one retrosharp.nes-capacity/v1, retrosharp.gb-capacity/v1 or retrosharp.sms-capacity/v1 (Master System and Game Gear, with target sms or gg) JSON object to stdout and does not change a single emitted byte: the ROM is identical with and without it. Fields added to a report are additive within its schema version: retrosharp.nes-capacity/v1 gained locals and the user-locals resource, which can now be the bindingConstraint, and every report gained locals.breakdown, without a version change. There is no built-in diff: to see what a change costs, such as one more local or a dynamic camera follow policy instead of one configured in setup position, compare the reports of the two builds.

Every report leads with bindingConstraint, the scarce resource currently closest to its own capacity, and shares locals and resources. locals measures the user-local reserve that holds a program's locals and globals (222 zero-page bytes on NES, 224 WRAM bytes on Game Boy, 224 RAM bytes on Master System and Game Gear): usedBytes, capacityBytes, headroomBytes, the bytes covered by named variables, and the unnamed rest, such as the seven bytes NES arithmetic loops reserve the first time a program needs them. locals.breakdown groups the same bytes by what they are for: each ActorSlots pool, found by its type, with its slots, bytesPerSlot and the spawnStateBytes the SDK keeps beside it, other SDK state such as a camera follow's, the program's named variables, the temporaries of inlined calls, unnamed scratch, and freeBytes (docs/Portable2DSdkV1.md, "RAM per slot and how many slots fit"). Which function needed a temporary is not attributed. The user-locals resource repeats the same figures, so a program a few locals from full reports it as the binding constraint and warns. A build that needs more than the reserve fails with an error that names user-locals and its capacity, for example NES user-locals is exhausted: the program needs at least 223 bytes, but its capacity is 222 bytes., followed by the allocation that did not fit and the same groups as they stood before it, named in an author's words rather than by the SDK's internal names that the report's sdkState keeps; the CLI places it at the first name it quotes, an ActorSlots pool's declaration when one is listed. With --capacity-report the CLI then prints no report and says that the build failed. The Master System and Game Gear report has selectedProfile (rom-only or sega-mapper), locals and the user-locals resource only, so its bindingConstraint says nothing about the ROM.

The Game Boy report adds program, the linker's placement of the contiguous program from the end of the cartridge header: its layout (rom-only or mbc1), bytes, the bytes the layout holds and the room left, its per-bank occupancy, the headroom in its last bank, and, once it spans switchable banks, the fixed-resident helper bytes that must stay in bank 0. banks lists every cartridge bank's measured occupancy and owners. Its resources are user-locals, rom-only-program on a ROM-only cartridge, and on MBC1 fixed-bank-helpers and mbc1-banks (banks used of the 32 the current lowering addresses). Running out of either of the last two fails the build with the same kind of error, naming fixed-bank-helpers or mbc1-banks and its capacity. Per-frame CPU work stays in --build-report-out.

The NES report also names the selected profile and PRG/CHR size; the fixed PRG region's used bytes, remaining headroom and capacity; the banked program's phase-to-bank map with per-bank occupancy and remaining R6 headroom; the bytes attributed to fixed veneers, pinned R7, boot R7 and resident CHR; the top user-function duplication holders; and per-frame CPU work for each named window. resources repeats user locals, fixed PRG, whole R6, the hot phase's per-bank cap, and the CPU windows with the same vocabulary: name, unit, use, capacity, headroom, binding status, next unit cost, and named remedy. Region capacity is used plus headroom as the build measured it, not a re-derived board constant, and bankedProgram is null for a program that is entirely fixed-resident. Shared SDK subroutines and outlined user functions are counted with their call sites rather than sized, because the build measures no byte total for either.

Two figures are deliberately labelled rather than trusted. duplication is a lower bound: pure functions the compiler answered from a generated ROM table never reach the user-function path, so it is not total recoverable headroom. callsPerFrame is static: it is not path sensitive and does not multiply loop iterations, so it is a lower bound where repeatsPerFrame is set and an upper bound over mutually exclusive branches otherwise. cpuWork is an observation per named window — frame is the whole NTSC frame, video-safe is the VBlank window. The video-safe figure is the full cost the NES frame plan already imposes, including NMI entry reserve, frame-boundary bookkeeping, packed column commit, and retained-OAM publication. Other incomplete CPU windows still mean uncalibrated work exists outside the known figure.

When any reported resource's headroom falls below five per cent, including user locals, the hot phase's per-bank cap or a CPU window, the report adds a near-cliff warning and the CLI repeats it on stderr. That is a warning, never a gate: spilling into R6 and escalating the board are designed behaviours where allowed, so the build still succeeds and nothing here can fail it.

Project paths are resolved relative to the JSON file. --target, --out, and additional --lib-path options still work as command-line overrides. Use project sources for code that belongs to the game itself; use retrosharp-library.json packages when code should behave like an external, imported dependency. libraryPaths tells the CLI where to discover local packages; libraries names the package import paths that this project loads. Source-level import still works as the explicit transitional form, but C#-style project code normally declares loaded libraries in the manifest and uses using only for namespace lookup inside source files.

When namespaceMode is physical, RetroSharp derives zero-cost source namespaces from the path under sourceRoot: src/player/State.rs belongs to Runner.Player, while src/camera/State.rs belongs to Runner.Camera. The compiler lowers those names to unique internal symbols, so folders can contain same-named helper classes such as Rules; source can refer to static constants with path-qualified names like Player.Rules.Start.

Export a GBS subsong into a Game Boy APU trace when you want faithful playback of the original register writes:

dotnet run --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- \
  gbs-to-gbapu \
  --in path/to/theme.gbs \
  --subsong 1 \
  --seconds 120 \
  --out path/to/theme.gbapu

This writes a compact binary .gbapu (use --emit-json for a JSON debug view, or gbapu-dump <file> to print the register writes). The loop is auto-detected and the capture is trimmed to one loop body (--no-auto-loop/--loop-cycle override). Use the result directly with Music.Asset(...). The source keeps cycle deltas for every supported APU register write; the ROM compiler maps them onto DMG VBlank frames, removes redundant non-trigger writes, deduplicates repeated frame groups into a pool, and stores full Wave RAM uploads as compact block commands for Audio.Update() playback.

The helper requires gbsplay on PATH unless --gbsplay <path> is supplied. GBS files are not loaded directly by Music.Asset(...). See docs/GameBoyApuTraceFormat.md for the format shape, runtime use, limitations, and future alternatives.

Build the first cross-target camera sample for both cartridge targets:

dotnet run --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- \
  --target gb \
  --out /tmp/cross-camera.gb \
  samples/cross-target-camera/camera.rs

dotnet run --project src/RetroSharp.Cli/RetroSharp.Cli.csproj -- \
  --target nes \
  --out /tmp/cross-camera.nes \
  samples/cross-target-camera/camera.rs

The sample sources have been migrated to the current language surface: symbolic const values and enums for static data, aliases where they clarify byte-backed values, let for immutable frame-local values, inline/pure helper contracts, SDK dot-calls, restricted static classes and receiver methods where they clarify ownership, switch expressions, pipelines, while (true) for infinite runtime loops, and compound mutation syntax where it maps directly to the old explicit assignments.

AI agent orientation

Repository-specific instructions live in AGENTS.md. For tasks that need architecture, acceptance, or ownership context, docs/AgentContext.md routes to the relevant owner document. Completed roadmaps are historical context, not a startup bundle. llms.txt is the compact documentation index.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.0.1254 106 10/30/2026
0.0.1246 99 10/29/2026
0.0.1242 104 10/29/2026
0.0.1240 100 10/29/2026
0.0.1231 106 10/28/2026
0.0.902 121 10/13/2026
0.0.777 110 10/5/2026
0.0.769 111 10/5/2026
0.0.662 121 9/27/2026
0.0.661 119 9/24/2026
0.0.660 115 9/24/2026
0.0.659 116 9/24/2026
0.0.658 115 9/24/2026
0.0.657 112 9/24/2026
0.0.656 104 9/24/2026
0.0.655 122 9/24/2026
0.0.654 112 9/23/2026
0.0.653 119 9/23/2026
0.0.652 120 9/23/2026
0.0.651 118 9/23/2026
Loading failed

# Changelog 0.0.1231

## Changes since v0.0.902

- Docs: drop the -m:1 left over from the old gate (#2340) (#2341) (`65778d8e`)
- runner-lift: remove stray platform tiles from the overworld map (`a4d2808e`)
- Retire three duplicate paths from roadmap #2237: world pack assembly, byte VerticalScrollMax, World.Load reader (#2332) (#2333) (`ce75a3b3`)
- runner-lift: doors lead to a cave and back (#2234) (#2331) (`0247913c`)
- Tests: the implementer runs publish's architecture check before handing back (#2328) (#2329) (`2cbd4f56`)
- runner-lift: Mario climbs the vine (#2236) (#2327) (`d8e2f6bd`)
- Tiled: a trigger names the object it leads to, instead of coordinates (#2323) (#2326) (`c7b96b9d`)
- Game Boy: the camera's column plane holds each activated world at its own height (#2315) (#2324) (`abd59c10`)
- NES: one flags read for a word column, for World.Collect and World.TileKindAt (#2320) (#2321) (`6a211ddb`)
- Game Boy and NES: World.TileKindAt over worlds wider than 256 columns (#2318) (#2319) (`399642e6`)
- Tests: one grouping of the cost marks and one discovery of Python modules (#2282) (#2317) (`0c2795ef`)
- Game Boy: read the collision of activated worlds from the compiled plane (#2316) (`9421e533`)
- Tests: suite.py refuses a filter that selects a whole project (#2313) (#2314) (`88b0f8b3`)
- NES: animate the background of streamed worlds that activate (#2292) (#2311) (`a9f32bb4`)
- Master System and Game Gear: animate the tiles of each activated world (#2269) (#2309) (`450316fd`)
- runner-lift: a second enemy, the Ninji, in the same pool as the goombas (#2235) (#2308) (`5c8a4b72`)
- NES: streamed columns carry up to 60 rows for any world width (#2289) (#2306) (`b85c2154`)
- Master System and Game Gear: Camera.Follow over an activated world taller than 49 rows (#2277) (#2305) (`17d28f92`)
- runner: Mario collects the coins (#2295) (#2304) (`1b924c91`)
- NES: activate streamed worlds of different shapes (#2302) (`3987a208`)
- World.Collect: Game Boy and NES share the check of its plan (#2298) (#2300) (`fd30d1e9`)
- Game Boy: World.Collect in worlds wider than 256 columns (#2294) (#2299) (`b17ee4bf`)
- runner: every coin is the animated coin, and tile kinds work under physical namespaces (#2233) (#2296) (`35c0d704`)
- Master System and Game Gear: collect and change tiles in worlds taller than the screen (#2232) (#2291) (`6bd4e3e9`)
- Game Boy: the world pack's directory leaves the fixed bank, so runner-lift's exterior and cave fit (#2284) (#2288) (`e3113b86`)
- Master System and Game Gear activate worlds of different sizes (#2271) (#2285) (`c71fa1ea`)
- NES: a world stores the CHR pages of the frame combinations its timers show (#2268) (#2283) (`f0dc79a4`)
- Tests: three slow tests check the same thing with fewer compilations (#2275) (#2281) (`61eca6b7`)
- Tests: outside the gate, a test run is filtered (#2274) (#2280) (`e0a88927`)
- NES: collectibles and World.SetTile in worlds the camera scrolls vertically (#1520) (#2279) (`c3214a4f`)
- SDK: Camera.Follow follows a target down a Game Boy world taller than 49 rows (#2270) (#2278) (`c3a402e3`)
- Workflow: opus implementer for changes to the language's semantics (#2272) (#2273) (`e177d310`)
- Compiler: one integer range table for LoopEntry and the constant store check (#2264) (#2266) (`5b3d3681`)
- Compiler: a constant store checks the exact range of its type (#2239) (#2263) (`622854b6`)
- Language: a method of a class names its constants without qualifying them (#2255) (#2262) (`f11c313b`)
- Compiler: one type scope for LetTypeInference and BoolConversionValidator (#2257) (#2258) (`e4232345`)
- Language: a class constant names its sibling members without qualifying them (#2210) (#2256) (`3424e35d`)
- Language: a variable named like a static class hides the class's members in its scope (#2213) (#2254) (`89836c70`)
- SDK: a named constant in a follow-profile condition selects the static camera policy (#2227) (#2250) (`658cd495`)
- Language: retire the omitted-default condition that ReadInPlace already covers (#2243) (#2246) (`089034e3`)
- Compiler: retire the unreachable multi-candidate argument path in BoolConversionValidator (#2241) (#2245) (`81d0424c`)
- Language: a helper that writes its by-value parameter leaves the caller's variable alone (#2031) (#2244) (`f2a4836c`)
- Compiler: check a constant against its storage type at every store (#2217) (#2242) (`21823fa0`)
- Language core: retire the duplicates and uncalled code left by #1372 (#2230) (#2231) (`cba47718`)
- snow-bros-jr: enemy walk clip and shot initializers (#2202) (#2229) (`f1dca67e`)
- snow-bros-jr: readable without new forms (#1937) (#2228) (`c15b35fb`)
- Games: runner and runner-lift share Mario's code and name their constants (#1938) (#2226) (`5f2da9f4`)
- SDK: one check for each animation clip bound (#2220) (#2225) (`2067e408`)
- Workflow: the batch picker skips issues that share files with the running lane (#2221) (#2224) (`67754cc6`)
- Workflow: redesign a mechanism that review keeps finding defects in (#2222) (#2223) (`b36eaebe`)
- SDK: ping-pong and one-pass animation clips (#1934) (#2219) (`6f8e4b05`)
- Compiler: one lowering for struct initializers in declarations (#2214) (#2218) (`71aac375`)
- Language: compound operators *=, /=, %=, <<= and >>= (#1371) (#2216) (`0597accf`)
- Language: assign a typed struct initializer to a variable, a field, an element or a union case (#1369) (#2215) (`2b2ac5a7`)
- push-box: derive board origin and camera from Viewport and the board size (#1370) (#2212) (`e79b67a2`)
- push-box: Board.Bit as a const array (#1931) (#2209) (`189db126`)
- Language: ref locals as compile-time aliases of an element or a field (#1368) (#2208) (`6480cac3`)
- Workflow: agents load the RetroSharp language server for .rs sources (#2206) (#2207) (`32030836`)
- SDK: one CollisionFlag enum, replacing the copies in games and samples (#1936) (#2205) (`f0602047`)
- SDK: one Cadence for acting every N steps, replacing ShooterCadence (#1935) (#2204) (`05ad1387`)
- Compiler: retire the unused conditions and parameter left by roadmap #2175 (#2199) (#2200) (`31998b98`)
- Resources: reject the statement form and migrate the tests that used it (#2194) (#2198) (`0577fcbb`)
- Workflow: one gate per issue, and a new reviewer after each round of fixes (#2196) (#2197) (`08ecdcaa`)
- Resources: migrate samples and runner diagnostics to typed declarations (#1421) (#2195) (`f313e524`)
- Game Boy: retire UGE music input and its row playback (#1002) (#2193) (`a3bb35c3`)
- Game Boy: retire the raw intrinsics no program uses (#2179) (#2192) (`ffd96c75`)
- Tests: one helper for the repository root, CRC32 and PNG (#2180) (#2191) (`52c0d997`)
- Master System and Game Gear: retire the camera's row edge lists and edges taking turns (#2178) (#2190) (`e9a77169`)
- Workflow: a compiler crash or a silent miscompile is filed as a follow-up issue (#2187) (#2189) (`ab8be263`)
- NES: the frozen camera modes are retired (raw rows over 60 rows, columns that follow the camera, staged columns) (#2176) (#2188) (`3862cb18`)
- NES: retire the activation of streamed worlds (#2177) (#2186) (`a0652ca9`)
- Z80: word sums and differences are evaluated in DE instead of the word scratch (#1476) (#2185) (`619a0398`)
- Workflow: batch waits end before the Bash timeout and do not pile up (#2183) (#2184) (`ff93bc82`)
- Game Boy: the hottest runtime bytes live in high RAM and the stack moves to the top of WRAM (#1478) (#2182) (`b497db35`)
- Z80: direct jumps are JR when their target is in reach, and a rebuild promotes the others to JP (#1477) (#2181) (`c765c8fd`)
- Camera: retire the leftovers of roadmap #2087 (an unreachable Game Boy step branch, the restart rule written three times, unread StreamMap fields) (#2173) (#2174) (`6098ca0f`)
- NES: world activation writes Activating only with animations, and the docs say when the caches are cleared (#2122) (#2172) (`f21d6b61`)
- NES: retire the unused negative-offset path of the packed wide source column (#2167) (#2171) (`bc26a05d`)
- Camera plan: the Master System rings, the NES unpacked four-screen row modulo and the Game Boy streamed row width read the plan (#1798) (#2170) (`a5d76d6b`)
- Master System and Game Gear: a run-time Camera.Init redraws the world at its start (#2125) (#2169) (`6366da60`)
- NES: retire the before-the-world column round and RoundBias, and test the left trim (#2130) (#2168) (`b57092b1`)
- NES: retire the unused camera row staging and fold the two nametable seed loops (#2141) (#2166) (`318d6659`)
- Game Boy: the row setup over an activated world uses camera_active_world_column_from (#2149) (#2165) (`e45aac79`)
- Packed camera: the ring and edge literals read SdkCameraStreamPlan (#2146) (#2164) (`f76d4e0d`)
- SDK 2D: retire the raw map_stream_column and map_stream_row calls (#1420) (#2163) (`ead5d4ee`)
- Game Boy: the packed camera without diagonal goes through the diagonal path (#2157) (#2162) (`ea217ac4`)
- Master System and Game Gear: Camera.Apply commits at the frame wait, with no immediate form (#2158) (#2161) (`619aaf39`)
- NES camera: the short Y tracking goes through the general path (#2152) (#2160) (`df86d561`)
- Game Boy: the column window merges into the unpacked camera's queue (#2153) (#2159) (`9b936a01`)
- Packed camera: one stop rule and one seeded-rows rule in the plan on Game Boy and NES (#2144) (#2156) (`47739181`)
- Packed camera: the patch-or-enqueue choice of SetTile and Collect is read from the plan on Game Boy and NES (#2148) (#2155) (`1d6f407b`)
- A run-time Camera.Init redraws the world at its start on Game Boy and NES (#2143) (#2154) (`b09f583e`)
- Game Boy: the #1709 test checks that the activation turns the LCD off only in vertical blank (#2121) (#2150) (`20db5d35`)
- ADR 0018: the packed camera's policy is written once for Game Boy and NES, with its census and migration order (#2138) (#2147) (`6038ff4b`)
- Workflow: a follow-up issue only for a regression, a safety defect or what a game shows (#2140) (#2142) (`575fe38a`)
- NES: a streamed world narrower than 272 px redraws World.Collect and World.SetTile at every copy (#2129) (#2139) (`c6e3eb1a`)
- NES: a camera over a world taller than its nametables writes each row and column ahead of the screen (#1703) (#2137) (`4cf8f001`)
- NES: a world that repeats vertically shows its rows again below it without four-screen nametables (#1773) (#2133) (`8f271e23`)
- NES: a streamed world's camera counts round past the active world's edge (#1909) (#2123) (`882ce34f`)
- Workflow: batch sessions and the reviewer run on opus, whatever the user's default model (#2115) (#2117) (`0356c565`)
- NES: the tile animation pauses while a world is pending (#2114) (`5e44509b`)
- NES: a world activation clears the raw cell caches when the new world is mapped (#1729) (#2113) (`31a91336`)
- NES: the packed camera column holds the world's rows and the empty tile below a short world (#1363) (#2111) (`d9a160a6`)
- batch: a merged session without structured output counts as merged, and a session that never started is resumed once (#2109) (`6037c325`)
- Game Boy: the cursor path drops its empty hooks and one of two equal scanline constants (#2107) (#2108) (`feded62c`)
- Game Boy: la lista de fila de la cámara sin empaquetar dice qué garantiza (#2104) (#2106) (`25cee84f`)
- Game Boy: a streamed row's list holds a full row of writes (#2102) (#2105) (`b94161de`)
- Game Boy: a streamed row's list holds every cell of a diagonal span across a wide world's edge (#1915) (#2103) (`3934c2db`)
- Game Boy: the camera's vertical scroll waits for a row the vertical blank could not write (#1898) (#2100) (`4699161b`)
- Game Boy: the packed camera's diagonal pan across an active world's edge advances every frame (#1925) (#2099) (`9ee3809b`)
- Game Boy: the world activation advance waits for vertical blank (#2098) (`bebe83dd`)
- Game Boy: Camera.VerticalScrollMax counts the band's start row (#1858) (#2097) (`3585a803`)
- Game Boy: the packed camera over a world that starts below row 0 shows the world where it is and the empty tile above it (#2093) (#2096) (`a0a04c9b`)
- Game Boy: the unpacked camera over a world that starts below row 0 shows the world where it is and the empty tile around it (#1900) (#2094) (`c789c563`)
- Game Boy: the packed camera shows the empty tile below a world lower than the screen (#2047) (#2092) (`2d86f6be`)
- Game Boy: a cell World.SetTile changes while the packed camera keeps moving shows within two frames, a test guards it (#1825) (#2091) (`2ae9ab1e`)
- Game Boy: a commit too late for a yielded vertical blank leaves no credit, so a busy game never walks 8 px at (4,4) (#2051) (#2090) (`a0fda126`)
- Game Boy: one routine shares each vertical blank, so animated tiles run at (4,4) (ADR 0017 D1) (#2055) (#2088) (`f7944da3`)
- Game Boy: a test bounds each VBlank price by the measured cycles (ADR 0017 D3) (#2054) (#2086) (`2a06256d`)
- Game Boy: animated tiles upload tile by tile through a cursor (ADR 0017 D2) (#2081) (#2085) (`58f2cdbe`)
- Master System: animated tiles upload whole roots by room, in turn (ADR 0017 D2) (#2053) (#2084) (`1aa1ca37`)
- Runtime: each memory map validates only in its static constructor; drop unused ShownSpriteColors (#2079) (#2080) (`bcbb40ba`)
- Language: expression dispatch lives once for NES and Z80 through a per-CPU port (#2073) (#2078) (`873ad55f`)
- Workflow: batch lanes wait for their session in the foreground (#2076) (#2077) (`e570ade7`)
- Language: statements and loops live once for NES and Z80 through a per-CPU port (#2072) (#2075) (`d5dfe2d8`)
- Language: condition jumps live once for NES and Z80 through a per-CPU port (#1952) (#2074) (`ecf4b984`)
- SDK: NES world AABB walk emits its hit step through EmitAabbHit (#2069) (#2071) (`708def12`)
- SDK: AABB tiles and hit-top share one probe walk per coordinate space on Game Boy and NES (#1989) (#2070) (`70a8e78d`)
- Workflow: batch lanes launch with the directory written out, not in a shell variable (#2067) (#2068) (`cb29843f`)
- Tests: collision runtime lowering rules run once for Game Boy, NES and Master System (#2000) (#2066) (`ba77c557`)
- Tests: Master System and Game Gear run the shared sprite lowering cases and drop their copies (#2062) (#2065) (`c1d0832a`)
- Tests: sprite tables read once through PublishedSprites on Game Boy, NES, Master System and Game Gear (#1999) (#2064) (`b065e850`)
- Tools: a Game Boy frame-budget step also ends where a packed commit enters the vertical blank without polling (#2052) (#2063) (`76021775`)
- Tests: camera follow, registered enemy, staircase and loading snapshot once for Game Boy and NES (#1998) (#2061) (`9fdeaf64`)
- Workflow: batch publishes, launches and picks without fragile steps, and /roadmap leaves two lanes to it (#2059) (#2060) (`57066c35`)
- Workflow: batch starts each session with a command one permission rule covers (#2058) (`88038664`)
- Tests: ManagedAudio and Portable2DSpriteAssetBaseline diagnostics once for Game Boy and NES (#1997) (#2056) (`6979bc37`)
- Language: a parameter or local named like a const holds its argument (#2044) (#2046) (`5ef7d689`)
- Language: a parameter named like a const reads its argument (#2037) (#2045) (`bf23c542`)
- Workflow: batch runs each issue's cycle as its own headless session (#2043) (`77e48e09`)
- Language: a ?: of an i8 and a u8 holds each arm's own value (#2019) (#2041) (`17a93531`)
- Workflow: a batch run that schedules issues through /issue in up to two lanes (#2040) (`f81e4c34`)
- Language: a let of an i8, or of byte arithmetic over one, stays an i8 (#2018) (#2038) (`fa1ee48f`)
- Language: a u16 compared with a negative constant has a fixed answer (#2025) (#2036) (`359908de`)
- Workflow: the implementer proves each acceptance criterion and waits for its background runs (#2035) (`4ce97b47`)
- Language: an argument the helper reads after something that can change it is the value at the call (#1910) (#2033) (`267c9236`)
- Language: a comparison with a constant that fits no integer type is a frontend error (#1964) (#2030) (`6557b503`)
- Workflow: the reviewer probes each changed path once and re-runs a mutation only when in doubt (#2028) (#2029) (`ad5dc56d`)
- NES: a wide constant in byte arithmetic takes its low byte, as on the Z80 targets (#1960) (#2027) (`7224405e`)
- Compare a u8 with a negative constant as signed 16-bit values (#2009) (#2026) (`716c25f2`)
- Language: mixed words, byte masks and selections mean the same in place as through a let (#1961) (#2023) (`323c8fcd`)
- Compare signed byte arithmetic as signed, and mixed-sign byte comparisons as the bytes they give (#2020) (`5e6074e0`)
- Retire recursion guards the frontend makes unreachable and drop the target from call-binding errors (#2017) (`a89fb4d2`)
- Language core: lvalue and overlap analysis lives once in LocalStorageAccess (#1972) (#2015) (`f2d19ba4`)
- Run Master System's projectile scenarios through the shared ActorProjectile class (#2014) (`9f70525a`)
- Workflow: /issue launches opus implementers only for new lowerings, banks, interrupts or calling conventions (#2012) (#2013) (`134b3876`)
- Tests: Master System runs the shared language tests and retires its copies (#1757) (#2011) (`22d0f004`)
- Compiler: an i8 compared with an unsigned byte above 127 compares as i16 (#1947) (#2010) (`ee0fe39c`)
- Workflow: slim /issue and /roadmap to the project's contract; sonnet for implementer and scout (#2006) (#2007) (`9efebd02`)
- Tests: ActorProjectile scenarios run once over Game Boy and NES (#1996) (#2004) (`22342276`)
- Tests: capacity report and runtime ABI projection run once over the targets (#1995) (#2002) (`f157bb61`)
- Tests: SDK frame input, plugin value lowering and the merged-helper twins run once over the targets (#1981) (#2001) (`cf94dd46`)
- Tests: language functions run once over Game Boy and NES (#1954) (#1994) (`86264213`)
- Language core: language errors NES and Z80 repeat come from one frontend check (#1951) (#1992) (`56e41816`)
- Language core: no per-target one-line wrappers, and one cast refusal point (#1987) (#1988) (`5053d0c0`)
- Language core: the rest of the constant reader and operator classifiers once (#1983) (#1986) (`ba6275e1`)
- Tests: SDK sprite lowering, edge pairs and the follow rebase run once over the targets (#1978) (#1985) (`b446f849`)
- Language core: transient pops that keep A once for NES and Z80 (#1980) (#1984) (`3f6e9a85`)
- Language core: one constant reader and one set of operator classifiers (#1950) (#1982) (`cfa6f2da`)
- Language core: the transient release marker and the capture expression once (#1976) (#1979) (`143fd3c2`)
- Tests: SDK and managed-game scenarios run once over the targets (#1957) (#1977) (`e94667fc`)
- Language core: transient and element-index captures once for NES and Z80 (#1971) (#1975) (`77e48ea0`)
- One runtime memory map and one runtime ABI projection for NES, Game Boy and Master System (#1956) (#1974) (`c343a06d`)
- Language core: local scopes and SDK storage once for NES and Z80 (#1949) (#1973) (`30c1ceaa`)
- Language core: one declaration path for a top-level local and a nested field (#1967) (#1970) (`f6a4f4bc`)
- Camera tests: strict cadence up to 4 px a frame on every machine, safety and image above it (#1955) (#1969) (`c5a6ba8f`)
- Language core: one declaration and reservation rule for structs and arrays on NES and Z80 (#1948) (#1968) (`a0115b2f`)
- WorldPack: one per-cell accessor for the Game Boy and NES runtimes and the tests (#1962) (#1966) (`802ebf6a`)
- Language: byte arithmetic with a wide constant means the same in place as through a let (#1903) (#1965) (`c3d3c717`)
- Retire production code nothing calls, and the unused Parser.Model project (#1953) (#1963) (`8761b92b`)
- Compiler: one operand type rule for the Z80 and NES backends (#1942) (#1945) (`4b93ab58`)
- Compiler: an array element read at a runtime index keeps its width and sign (#1940) (#1944) (`c03a5a27`)
- Language: an enum with a value over 255 is stored in 16 bits (#1890) (#1941) (`6c67db0b`)
- Language: const arrays in ROM, and every generated ROM table lookup keeps its index (#1367) (#1939) (`b9f34129`)
- Language: an inline helper's value computed by statements can go wherever its statement evaluates it once (#1366) (#1929) (`d93101cb`)
- ADR 0017: the Game Boy packed camera shares each vertical blank in one order, within its 4-pixel guarantee (#1919) (#1928) (`da5b814e`)
- Tools: a hook blocks an unbounded Read of a large file or a saved tool output (#1921) (#1924) (`cc54307d`)
- GB camera walks the active world: screen and queries agree past its edges (#1786, #1510) (#1923) (`bacbb310`)
- Workflow: a roadmap does next the cleanups it finds, and two lanes share no file (#1920) (#1922) (`e0afde7a`)
- Language: an argument with effects runs once and in source order, through one shared argument-capture rewriter (#1918) (`7bb21730`)
- GB packed camera: both axes every frame at top speed, no stopped frames (#1714) (#1914) (`962ed1e1`)
- Build report: list the compiler's fixed storage with function, bytes, address, position and reason (#1912) (`07fb60f6`)
- Language: let infers the smallest type its value fits and never truncates a wide constant (#1867) (#1908) (`e1a94749`)
- Language: the frontend reports cost warnings, placed at their construct by build, check and the editor (#1869) (#1906) (`815ed452`)
- Camera: streamed columns cover every row the screen shows under a short band (#1800) (#1901) (`cd465786`)
- Language: a value call standing as a statement keeps the effects of what it returns (#1511) (#1899) (`4c4a7700`)
- Philosophy: the language takes C# as its model, not as a copy (#1883) (#1884) (`a180b4b8`)
- Capture: an argument passed to a word parameter is computed at the parameter's width (#1523) (#1868) (`10c32744`)
- NES: a byte context reads the low byte of every constant, also of a folded inline call (#1465) (#1866) (`8de61b35`)
- Camera: one order-independent set-up rule; boot sets the camera up on every target (#1859) (#1863) (`f33f5636`)
- Hook: reject pgrep -f and pkill -f, which match their own shell (#1861) (#1862) (`1c66b74d`)
- Camera set-up: camera_init and VerticalScrollMax rules in the plan, one text on every target (#1850) (#1860) (`0b2e528e`)
- Collision: the box query refusals come from SdkAabbQueryPlan, one text on every target (#1849) (#1857) (`4b72fba2`)
- Camera step: the set-position roots on the shared emitter, family migrated in the brake (#1848) (#1853) (`ff230abd`)
- Ledger: the real status of collision, frame boundary, camera and tilemap writes (#1846) (#1852) (`5f5befc9`)
- Brake: the walk stops at any migrated family's primitives and at the named NES expression-compiler entry points (#1845) (#1847) (`01356fa4`)
- Join duplicates the roadmap left: one runtime Tilemap.Set rule and check, GB edge descriptions, unrolled-loop arithmetic (#1841) (#1844) (`b1756328`)
- Retire what the roadmap left unused, and small duplicates (#1840) (#1843) (`b5becb74`)
- Benchmarks: retire the temporary tolerance, baselines at the measured cost (#1640) (#1839) (`6b7fce46`)
- Tile animation: timers once in the plan, one Z80 emitter, the upload per machine (#1652) (#1838) (`3d8e9bfc`)
- Frame wait: SMS and NES copies call the target's one wait helper (#1811) (#1837) (`68ba635b`)
- Static setup: one walk for GB and NES, a target sink each (#1653) (#1835) (`98994cd7`)
- Workflow: fewer implementer turns, mutation checks from one script (#1832) (#1834) (`b93c2a84`)
- Camera: pending edges take turns as a plan rule on three targets (#1791) (#1831) (`dc5b4214`)
- Input: button state once in the plan, one Z80 emitter, the poll per machine (#1651) (#1829) (`bf6cdbd2`)
- Workflow: fixes never go back to the implementer that wrote the change (#1827) (#1828) (`44be973b`)
- Runtime Tilemap.Set is written at the frame boundary on every target (#1806) (#1826) (`445f2c21`)
- Game Boy: a loop from the end of the fixed region into the first switchable bank is kept in one bank (#1816) (#1824) (`54bb6ada`)
- Camera: one Z80 walk for GB and SMS/GG, signed-order direction on three targets (#1790) (#1820) (`20ea8af3`)
- Game Boy: one emitter for every frame wait, with master's commit-VBlank reuse per path (#1802) (#1817) (`62871976`)
- Frame boundary: one moment for the step counter (#1803) (#1815) (`0dbfd04b`)
- Workflow: prune the agents' rules to what review and tools do not already catch (#1814) (`eb7fe4d9`)
- A program that never waits drains its redraw queue inside a vertical blank on all targets: its camera application waits for the next one (#1807) (#1812) (`8a31b32d`)
- Z80 family: one emitter steps the camera cursors and queues the edges on GB and SMS/GG; GB refuses a vertically scrolled world over 256 rows (#1789) (#1810) (`2a5b6757`)
- Frame boundary: one layer-1 plan for the wait, the publication and the video jobs (#1649) (#1809) (`cb093fe9`)
- Game Boy: the packed camera admits a commit only while its bounded work fits the vertical blank (#1512) (#1805) (`b0d1c51f`)
- Sprites: explicit layout follows the one piece-offset rule on four targets, checked over the orientations each drawn sprite is drawn in (#1775) (#1804) (`63362681`)
- Camera: the cursor rules come from the plan's per-axis ring, source and edges; a camera over a world that streams rows reveals the screen's bottom row (#1788) (#1801) (`252eb26c`)
- Game Boy: World.TileFlagsAt and the tile-kind query share one cell-flag read through the query plan (#1772) (#1797) (`49261950`)
- SDK brake: World.Collect migrated under R2-R6 (#1779) (#1795) (`6d236926`)
- Camera: the walk cap, the direction rule and the NES band clamp come from one step plan; the NES byte walk no longer walks the wrong way to a far target (#1650) (#1792) (`07be0aae`)
- World collision: the plan states the band row, the repeating rows and the active world's bounds and columns (#1771) (#1787) (`c29a132e`)
- Z80 family: one enqueue for the redraw queue on GB and SMS/GG, lists first, then residency, then the queue (#1778) (#1783) (`57803b46`)
- World collision: one pixel-to-tile rule in the plan for all three targets (#1770) (#1781) (`c0f785ff`)
- SDK brake: SetTile is migrated, R6 checks the plan's span in each layout, and R2 reads every method the roots reach (#1699) (#1780) (`d1d969e3`)
- World collision: box queries read collision worlds with the flags of SetTile's tiles, built once by the plan (#1769) (#1777) (`f9418cf6`)
- Sprites: one band routine for the Z80 family; the Sprites family is migrated and the brake checks R2-R5 (#1687) (#1776) (`bd5c2973`)
- World collision: the plan classifies each box query's columns, rows and answer for all three targets (#1648) (#1774) (`f169a2f6`)
- Game Boy: adding an activated world's origin keeps the local column's high byte (#1723) (#1766) (`f04bd916`)
- Word comparisons with a constant that fits no byte compare both bytes on every target (#1756) (#1764) (`6c646e36`)
- Redraw queue: the stale-write rule is plan data read by the Z80 family and the NES (#1759) (#1763) (`8ff5c597`)
- Z80 family: one redraw queue for collectibles and tile changes, with one full-queue rule and one clear (#1698) (#1761) (`bf1ab71c`)
- Game Boy lifetimes fixture: main.rs draws the Marker once so its manifest compiles again (#1303) (#1760) (`c86d795d`)
- Tests: language tests copied between Game Boy and NES are written once and run on each machine (#1748) (#1758) (`f6c903cd`)
- Workflow: split multi-part issues before implementing; review nits open no new round (#1754) (#1755) (`517e77f1`)
- Master System and Game Gear redraw rows through the shared edge lists (#1728) (#1753) (`1320bec5`)
- Production clones: one NES palette table, Game Boy sprite sheets read PNGs through Core, one portable intrinsic list (#1749) (#1751) (`4ef2e2fa`)
- Sprites: Game Boy draws pieces through the shared Z80-family emitter (#1725) (#1747) (`33b8bca2`)
- Master System and Game Gear: one vertical blank budget for edges, redraws, the write queue, sprite streaming and tile animation (#1514) (#1746) (`659ba080`)
- Game Boy: animated tiles fit the vertical blank beside the sprites, or are uploaded one root per blank (#1707) (#1745) (`a8cbbbd4`)
- NES: a late step still shows its picture; every frame boundary waits for a fresh vertical blank (#1655) (#1744) (`db3dd15c`)
- Tests: the last tests that read games/ move to frozen copies and fixtures (#1736) (#1743) (`5153add6`)
- Tests: falling-blocks, push-box, ship and the guards stop reading games/ (#1734) (#1742) (`d07f8423`)
- Edge lists: one Z80-family core for columns and rows; Game Boy streams rows through it (#1697) (#1741) (`13f50efd`)
- Sprites: Master System and Game Gear draw pieces through one Z80-family emitter (#1686) (#1740) (`c0aea4da`)
- Tests: runner and runner-lift tests run on the frozen copies or are retired (#1733) (#1738) (`4a4d1b7a`)
- Tests: snow-bros-jr tests run on the frozen copy or are retired (#1732) (#1737) (`0328aecd`)
- Workflow: no test reads games/; game tests run on the frozen copies (#1731) (#1735) (`ef4ba1dd`)
- NES: retire run-length decoding of world packs; reject RLE planes at build time (#1719) (#1730) (`cbd2fa5d`)
- Master System and Game Gear redraw columns through the shared edge lists (#1696) (#1727) (`e405b799`)
- NES: the first frame shown is where the camera is (#1713) (#1726) (`27d455b7`)
- Banking: a jump from the fixed bank never reaches a switchable bank unselected; ActorSlots.Draw drops its 48 px skip (#1718) (#1724) (`ecc8b13a`)
- Retire camera lifecycle counters, the NES pack probe and test-only hooks; camera acceptance reads what the screen shows (#1682) (#1722) (`210ac502`)
- Sprites: one clipping rule hides a piece off the screen and never wraps it (#1685) (#1721) (`8035884b`)
- Column edge lists: one Z80-family emission, Game Boy supplies primitives (#1695) (#1716) (`500024cf`)
- Tests: runner and runner-lift at their camera's top speed on NES, Game Boy and Master System (#1676) (#1715) (`cb064a5d`)
- Sprites: one rule hides what was not drawn since the last publication (#1712) (`16eca22a`)
- World activation: SetTile and Collect are refused while it is pending, on every target (#1694) (#1711) (`5eb651a7`)
- NES column lead, cost, cadence, activation and lowering tests: check what the screen shows, not the internal path (#1681) (#1710) (`6e4f2c5a`)
- Redraw refusals: the plans own every SetTile and collectible check (#1647) (#1708) (`a5ef513f`)
- NES pack reader, large world and whole-column tests: check what the screen shows, not the internal path (#1680) (#1706) (`4fa815c7`)
- Workflow: look up existing test helpers before writing one (#1702) (#1705) (`4cd8f2c1`)
- Camera follow tests: read the camera the screen shows, not the internal follow state (#1688) (#1700) (`afc62b9c`)
- Collectibles: one Z80-family emission of the per-cell routines (#1669) (#1693) (`e1d0ae3f`)
- Sprites: one layer-1 draw plan for reservation, refusals and the clip mode (#1646) (#1692) (`907d7047`)
- Camera and streaming tests: check what the screen shows, not the internal path, on Game Boy, Master System and Game Gear (#1662) (#1689) (`2c134666`)
- SetTile queries: one Z80-family emission behind a primitives port (#1668) (#1683) (`bd81dea7`)
- Semantics brake: keep R1 and the table builders, count every own error of the lowerers (#1659) (#1679) (`2f971ec3`)
- SetTile and collectibles: one contract for the Game Boy and Master System queries (#1667) (#1678) (`5cf1acea`)
- NES: retire sliced column preparation; every packed camera that streams columns prepares whole columns with a lead (#1675) (`f9b05d36`)
- Tests: observable behaviour is what the layer under test promises its client (#1673) (#1674) (`545e2cf9`)
- SetTile and collectibles: preparations for the shared Z80 emission (#1658) (#1672) (`6fb5e2b1`)
- Rules: least code in tests too, Khorikov's testing doctrine, an own temporary directory per agent (#1670) (#1671) (`f4852bbb`)
- SetTile and collectibles: shared bookkeeping in the SDK plans (#1666) (`12087aba`)
- Workflow: fight entropy in every issue, bounded so it is no rabbit hole (#1664) (#1665) (`6e937029`)
- Workflow: a technical criterion, not the owner, decides what joins a roadmap (#1660) (#1661) (`d9484337`)
- Benchmarks: temporary performance tolerance during the SDK unification (#1638) (#1657) (`98dff31d`)
- Architecture: brake against new SDK semantics in target lowerers (ADR 0016 D8) (#1639) (#1656) (`7ee3ee57`)
- ADR 0016: SDK semantics once, emission per CPU family, hardware per machine (#1637) (#1654) (`1cd09c9c`)
- NES: the packed static band writes from the world's row, and the band at the camera's row is retired (#1615) (#1645) (`fe4a4011`)
- Workflow: the scout compacts its context past 200K tokens too (#1642) (#1643) (`13e23a3c`)
- AGENTS.md: keep the language free of quirks; costly constructs warn instead of failing (#1633) (#1634) (`5275774b`)
- GB: cap the packed camera's walk at 8 pixels a call so sprites stay on the background (#1632) (`69d92007`)
- Camera: census of camera paths, remove NES packed row streaming and the unused RLE decoder, name the GB unpacked commit (#1619) (#1631) (`aa91fee5`)
- GB: a frame wait that reuses a commit's vertical blank hides the sprites its frame did not draw (#1577) (#1630) (`56c83d0c`)
- issue.py cleanup: fast-forward the main checkout so merged rules take effect (#1627) (#1629) (`c2876905`)
- Workflow: one state comment per roadmap instead of piled handoff notes (#1625) (#1626) (`aa17d68a`)
- AGENTS.md: cover most use cases with the least code, optimize last (#1622) (#1624) (`7501c537`)
- Workflow: the owner decides the product, agents decide technical choices (#1621) (#1623) (`73e1f2f7`)
- Workflow: bound the implementer's context and keep roadmaps converging (#1617) (#1618) (`8e1fbec3`)
- NES: Camera.Init must start its stream area at the world's stream row for the raw camera (#1608) (#1616) (`12a67666`)
- GB: share the column-window checks between the unpacked camera's SetPosition sites (#1603) (#1614) (`33c52b29`)
- Build: pin System.Security.Cryptography.Pkcs 8.0.2 over the vulnerable 8.0.1 (#1612) (#1613) (`7bd87372`)
- NES: the four-screen camera stays inside its band when the world is taller (#1605) (#1609) (`12f741d5`)
- GB: the unpacked camera moves only over a window of columns written ahead, with a lead (#1549) (#1607) (`7263f692`)
- NES: refuse at compile time the four-screen packed camera that cannot keep its picture (#1601) (#1606) (`618edbb4`)
- NES: walk a camera that keeps a column lead up to 16 px a frame (#1548) (#1600) (`3980f7d8`)
- Camera.Follow: place the camera near a far entry instead of walking to it (#1509) (#1598) (`00df9dd2`)
- NES: move the camera only over columns the window holds in static bands (#1547) (#1596) (`a65c0a23`)
- NES: prepare the lead's columns in pairs so a vertical blank can write two (#1589) (#1595) (`89e5cefb`)
- NES: write rows 30 and up of a four-screen band into the nametable below (#1585) (#1594) (`ceca88bf`)

_18 more changes left out: NuGet limits release notes to 35000 characters._