Skip to content

Releases: blit386/blit386

Release 1.4.0

Choose a tag to compare

@vancura vancura released this 23 Jul 10:40
1.4.0
98166b0

You change a line, hit save, and the game keeps running – ticks, camera, palette still where you left them. That is what 1.4.0 is about: true engine hot reload, fast enough that you forget the page used to blink, with a blit386/vite plugin, in-place asset hot-replace, and a loading-progress API for the screens you still have to wait for. CI, agent tooling, and docs catch up around the edges.

Hot Reload

Edit, save, keep playing – that is the whole trick.

  • Engine-side hot-swap runtime (src/hot/): registerHotReload, optional IBTDemo.onHotReload(context), and a three-tier swap – method-only prototype rebind when only method bodies change, full re-init when init() or the constructor changes, full page reload when hardware settings change. Ticks, camera, palette, and palette effects persist across method and re-init swaps. A second bootstrap() under a registered hot context routes to a swap instead of starting a second GameLoop; without one, it logs and returns false. (#374)
  • blit386/vite dev-server plugin (import { blit386 } from 'blit386/vite'): injects the hot-reload registration snippet into entry modules that call bootstrap( and import from 'blit386', watches configured asset directories (default public/), and broadcasts blit386:asset-changed HMR events (full reload for unrecognized extensions). Ships as a second build entry (dist/vite.js / .cjs / .d.ts) with no runtime dependency on vite itself. (#375)
  • In-place asset hot-replace for images, audio, and .btfont files: AssetLoader.evict, SpriteSheet / AudioClip / BitmapFont hot-replace (dev registries gated on an active hot context), and music restart via MusicPlayer when the current track's buffer is replaced. A broken asset event cannot crash the game loop. (#376)
  • Tier 3 under ?backend=software no longer forces a full reload on every edit – the hardware-settings diff now reapplies the URL backend override on the candidate before comparing. Plain .js / .mjs entries are syntax-checked with acorn before snippet injection so Vite's error overlay surfaces parse failures that previously only failed on the client. (#378)

Asset System

Loading screens finally get a real signal to poll.

  • BT.loadingAssetsCount returns the combined in-flight image + audio load count (AssetLoader.loadingCount + AudioClip.loadingCount). SpriteSheet.status ('loading' | 'ready' | 'failed') and coarse SpriteSheet.progress (0 or 1.0) expose per-sheet hot-reload replacement state for loading UI. (#388)

API Changes

Deprecation tags grow version stamps; dead internal aliases leave.

  • Every remaining date-only @deprecated JSDoc tag in src/ is upgraded to Deprecated since <version> (2026-05-31)., and dead internal-only compatibility aliases (PaletteView, RenderPaletteUsage, AssetLimits, OverlayToggleIcon) with no public callers are removed. docs/reference-deprecations.md now lists a removal target of 2.0.0 per alias group. (#406)

Input

Touch scrolling follows the same opt-in as pointer wheel capture.

  • canvas.style.touch-action is no longer hardcoded to none on attach. It tracks HardwareSettings.isCapturingPointerScroll (and the overlay palette band's forced capture): none while capturing, pan-y otherwise, updated live so touch tap-hold-scroll past the canvas works when scroll capture is left at its default false. (#387)

Security

The expired esbuild exception goes away for real.

  • pnpm.overrides.esbuild bumps to ^0.28.1, the GHSA-gv7w-rqvm-qjhr audit ignore is removed, and the Active exceptions row is cleared. esbuild is added as a direct devDependency so Vite 8's optional minify peer is present; fast-uri is pinned to clear the restored audit gate. (#390)

Developer Experience

Remote sessions and agent tooling stop needing a scavenger hunt.

  • Fresh remote/web sessions auto-bootstrap via scripts/session-start-bootstrap.sh (Claude Code SessionStart, Cursor sessionStart, optional .devcontainer postCreateCommand): Corepack enable when needed, then pnpm install --frozen-lockfile, skipped when the lockfile checksum stamp still matches. (#383)
  • Missing .claude/rules mirrors for Cursor-only rule files restore Claude/Cursor parity ahead of the drift checker. (#379)
  • agents:check verifies .cursor/rules.claude/rules parity, .agents/skills/* symlink integrity, and the AGENTS.mdCLAUDE.md pointer; wired into preflight and the CI quality job. (#381)
  • .github/copilot-instructions.md points Copilot agents at AGENTS.md / CLAUDE.md and the hard rules (no emoji, integer coordinates, BT namespace, American English, Conventional Commits + DCO). (#385)
  • .cursor/commands/ are generated from .claude/skills/*/SKILL.md via scripts/sync-cursor-commands.mjs, with sync:cursor-commands:check and test:cursor-commands in preflight and CI. (#386)
  • agents:check also covers Copilot instructions presence and Zed settings JSON consistency; the annotated src/ architecture tree moves into a mirrored rule pair and CLAUDE.md shrinks further. (#410)
  • Agent rules, skills, and command mirrors cleaned up after the architecture extraction – stale cross-refs and duplicated guidance trimmed. (#413)

CI and Tooling

Gates tighten, Renovate wakes up, and duplicate work gets cut.

  • Markdown link checks run with concurrency 8; quality and bundle-size checks move into ci.yml (including Knip and a 70 KB gzipped ESM limit), dropping the duplicate jobs from pr-checks.yml. (#380)
  • checkFile settles exactly once when both error and close fire, and each markdown-link-check child gets a timeout so a hung process cannot stall docs:links forever. (#382)
  • docs:links enumerates files with git ls-files "*.md" "*.mdx" so checks honor .gitignore and skip symlink duplicates under .agents/skills/*. (#391)
  • preflight gains test:api-history and test:security-preflight; CI quality runs api:since:check / api:history:check (with fetch-tags: true), and PR-scoped workflow concurrency cancels duplicate runs. (#394)
  • Dormant renovate.json repaired: real package matchers, 7-day minimumReleaseAge aligned with .npmrc, and Dependabot (security-only) vs Renovate (versions + Actions digests) documented. (#395)
  • Renovate commits get :gitSignOff and lowercase commitMessageAction: "update" so they pass DCO and commitlint; workflow node / pnpm pins are excluded from Actions digest updates. (#401)
  • GitHub Actions digests and the workflow Node version bump (22.18.022.23.1) via Renovate. (#402)
  • Dead root configs removed (.npmignore, .markdownlint.json); inert options trimmed in Vite, Biome, Prettier, and knip configs. (#405)
  • Guided GitHub issue forms (bug / feature / docs), issue chooser with contact links, and a PR template that surfaces DCO, Conventional Commits, preflight, and visual-test checklist. (#407)
  • Biome / ESLint / Prettier patch bumps (@biomejs/biome 2.5.4, ESLint 10.7.0, Prettier 3.9.5). (#404)
  • pnpm run lint uses --max-warnings 0; Biome noExplicitAny, noNonNullAssertion, and noParameterAssign promote from warn to error so full-repo lint matches pre-commit. (#408)
  • CodeRabbit reviews gated on the cr label only, and path filters / path instructions expanded for generated mirrors and house TypeScript / docs rules. (#411) (#412)
  • Post-1.3.1 API-history bookkeeping: UNRELEASED_VERSION advanced and docs/_api-history.json regenerated for the next cycle. (#373)
  • Release tracking advanced toward 1.4.0 in the API-history generator metadata. (#389)

Tests

Post-process effects finally carry their own coverage floor.

  • Unit coverage for FullscreenPixelEffect / FullscreenEffect, PixelGlitch / PixelMosaic, post-process tier dispatch, and software-renderer BT.effectAdd rejection; 80% per-directory threshold for src/render/effects/**. (#392)

Documentation

Hot reload gets a guide; the rest of the docs catch up.

  • New docs/guide-hot-reload.md: swap tiers, onHotReload / HotReloadContext, the blit386:hot-reload DOM event, the asset hot-replace matrix, full-reload triggers, and the blit386/vite plugin – published via the sitemap. (#377)
  • CLAUDE.md trimmed from a session-loading monolith into short summaries with pointers; rule files become the canonical detail; AGENTS.md grows into a real standalone quick start. (#384)
  • Changelog gains the missing 1.4.0 editorial entry for the asset loading-progress API (BT.loadingAssetsCount, loader counters, SpriteSheet.status / progress). (#393)
  • Stale agent line citations removed, SECURITY.md points at GitHub private vulnerability reporting only, and CONTRIBUTING.md gets a Getting Started path. (#409)
  • Docs index, hot-reload cross-links, loading-progress guidance, and related API/guide pages refreshed after the 1.4.0 surface settled. (#414)

Full Changelog: 1.3.1...1.4.0

Release 1.3.1

Choose a tag to compare

@vancura vancura released this 19 Jul 16:13
1.3.1
a31f973

This release focuses on mobile and embedding-friendly hardware controls: screen orientation detection with an optional lock, a Screen Wake Lock API to keep the display awake during gameplay, and opt-in scroll capture for both the pointer wheel and the keyboard so an embedded canvas no longer hijacks page scrolling by default. The overlay's toggle hit region shrinks further with a new debug outline, and a dependency bump closes nine open Dependabot alerts.

Core and Utils

Two new opt-in hardware subsystems land alongside a screen orientation getter.

  • Screen orientation detection and optional lock: BT.screenOrientation reads the current screen.orientation.type (null when unavailable), IBTDemo.onOrientationChange(type) fires on rotate, and HardwareSettings.preferredOrientation ('landscape' | 'portrait' | 'any', default 'any') attempts screen.orientation.lock() after init(). Unsupported or rejected locks are silent no-ops and never fail initialization. (#370)
  • HardwareSettings.isWakeLockEnabled (default false) requests a screen wake lock after a successful init() to stop mobile browsers from dimming or locking the screen during active gameplay, and re-acquires it automatically once the page returns to the foreground. Backed by a new internal WakeLock subsystem; not exported as a public BT.* method. (#368)

Input

Both canvas scroll paths move from unconditional capture to an explicit opt-in.

  • Breaking: pointer wheel capture is now opt-in via HardwareSettings.isCapturingPointerScroll (default false). The canvas used to call preventDefault() on every wheel event unconditionally, blocking host-page scroll the moment the pointer crossed the canvas even when nothing read BT.pointerScrollDelta. Games that map scroll input must now set the flag; the overlay's palette band still force-captures scroll so palette scrolling never leaks to the page. (#367)
  • HardwareSettings.isCapturingKeyboardScroll (default false, additive) calls preventDefault() on canvas keydown for the arrow keys, Space, Page Up/Down, Home, and End when enabled, so games can map those keys without scrolling the host page. (#369)

Overlay

  • The bottom-left toggle hit region shrinks from 48×48 to 17×13 pixels to match the visible toggle icon, and a new isOverlayToggleHitDebugVisible flag (default false) draws a 1px outline of that region for tuning. (#366)

CI and Tooling

  • 15 devDependencies bumped and pnpm-lock.yaml regenerated, resolving all 9 open Dependabot alerts (js-yaml, undici, @babel/core). contents: read permissions added to the quality, commitlint, and bundle-size GitHub Actions jobs, closing 3 open code-scanning alerts. (#365)

Documentation

  • API history updated with the 1.3.0 release timestamp, and audio and game-loop demos embedded across the Audio API, overlay API, and audio and game-loop guide pages. (#364)

Full Changelog: 1.3.0...1.3.1

Release 1.3.0

Choose a tag to compare

@vancura vancura released this 13 Jul 15:41
1.3.0
518218a

This is the audio release: the engine gains a full sound subsystem – bus mixing, streamed clip loading, a voice pool with priority stealing, deterministic synthesis, and crossfading music – plus BT.renderAlpha for smoother motion between fixed update steps. The overlay gains audio level meters and drift-free dividers, a handful of camera and frame-capture bugs are fixed, and the published docs move to a site-first workflow (Fumadocs components, Twoslash-checked examples, per-symbol version history) for blit386.dev.

Audio

The engine's first audio subsystem, built around a three-bus Web Audio graph.

  • Added a full audio subsystem: AudioManager runs sfx and music buses into main, with per-bus BT.audioVolumeSet / BT.audioVolumeGet, BT.audioMuteSet / BT.isAudioMuted, and the BT.isAudioUnlocked getter tracking the first-gesture autoplay unlock. HardwareSettings.audioVoices (default 16, range 1-64) sizes the SFX voice pool. (#327)
  • AudioClip asset loading: streamed fetch and decode with byte-accurate progress, per-URL caching with in-flight request deduplication, and ordered fallback URL lists for browsers that cannot decode the first choice. AudioClip.loadAll runs requests in parallel; unload() releases decoded buffers. (#328)
  • Sound-effect playback through a fixed-size voice pool with priority-based stealing: BT.soundPlay, BT.soundStop, BT.isSoundPlaying, per-voice BT.soundVolumeSet / BT.soundPitchSet / BT.soundPanSet (plus getters), and generational SoundRef handles that safely no-op once stolen or stopped. (#329)
  • Deterministic procedural sound synthesis via AudioClip.synth(params) – waveforms, an ADSR envelope, a linear pitch sweep, vibrato, and noise mixing rendered on the CPU with no source file – plus a preset library, BT.synthPreset.jump / pickup / explosion / laser / hit / blip. (#330)
  • Crossfading music playback: BT.musicPlay, BT.musicStop, BT.musicVolumeSet / BT.musicVolumeGet, and the BT.isMusicPlaying getter, with configurable fade duration, crossfade overlap, and loop points. A music request made before the audio context unlocks is remembered and starts automatically on the first gesture. (#333)
  • Overlay audio meters: a per-bus level band plus a voices used/total, steal, and drop readout, gated behind HardwareSettings.isOverlayAudioMetersEnabled with lazy AnalyserNode creation so metering costs nothing when disabled. (#337)

Overlay

Two toggle-timing and layout fixes land alongside the new audio meter band.

  • The overlay's Backquote toggle press is now sampled during the fixed-update tick instead of the render phase, fixing intermittently dropped toggle presses under rapid input – previously missed roughly 20% of the time on 120+ Hz displays. (#314)
  • Engine-composed overlay label separators (backend/resolution, frame metrics, timing, renderer diagnostics, the audio meter voice readout) now render as 1px gap-colored divider fills instead of | glyph text, with wider edge margins and divider gaps. (#339)
  • Numeric overlay fields (FPS, frame/update/render milliseconds, the update-step suffix, renderer diagnostics counts) are now padded to a fixed character width, fixing divider drift that occurred whenever a value's digit count changed between frames. (#341)

Rendering and Camera

  • The camera offset now persists across render frames that run zero update() steps, so scrolling no longer snaps back to the previous frame's origin on high-refresh displays; the render-loop delta is also snapped to the nearest fixed-update interval multiple to reduce timing drift. (#343)
  • WebGPU canvas configuration now requests COPY_SRC usage alongside RENDER_ATTACHMENT, fixing BT.captureFrame() / BT.downloadFrame() producing fully transparent PNGs on backends that enforce texture-usage validation strictly. (#335)

Asset System

  • BitmapFont.measureTextSize() now allocates a fresh { width, height } object on every call instead of returning a shared internal instance, fixing two outstanding results silently aliasing each other. measureTextSizeInto() remains the zero-allocation option for hot loops. (#334)

Core and Utils

  • Added the read-only BT.renderAlpha getter: the fractional interpolation progress in [0, 1) between fixed update() steps, for smoothing motion during render() on displays that render faster than the update rate. (#344)

CI and Tooling

  • Independent quality-check steps (format, lint, typecheck, spellcheck, docs:links, doc-banner check) now run concurrently in CI via background: true / wait-all, cutting overall pipeline time. (#313)

Documentation

The published docs move to a site-first workflow for blit386.dev, with a large restructuring and tooling pass alongside.

  • A self-maintaining blit386.dev banner now appears below the H1 of every published doc, generated from docs/_sitemap.json and kept in sync by pnpm run sync:doc-banners / sync:doc-banners:check in CI. (#307)
  • Published docs adopt Fumadocs MDX components (Callout, TypeTable, Steps, Tabs, Accordions, Cards) across 15 pages for a more interactive read on the site. (#308)
  • The oversized api-core.md page is split into six focused pages (overlay, game loop, camera, easing, core types, browser support); guides, reference, and performance docs are renamed to mirror their sitemap section (guide-*, reference-*, performance-*), and a house prose style (no bold, no --- rules, × for dimensions) is codified. (#309)
  • Documentation and rule/skill files across the repo drop decorative bold emphasis and reflow headings and lists for consistency; CLAUDE.md is condensed. (#310)
  • All TypeScript code blocks in published docs now use ts twoslash, enabling type-on-hover popups on the live site; every block was rewritten to compile standalone. (#311)
  • Broadened markdown-link-check ignore patterns to stop CI false failures on blit386.dev and demos.blit386.dev links. (#315)
  • Dash typography (en dash for parenthetical breaks and ranges) standardized across documentation. (#316)
  • README rewritten with a beginner-first voice and updated to point at the typeset docs on blit386.dev. (#317)
  • Added interactive demo embeds across API and guide pages (assets, camera, core, easing, game loop, palette, rendering, bitmap fonts, input, overlay, palette presets, post-process effects) – documentation only, no API changes. (#318)
  • Introduced scripts/gen-api-history.mjs, generating docs/_api-history.json from @since / @changed / @deprecated JSDoc tags on every public symbol; added api:history, api:history:check, and api:since:check to pnpm run preflight, plus <Since>, <ApiAvailability>, and <PageChangelog> doc components and a new documentation-and-versioning-guide.md. (#331)
  • Wired the @since / @changed / @deprecated tagging workflow into the docs-sync rules so adding or changing a public API symbol reliably surfaces the required tags and the api:history regeneration step. (#332)
  • Normalized British spellings to American English (optimisation to optimization, cancelled to canceled) across docs and JSDoc, and codified the convention as a repo rule. (#338)
  • Documented Safari Low Power Mode / WebKit requestAnimationFrame throttling and its effect on render() cadence versus the fixed-update accumulator, including the overlay's xN multi-update indicator. (#342)
  • Audited documentation, rules, and skills against the shipped 1.3.0 surface and closed the gaps: added the changelog entry, tagged HardwareSettings.audioVoices and the synth pitch/vibrato types with @since 1.3.0, corrected several audio-subsystem JSDoc comments that still described a future phase, and removed a leftover "coming soon" placeholder section. (#345)

Full Changelog: 1.2.1...1.3.0

Release 1.2.1

Choose a tag to compare

@vancura vancura released this 20 Jun 19:13
1.2.1
5c484ed

Keyboard edges were getting eaten on 120+ Hz displays – fast taps between fixed update steps could just disappear. Fixed now. Docs got a proper cross-reference pass too, which honestly should have been there from the start, and .mdc files finally land in the spellcheck and formatting hooks.

Core and Utils

A single targeted fix to keyboard edge buffering.

  • KeyboardInput now buffers press and release edges from DOM events into pendingPress / pendingRelease buffers, which are consumed and cleared by endUpdate() at the fixed update boundary. Previously endFrame ran once per render frame while update ran at targetFPS; on 120+ Hz displays with a 60 FPS target this caused fast taps and releases to be dropped between fixed steps. The new endUpdate(_currentTick) is the canonical boundary; endFrame becomes a deprecated alias forwarding to it. (#304)
  • Stray keyup events for keys that were not actually held no longer produce a released edge; guard added inside the DOM event handler. (#304)

Documentation

Cross-referencing and navigation improved across the full docs surface.

  • "See Also" guide tables added or expanded in 16 documentation files (docs/api-core.md, docs/api-palette.md, docs/api-rendering.md, docs/api-assets.md, docs/bitmap-fonts.md, docs/deprecations.md, docs/input.md, docs/overlay.md, docs/palette-guide.md, docs/palette-presets.md, docs/performance-best-practices.md, docs/performance-testing.md, docs/post-process-effects.md, docs/testing.md, docs/tooling.md, docs/voice.md) and the three security docs. Related guides now link to each other. (#303)
  • README.md rewritten with a beginner-first voice: leads with a complete bootstrap()-based sample, adds a "Load a sprite and draw it" walkthrough and a palette cycling example, removes the marketing blocks, and trims Prerequisites down to what's actually required. (#303)
  • docs/api-core.md gained a "Browser support" section describing the WebGPU-first / Canvas 2D fallback behavior. (#303)
  • BT.inputString timing semantics in docs/input.md corrected: the typed-text buffer clears at the end of each fixed update step (read during update()), not at end-of-frame. (#304)

CI and Tooling

Dev tooling got broader .mdc coverage and more robust link-checking.

  • .editorconfig, .lintstagedrc.json, and the spellcheck script now include *.mdc files alongside *.md and *.mdx. The bt-spellcheck skill documentation updated to match. (#305)
  • .husky/commit-msg now quotes "$1" for safer path handling; .husky/ensure-pnpm.sh only sources nvm when running under bash or zsh. (#305)
  • scripts/check-markdown-links.mjs treats result.error as a failure condition (previously only result.status === 'dead' was caught); documentation updated to reflect full recursive scanning. (#305)
  • .github/markdown-link-check.json ignore patterns narrowed to match /blit386 or /create-blit386 specifically; demos.blit386.dev added as an ignored domain. (#303, #305)
  • .gitignore adds pnpm-store/; .prettierignore adds yarn.lock. (#305)
  • "Waku" added to cspell.json; "gamedev" added to package.json keywords. (#303, #305)

Full Changelog: 1.2.0...1.2.1

Release 1.2.0

Choose a tag to compare

@vancura vancura released this 19 Jun 09:15
1.2.0
3b587c7

Release 1.2.0

The package is blit386 now. Was blit-tech, which nobody could spell anyway – dist files, default canvas ID, and the public demo interface (IBTDemo, replacing IBlitTechDemo) all follow. A renderer fix lands alongside: module-level GPUTextureUsage access was crashing non-WebGPU browsers at load time; it's inlined at call sites now. The rest is housekeeping.

API Changes

The rename is a breaking change – update imports and type annotations.

  • Breaking: The npm package is now blit386 (was blit-tech). Import paths, package.json name, repository URLs, and dist output filenames (blit386.js, blit386.cjs, blit386.d.ts) all updated. The public demo interface is now IBTDemo (was IBlitTechDemo). DEFAULT_CANVAS_ID is now 'blit386-canvas' (was 'blit-tech-canvas'). Default frame-capture filename is now 'blit386-capture.png'. The REGISTRY_TITLE_PATTERN in labels.ts was also fixed to match BLIT386 Demo titles (the regex was already broken before the rename). (#282)

Rendering

A load-time crash on non-WebGPU browsers is fixed, and a previous dynamic-import workaround is reverted.

  • Three module-scope constants (SCENE_TARGET_USAGE, OFFSCREEN_USAGE, OUTPUT_USAGE) evaluated GPUTextureUsage at module load time, crashing browsers without WebGPU globals (e.g. Firefox on Linux). They are now inlined at their call sites. WebGPURenderer reverts to a static import; the dist output is back to two files instead of the split chunks introduced by the prior dynamic-import workaround. (#272)

Security

A temporary audit exception covers an esbuild CVE while the minimum-release-age gate clears.

  • GHSA-gv7w-rqvm-qjhr (esbuild) is accepted as a temporary audit exception via --ignore on the security:audit script. The patched release (esbuild 0.28.1) was published too recently for the seven-day release-age gate; pnpm.auditConfig.ignoreGhsas does not work in pnpm 10.x, making the CLI flag the only effective path. Exception documented in docs/security/audit-exceptions.md with a review-by date of 2026-06-21. (#278)

CI and Tooling

A batch of identity cleanup, config extractions, and dependency bumps rounds out the release.

  • All internal references to vancura/blit-tech / Blit-Tech updated to blit386/blit386 / blit386 across Claude skills, Cursor rules, the GitHub security-risk acceptance template, and editor config. Biome bumped from 2.4.15 to 2.5.0; CSS linter rules migrated to the new preset: "recommended" form. cspell.json tidied (reordered entries, removed redundant rasterizing). (#291)
  • vite bumped from 8.0.14 to 8.0.16 (Dependabot). (#281)
  • Engine logo updated. (#300)
  • package.json metadata updated (repository URL, homepage, bugs link) to match the new blit386/blit386 identity. (#280)
  • lint-staged configuration extracted from package.json into a dedicated .lintstagedrc.json file. No behavior change. (#275)
  • bt-issue-audit Claude skill added: re-audits open GitHub issues against the current codebase and posts an audit-update comment only when the status has materially changed since the last audit. Mirrored into .agents/skills/ for Zed. (#273)
  • Claude skill descriptions expanded for bt-deep-review, bt-format, bt-pr, bt-preflight, bt-quick-format, bt-review, bt-spellcheck, and bt-test to include "Use when..." guidance for better skill selection. (#277)
  • AGENTS.md entry point added; .claude/rules/docs-sync-required.md rule added defining when documentation must be updated alongside implementation changes. PostToolUse hooks in .claude/settings.json updated to use pnpm exec for formatting and spellchecking. (#276)

Documentation

Onboarding now leads with the scaffolder rather than manual Vite boilerplate.

  • README.md Quick Start replaced with the npm create blit386@latest scaffolder workflow. Manual Vite setup boilerplate removed. docs/api-core.md gets a callout near the top directing new users to the scaffolder. CLAUDE.md documents the new "Onboarding and the scaffolder" section. A docs-sync rule added requiring scaffolder templates, kit docs, and pinned version ranges to stay in sync when onboarding surface changes. (#274)

Full Changelog: 1.1.1...1.2.0

Release 1.1.1

Choose a tag to compare

@vancura vancura released this 07 Jun 12:11
1.1.1
c7d4f81

The biggest thing in 1.1.1 is a crash fix that should have been caught earlier: WebGPURenderer was imported statically, which evaluated GPUTextureUsage at module load time, which meant Firefox – and anything else running without WebGPU globals – died before the software fallback could do anything about it. That's fixed. Everything else is cleanup: the BT API got a naming pass so boolean queries, predicates, and HardwareSettings config flags all follow is*/has* consistently, the asset subsystem went through a structural refactor across BitmapFont, SpriteSheet, Palette, PaletteEffect, and SystemFont, and documentation was corrected and expanded across 45+ files, including a new overlay guide that probably should have existed a year ago.

API Changes

A large naming normalization pass touches the BT namespace and related input/config surfaces.

  • Boolean queries (isDown, isPressed, isPointerActive), predicate methods (isEqual, isContaining, isIntersecting), and HardwareSettings config flags (isOverlayEnabled, isDetectingDroppedFrames) now all follow is*/has* conventions consistently across the board. Deprecated aliases keep previous names alive for backward compatibility. (#241)

Asset System

Seven refactor PRs tighten control flow, strengthen validation, and extract focused helpers across every asset class.

  • WebGPU mock factories and test-time stubs consolidated across asset loading, palette management, sprite-sheet processing, font generation, and preset data. Shared helpers introduced; duplication removed. (#256)
  • BitmapFont load helpers, glyph lookup, and measurement now use single-return control flow. .btfont JSON is parsed as unknown and narrowed through a runtime FileData guard so texture and glyph fields are validated before use. Benchmark glyph seeding merged into one loop. (#257)
  • AssetLoader.buildExtensionHint uses a single return path, matching the style used elsewhere in the asset system. (#258)
  • Palette.fromJSON validates with positive checks and loads colors and names in one loop. Palette.set uses if/else for slot 0 vs other indices; constructor positioned before the isDirty getter. (#259)
  • PaletteEffectManager.update, CycleEffect.update, FlashEffect.update, and paletteSwap collapsed to single return points. Flash apply/restore loops extracted into module helpers so update() stays loop-free. Related test assertions deduplicated. (#260)
  • SystemFont.writeGlyphPixels replaces nested x/y loops with a single flat index loop, preserving atlas write semantics and bitmap bit decoding. (#261)
  • SpriteSheet color collection, palette-fit validation, palette writes, and usage-mask marking extracted into focused helpers. Method complexity reduced; dead oversized-image canvas stubs removed from tests. (#262)

CI and Tooling

Zed agent skill support landed, editor configs were cleaned up, and // #region markers were removed everywhere.

  • .agents/skills/ added with symlinks to .claude/skills/ for Zed skill discovery. All skills renamed with bt- prefix (perfbt-perf, releasebt-release, testbt-test). Required name frontmatter added to all SKILL.md files for Zed validation. (#253)
  • Dead VS Code debug launch config removed. .zed/settings.json is now tracked (.gitignore updated to mirror the .vscode pattern). Prettier coverage expanded to .mdc files. Zed agent terminal/git/write/edit allowlists tightened. (#254)
  • // #region and // #endregion markers stripped from src/, scripts/, tests, and benchmarks. CLAUDE.md and docs/testing.md updated to remove guidance that recommended region markers for long files. (#263)
  • Broad reformat pass across documentation and internal code structure. No public API or behavior changes. (#266)

Rendering

GPU acronym casing normalized to uppercase across all identifiers.

  • WebGpuRenderer, disposeGpuState, renderDimensionGpuLimitError, and related identifiers renamed to their uppercase GPU forms (WebGPURenderer, disposeGPUState, renderDimensionGPULimitError). Changes touch source, tests, and documentation; behavior is unchanged. (#267)

Core and Utils

The software fallback was unreachable on browsers without WebGPU globals.

  • Static import of WebGPURenderer was evaluating GPUTextureUsage at module load time, crashing Firefox on Linux and any browser without WebGPU globals before the software backend could start. WebGPURenderer is now loaded dynamically only after WebGPU init succeeds, so the software fallback can actually reach init. (#240)

Tests

  • Visual test specifications and fixtures refactored for naming consistency and code organization. Linting improvements across test specs. (#255)

Documentation

Four PRs corrected factual accuracy and added new guides across the full doc set.

  • Cursor glob-scoped rule and Claude Code rule added documenting src/ file layout, class member order as enforced by perfectionist/sort-classes, and the ban on // #region markers. Mirrors CLAUDE.md and the developer-experience guide. (#265)
  • CLAUDE.md architecture map updated to reflect SystemFont, Timer, fullscreenVS, overlay constants/labels/types, and the consumer-doc-imports test guard. BT API methods list expanded with grouped categories, deprecated aliases, and top-level package exports. (#268)
  • New docs/overlay.md covering subsystem layout, visibility model, custom overlayRows, optional bands, HUD palette slots, and the present/target FPS distinction. Factual accuracy corrected across api-assets.md, api-core.md, api-utils.md, and other existing guides. (#269)
  • 45+ files of JSDoc precision work: all {@link} tags qualified to full interface.member form, paletteSet vs in-place mutation semantics clarified, spritesRefresh documented for when it is actually required (layout swaps, not value changes), effectAdd/effectRemove/effectClear corrected. (#270)

Full Changelog: 1.1.0...1.1.1

Release 1.1.0

Choose a tag to compare

@vancura vancura released this 30 May 14:23
1.1.0
2423268

Version 1.1.0 is the overlay release. See the NPM package.

Demo 001:

screenshot-1

The old software-mode ticker is gone, replaced by a proper engine HUD that now lives in its own src/overlay/ subsystem: a toggle-able bar stack with a live palette swatch grid, and a scrolling timing chart that grew grid lines, severity tints, event tags you drop with BT.assignTag, and renderer pipeline diagnostics. Two breaking vocabulary changes ride along. HardwareSettings.renderer is now backend, the canvasDisplaySize / maxCanvasDisplaySize pair moves to a drawingBufferSize / maxCanvasSize resolution model, and every statsOverlay* setting drops its stats prefix.

Demo 010:

screenshot-2

API Changes

Three breaking renames clean up the public configuration vocabulary, plus one new getter.

  • Breaking: the rendering-mode API renamed from renderer to backend. RendererBackend is now Backend, HardwareSettings.renderer is now HardwareSettings.backend, and the URL override is ?backend=software (was ?renderer=software). BT.activeBackend keeps its name and now explicitly reports the backend that actually started after fallback. (#176)
  • Breaking: HardwareSettings display fields move to a resolution model. canvasDisplaySize becomes drawingBufferSize and maxCanvasDisplaySize becomes maxCanvasSize; BT.canvasDisplaySize is replaced by BT.drawingBufferSize, and BT.outputSize now derives from drawingBufferSize ?? displaySize. configure() merging also rejects invalid sizing via validation instead of throwing on clone. (#189)
  • Breaking: the stats overlay was promoted to a top-level src/overlay/ subsystem and every statsOverlay* HardwareSettings field, the statsOverlayRows() hook, and the StatsOverlay* exported types lost their stats/StatsOverlay prefix (overlayEnabled, overlayRows(), OverlayRow, OverlayStyle, and so on). The STATS_OVERLAY_NO_BACKEND error constant is now OVERLAY_NO_BACKEND. No compatibility shims remain. (#226)
  • Added BT.requestedBackend, a getter for the resolved init backend (after configure() merge and ?backend=software). It stays fixed even when WebGPU falls back to software; use BT.activeBackend for runtime feature gating. (#178)

Asset System

  • Redrew the ampersand glyph (codepoint 38) in the system font and regenerated systemFontData.ts from the updated assets/system-font.png. (#180)

CI and Tooling

Tooling housekeeping ahead of the public release: skill namespacing, link checking, and dependency bumps.

  • Namespaced the Claude skill commands under bt-* (/preflight is now /bt-preflight, and so on) and standardized every doc, workflow, and package.json script on pnpm run <script>. (#172)
  • Expanded .gitignore / .npmignore to cover local AI tooling and MCP config files, and added Cursor IDE hooks for formatting, shell safety, and the RTK rewrite, plus coding-rule documents. (#173)
  • Bumped dev dependencies (commitlint, Playwright, vitest, eslint, biome, vite, typescript, and others) and refreshed the package.json keyword list toward palette-first and WebGPU themes. (#227)
  • Added a docs:links script that recursively validates Markdown links via markdown-link-check, wired into preflight, the pre-push hook, and the CI quality jobs. (#228)
  • Added contributor-covenant.org and conventionalcommits.org to the link-check ignore list; both return intermittent Status 0 on GitHub Actions runners and caused flaky failures. (#234)

Overlay HUD

The release theme. The software ticker was replaced by an engine-managed overlay, which then grew a palette grid, a timing chart, interaction, and its own subsystem.

  • Added the engine-managed overlay HUD showing demo title, active backend, resolution, and FPS, replacing the old SoftwareTicker banner. Toggle-able at runtime and available on both backends; opt out with configure({ overlayEnabled: false }). (#174)
  • Added overlay compositing layers on WebGPU so HUD content batches after the sprite and primitive passes and reliably renders on top; the software backend preserves the same layering through call order. (#175)
  • Expanded the overlay to a three-bar layout with per-frame timing metrics (frame, update(), render() times, update steps, draw calls) and renamed the FPS readout to "Present FPS" to match the measured requestAnimationFrame cadence. (#182)
  • Split the monolithic overlay into a focused module tree (orchestrator, bars, toggle, FPS/timing samplers, dynamic layout planner) behind a barrel export, with no change to the public surface. (#183)
  • Added a live palette swatch grid to the overlay bottom band, driven by a per-frame palette-usage mask that records which indices each demo draw, clear, and sprite call references. Opt in via overlayPaletteView. (#185)
  • Gated palette-usage tracking behind overlay visibility and the palette grid being enabled, reused scratch buffers in markPaletteIndicesInRect, and cached the hint-exclusion rect to cut per-draw allocations. The CI benchmark regression threshold was raised from 10% to 25%. (#186)
  • Added an opt-in scrolling timing-chart band that records per-frame update/render timings into a ring buffer and draws one-pixel dots with sub-millisecond minimum visibility. Configure via overlayTimingChart, overlayTimingChartHeight, and overlayTimingChartStyle. (#187)
  • The overlay body now starts hidden behind a toggle hint by default. Added overlayVisibleAtStart, overlayToggleHintVisible, and overlayToggleEnabled, and split the footer into a palette band and a hint bar. (#190)
  • Added palette swatch hover tooltips and click-to-copy: clicking a swatch writes its index to the clipboard, with a transient copy-status tooltip. Introduced a foreground overlay tier so popovers render above other overlay content. (#224)
  • Removed the overlay-specific OnTop/Foreground methods from IRenderer and routed the HUD through an internal OverlayDrawTarget port (drawBarFill / drawLabel) with fill-then-label ordering, dropping the extra WebGPU foreground pipelines. (#225)
  • Fixed palette tooltip z-ordering so tooltips composite above custom row labels, via new drawBarFillOnTop() / drawLabelOnTop() overlay draw methods and a late WebGPU batch. (#229)
  • Added gapPaletteIndex to OverlayStyle for independent coloring of the 1px row gaps and cluster separators between overlay bands; it falls back to barPaletteIndex when omitted. (#230)
  • Added a scrollable, row-capped viewport for the palette grid with a macOS-style scrollbar. Cap visible rows with overlayPaletteRowsVisible; scroll by wheel inside the palette band or by dragging the thumb. (#231)
  • Replaced the text [~] toggle hint with an inline 11x7 bitmap icon, moving the hint and its hit region to the bottom-left corner. (#232)
  • Added severity tinting to the timing chart: update/render dots switch to warning or error palette indices when frames run over budget or drop, with error taking precedence. OverlayTimingSnapshot now carries a droppedFrames count. (#233)
  • Added horizontal grid reference lines to the timing chart at 5 ms, 10 ms, and 33.33 ms, plus a dynamic line at the targetFPS frame budget, with dots anchored to the baseline. Style via the new gridPaletteIndex. (#235)
  • Added BT.assignTag(label?) to drop labeled milestone markers on the scrolling timing chart at the current tick; tags scroll with samples, prune off-screen, and a "Start" tag is injected on reset. Breaking: the eventPaletteIndex style field is renamed tagPaletteIndex. (#236)
  • Extended the overlay timing snapshot with WebGPU renderer pipeline diagnostics (primitive/sprite overflow counts and submitted vertex counts), surfaced via overlayTimingChartDiagnostics ('minimal' | 'rich' | false) and an optional overlayRendererDiagnosticsBar. (#237)

Documentation

Documentation tracking the API renames, plus terminology cleanup and a pre-release scrub.

  • Clarified the distinction between the fixed update() simulation rate (targetFPS, ticks, Timer) and the measured render rate, and renamed the overlay label from "FPS" to "Render FPS". (#177)
  • Unified palette addressing terminology across the API docs: slot (prose), paletteIndex (absolute draw index), and paletteOffset (per-draw shift on stored texels), with BT.paletteSet vs BT.palette guidance. (#179)
  • Added a resolution-model glossary to api-core.md disambiguating logical size, drawing buffer, CSS cap, effect tier, and BT.outputSize. (#181)
  • Reclassified BT.outputSize as a derived getter (drawingBufferSize ?? displaySize) with no matching HardwareSettings field, across the getter rules and DX guide. (#188)
  • Stripped all Linear VV-* ticket IDs and linear.app links from comments, JSDoc, docs, and templates ahead of the public release, and synced the architecture map and README overlay section to the hidden-by-default body and new toggle flags. (#238)

Full Changelog: 1.0.5...1.1.0

Release 1.0.5

Choose a tag to compare

@vancura vancura released this 21 May 13:05
1.0.5
a0c0e09

Most of this release is about trust - the kind you either build incrementally or don't have when it matters. The MCP preflight infrastructure lands with documented fallbacks for when tooling goes offline; the CI now runs a dependency audit on every PR so CVEs don't quietly accumulate between releases. The btfont loader grew real validation this cycle: embedded textures must be data:image/png;base64 with a 512 KiB cap, and glyph metrics are checked before any atlas decode begins. The configure() method now accepts a partial HardwareSettings object, so demos can override just targetFPS without spelling out every field.

Asset System

The btfont pipeline got proper input validation.

  • Embedded font textures in .btfont files must now use data:image/png;base64 with a 512 KiB payload cap; other URI schemes and oversized payloads fail fast with a clear error. Glyph metrics are validated before atlas decode, and atlas bounds are checked after. A new AssetLimits module centralises these constants. (#168)

Security

Three PRs tighten dependency and tooling security posture.

  • MCP preflight script (scripts/security/mcp-preflight.mjs) added with governance shadow detection; classifies servers as healthy, auth_required, errored, or absent and produces a proceed/deny decision before security scans run. A new /security-run skill and runbook fallback matrix ensure scans never silently fail when MCP tooling is degraded. (#163)
  • A parallel CI job now runs pnpm security:audit (all deps) and pnpm security:audit:prod (production only) at moderate+ severity on every PR and main push. A new docs/security/audit-exceptions.md playbook covers the risk-acceptance workflow. (#164)
  • brace-expansion pinned to >=5.0.6 and ws pinned to >=8.20.1 via pnpm.overrides; both excluded from minimum-release-age so patches apply immediately. @typescript-eslint/* bumped to 8.59.3. (#165)

CI and Tooling

Tooling additions and a typographic cleanup land in this cycle.

  • A /release Claude Code skill added at .claude/skills/release/SKILL.md; automates gathering PRs since the last tag, grouping by semantic section, and writing RELEASE.md. Does not modify source files or create commits. (#170)
  • Em dashes, en dashes, and spaced double hyphens normalised to ASCII hyphens across 49 files: docs, JSDoc, inline comments, error messages, and region markers. No functional changes. (#161)
  • VS Code launch configuration added at .claude/launch.json to start the sibling blit-tech-demos Vite dev server on port 5173 from within this repo. (#160)

Tests

Test coverage expanded for the render dimension validation layer.

  • Unit tests added for RenderLimits, WebGPUContext, and BTAPI covering invalid maxCanvasDisplaySize (NaN), canvasDisplaySize exceeding adapter maxTextureDimension2D, and GPU limit failures without software fallback. The renderDimension* error message helpers are now covered by Vitest assertions. (#169)

Documentation

Security documentation filled in for the hardening program.

  • Maintainers section added to docs/security/security-runbook.md with a named primary owner, explicit no-backup stance, and step-by-step triage; closes the VV-522 ownership fallback requirement. (#166)
  • GitHub Actions pinning guidance added to docs/security/dependency-policy.md: SHA format requirement, per-job minimum permissions, Renovate vs manual bump procedures, and a note on why npm provenance is not currently enabled for the local release flow. (#167)

Examples

The configure() API now accepts a partial settings object.

  • IBlitTechDemo.configure() return type changed to Partial<HardwareSettings>; the engine merges the result with defaults via the new exported mergeHardwareSettings() function. Demos can now return only the fields they need to override (e.g. { targetFPS: 30 }) instead of a complete HardwareSettings object. (#162)

Full Changelog: 1.0.4...1.0.5

Release 1.0.4

Choose a tag to compare

@vancura vancura released this 17 May 12:03
1.0.4
8df339a

One breaking change: BT namespace accessors are proper ES getters now, so BT.displaySize() becomes BT.displaySize, BT.ticks() becomes BT.ticks. Drop the parentheses and you're done. The rest is security work – RenderLimits and AssetLimits cap dimensions before anything gets allocated, pnpm 10 supply-chain hardening is on, and CI action tags are pinned to commit SHAs instead of version tags that can move.

API Changes

The BT namespace read-only accessors changed shape.

  • Breaking: BT.displaySize(), BT.ticks(), BT.deltaSeconds(), BT.timeSeconds(), BT.pointerScrollDelta(), BT.gamepadCount(), and BT.inputString() are now ES getters – remove the call parentheses. Four accessors were also renamed: fps() to BT.targetFPS, getActiveBackend() to BT.activeBackend, paletteGet() to BT.palette, cameraGet() to BT.camera. Two new getters added: BT.canvasDisplaySize and BT.outputSize (canvasDisplaySize ?? displaySize). Vector2i getters return a fresh clone per read. (#153)

Core and Utils

Two new utility modules centralise dimension handling and canvas CSS sizing.

  • CanvasLayoutStyles extracts canvas CSS sizing logic from renderer initialization. HardwareSettings gains an optional maxCanvasDisplaySize field (default 960x720). CSS custom properties --canvas-display-w, --canvas-display-h, --canvas-max-w, and --canvas-max-h are injected onto the layout container; max-width/max-height are enforced via inline styles. (#152)
  • RenderLimits validates displaySize, canvasDisplaySize, and maxCanvasDisplaySize before any canvas, renderer, or GPU resource is allocated. Checks cover positive finite integers, a per-axis cap of 8192 px, and a total-pixel cap of 16M. WebGPU configurations additionally validate against adapter and device maxTextureDimension2D limits. Oversized or invalid configs fail with RenderDimensionLimitError before any allocation occurs. (#155)

Asset System

Asset loading now enforces hard dimension and payload caps before any GPU or buffer work.

  • AssetLimits enforces max 8192 px per side and 16M total pixels for images and indexed buffers; .btfont files are capped at 512 KiB JSON and 65535 glyphs. AssetLoader rejects oversized images before caching; SpriteSheet validates inputs before GPU allocation; BitmapFont validates JSON byte size, glyph count, glyph metrics, atlas bounds, and the font texture before any decode. Invalid assets surface AssetLimitError with clear messages. (#156)
  • Software renderer clips sprite source rectangles via clipSpriteSourceRect; partially out-of-bounds blits are clipped while preserving destination alignment. .btfont metadata fields size, lineHeight, and baseline are now coerced to positive finite numbers instead of trusted as-is. (#156)

Security

pnpm supply-chain posture tightened.

  • pnpm 10 hardening applied: minimum-release-age=10080 (7 days) blocks packages published less than a week ago; block-exotic-subdeps=true and strict-dep-builds=true are enabled. esbuild's post-install script is whitelisted via pnpm.allowBuilds instead of the deprecated ignoredBuiltDependencies key. (#154)

CI and Tooling

Third-party action tags replaced with pinned commit SHAs.

  • All GitHub Actions in ci.yml are now pinned to 40-character commit SHAs: actions/checkout, pnpm/action-setup, actions/setup-node, actions/upload-artifact, actions/github-script, and codecov/codecov-action. Jobs that lacked explicit permissions now declare permissions: contents: read. (#157)

Documentation

Architecture map updated to reflect the new utility modules.

  • CLAUDE.md "Where to Find Information" table and src/utils/ architecture index updated to include CanvasLayoutStyles.ts, RenderLimits.ts, and AssetLimits.ts with links to dimension constraint documentation. (#158)

Full Changelog: 1.0.3...1.0.4

Release 1.0.3

Choose a tag to compare

@vancura vancura released this 16 May 10:47
1.0.3
4b5fc87

Blit-Tech 1.0.3 is the first stable release of the engine, representing the full development arc from the 0.2.x prototype. The rendering stack is now palette-first end-to-end: draw calls write palette indices into an r8uint framebuffer, a PaletteResolveUpscalePass converts to RGBA just before output, and a Canvas 2D SoftwareRenderer kicks in automatically when WebGPU is unavailable. The input system is complete - pointer, keyboard, and gamepad all share the BTN_* bitmask API. The BT namespace has reached its stable shape, with configure(), init(), and IBlitTechDemo replacing their earlier names across the board.

API Changes

The BT namespace and demo lifecycle went through several rounds of renaming and hardening to reach their stable forms.

  • Breaking: IBlitTechGame renamed to IBlitTechDemo; bootstrap() now accepts a demo class rather than a game class; default canvas ID changed from game-canvas to blit-tech-canvas. (#38)
  • Breaking: BT.initialize() and IBlitTechDemo.initialize() renamed to init() across the entire engine lifecycle - BTAPI, IBlitTechDemo, Renderer, PrimitivePipeline, SpritePipeline, and initializeWebGPU() all shortened to their init equivalents. (#114)
  • Breaking: IBlitTechDemo.queryHardware() renamed to configure() and made optional; demos that omit it fall back to defaultConfig() (320x240 display, 640x480 canvas, 60 FPS, nearest-neighbor upscale). (#115)
  • BTAPI split into three focused modules: GameLoop owns the fixed-timestep accumulator and double-RAF canvas-ready delay; WebGPUContext owns adapter/device/context setup; BTAPI delegates to both. (#42)
  • BT.getActiveBackend() facade added and exposed on the BT namespace, returning 'webgpu', 'software', or null. (#132, #134)
  • BT.effectAdd, effectRemove, and effectClear wired through BTAPI for managing the post-process chain from demo code. (#107)
  • Opt-in dropped-frame detection added via HardwareSettings.detectDroppedFrames; GameLoop calibrates a 60-frame rolling baseline and fires an onFrameDrop callback when the browser misses vsync deadlines. (#105)
  • Obsolete WebGPU-only bootstrap helpers removed: checkWebGPUSupport, detectBrowser, getWebGPUInstructions, buildErrorPreviewEntries, and previewWebGPUErrors. (#133)
  • FullscreenEffect and FullscreenPixelEffect exported from the package entry point so npm consumers can build custom post-process passes without reaching into internal paths. (#149)
  • Engine initialization hardened against common beginner mistakes: runtime guards catch pre-bootstrap draw calls, missing await on asset loads, invalid drawPixel argument shapes, and missing update/render methods on the demo class. (#39, #122)
  • All engine error messages rewritten to beginner-friendly plain language: friendly second-person voice, contractions, concrete valid examples, actionable next steps. A shared src/utils/errorMessages.ts module prevents Bootstrap.ts and canvas error handling from drifting apart. (#120, #121, #122, #123, #124, #125)

Rendering

The rendering architecture moved from a single-file WebGPU pipeline to a modular, palette-first, backend-agnostic system.

  • Renderer.ts split into PrimitivePipeline.ts (colored geometry pipeline, batched palette-index writes) and SpritePipeline.ts (palette-indexed textured quads, nearest-neighbor, auto-batched by texture). (#40)
  • Primitive pipeline reworked to write palette indices instead of RGBA colors; all drawPixel, drawLine, and drawRect calls now resolve through the active palette on the GPU. (#87)
  • IRenderer interface introduced as a backend-agnostic contract; existing WebGPU implementation migrated to WebGpuRenderer implementing IRenderer; BTAPI holds IRenderer internally for backend selection. (#129)
  • SoftwareRenderer added as a Canvas 2D fallback backend: palette-first rendering, camera offsets, filled/outline rects, Bresenham lines, indexed sprite blits, and bitmap text. Post-process effects throw with a clear redirect to the WebGPU backend. Activates automatically when WebGPU init fails; force with HardwareSettings.renderer: 'software' or ?renderer=software. (#130)
  • BTAPI backend selection wired end-to-end; a dismissible in-canvas SoftwareTicker banner confirms fallback mode each frame. (#132)
  • r8uint logical framebuffer adopted for the WebGPU pipeline: the framebuffer stores one palette slot per pixel at logical resolution, pixel-tier effects run on the index buffer, then PaletteResolveUpscalePass applies the LUT and upscales to canvas resolution before display-tier effects. (#136)
  • Stackable post-process effect chain added with zero idle cost: pixel-tier effects (PixelGlitch, PixelMosaic) run on the r8uint index buffer; display-tier effects (BarrelDistortion, Scanlines, chromatic aberration, vignette) run on the upscaled RGBA output. Pre-configured crtPipBoy, amber, and green presets included. (#107, #109)
  • Frame capture to PNG via GPU readback added: BT.captureFrame() reads the framebuffer asynchronously and returns a PNG data URL; BT.downloadFrame() triggers a browser download. (#51)

Asset System

The palette and sprite asset pipeline grew from basic color storage to a fully integrated palette-first system.

  • Palette class introduced with 256-color support, VGA preset, serialization, named indices, GPU-compatible export, and a _dirty flag that auto-propagates mutations to the GPU without requiring a redundant BT.paletteSet() call at the end of every update. (#85, #90)
  • SpriteSheet.indexize() added for converting RGBA sprite pixels to palette indices via exact color matching; loadIndexed() convenience path chains load, loadColorsIntoPalette, and indexize in one call. (#89)
  • SpriteSheet.loadColorsIntoPalette() added: walks a PNG and registers every unique opaque color into the palette starting at startSlot, sorted darkest-first by Rec.601 luminance by default. (#103)
  • SpriteSheet.width, height getters and fullRect() helper added for whole-sheet source rectangles in sprite draw workflows. (#118)
  • SpriteSheet.fromIndexedPixels() and getIndexedPixels() added for CPU-side pixel access required by SoftwareRenderer. (#130)
  • PaletteEffect system introduced via PaletteEffectManager: CycleEffect (zero-alloc rotation), FadeEffect/FadeRangeEffect (timed interpolation with easing), FlashEffect (temporary color override with auto-restore), and paletteSwap (instant two-entry exchange). Runs after demo.render() and before GPU upload; skipped entirely when no effects are active. (#94)
  • Palette.applyHUD() convenience method added: writes six built-in HUD UI colors (white, bg, label, header, dim, code) contiguously from startSlot and registers hud_* name aliases. (#135)
  • Built-in 6x14 system font embedded in the library: 95 ASCII glyphs as bit-pattern data, synchronous atlas builder, and BT.systemPrint() / BT.systemPrintMeasure() for rendering text without loading external files. (#97, #98)
  • Asset load error messages rewritten with beginner guidance: status-specific font messages, hints for missing / or ./ path prefixes, and wrong file extension detection. (#119)

Input

A complete unified input system was built from scratch covering all three device types.

  • PointerInput subsystem added with four slots: slot 0 for mouse, slots 1-3 for touch/pen contacts in arrival order. BT.pointerPos, pointerDelta, pointerPosValid, pointerScrollDelta exposed; BTN_POINTER_A/B/C/D wired into buttonDown/buttonPressed/buttonReleased. Mouse buttons follow the RetroBlit canonical mapping (A=left, B=right, C=middle, D=back). (#110)
  • KeyboardInput subsystem added: KeyboardEvent.code state with edge detection, tick-based repeat, and beforeinput text accumulation into BT.inputString. BT.keyDown, keyPressed, and keyReleased exposed; BTN_UP..BTN_SELECT mapped for players 0-1 via DEFAULT_KEYBOARD_PLAYER0/1. Canvas gains tabIndex and auto-focus in bootstrap. (#111)
  • Keyboard face-button remapping added: BT.inputMapSet, inputMapGet, and inputMapReset let demos remap face buttons per player at runtime; DEFAULT_KEYBOARD_PLAYER0/1 serve as the out-of-box defaults. (#112)
  • GamepadInput subsystem added: polling-based per-frame snapshots, dead-zone-filtered axes, player connectivity helpers, and repeat-capable edge detection. BTN_* constants migrated to bit flags (powers of 2); keyboard and gamepad semantics merged for players 0 and 1; players 2 and 3 are gamepad-only. Default stick dead zone is 0.75. (#113)

Core and Utils

A set of standalone utilities was extracted and several engine-wide quality-of-life improvements landed.

  • Bootstrap.ts split into Bootstrap.ts (demo bootstrap utilities) and BootstrapHelpers.ts (canvas lookup and error display), reducing coupling and making each file independently testable. (#41)
  • Easing module added: EasingFunction type and applyEasing() supporting linear, ease-in, ease-out, and ease-in-out curves, used by FadeEffect and FadeRangeEffect. (#93)
  • Browser-specific WebGPU error guidance added: detectBrowser() parses the UA string and getWebGPUInstructions() maps each browser (Chrome, Edge, Firefox Nightly, Safari) to actionable flag pages, download links, and version requirements. Later consolidated into SoftwareRenderer auto-fallback and the errorMessages.ts module. (#100, #102)
  • BT.cameraClamp() added: wraps clampCameraToWorld(camera, worldSize, viewSize?) from a new CameraUtils module; viewSize defaults to BT.displaySize. (#116)
  • Color32.luminance getter (Rec.601 formula) added and SpriteSheet luminance sorting migrated to use it, removing duplicate inline formulas. (#126)
  • Color32.lerp(a, b, t) static helper added, delegating to the existing inst...
Read more