Skip to content

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 instance lerp. (#138)
  • Breaking: Color32.white(), black(), transparent(), and color primary statics converted from zero-argument methods to static getters returning frozen singletons. Call sites must drop parentheses (Color32.white not Color32.white()). Color32.gray(value) is unchanged. (#139)
  • Named color registry added to Color32: register, update, unregister, and resolve APIs with normalized key handling, duplicate/missing-name validation, and hud_* name aliases populated by Palette.applyHUD(). (#128)
  • Timer utility added for fixed-timestep interval checks in update loops; exported from the public API. (#127)
  • IndexedSpriteLoadResult exported as a named type so demos can annotate SpriteSheet.loadIndexed results without importing from internal paths. (#116)

Tests

Comprehensive test coverage was built from scratch across all four test tiers.

  • Full unit and integration test suite introduced covering Vector2i, Rect2i, Color32, GameLoop, AssetLoader, BitmapFont, SpriteSheet, Renderer, PrimitivePipeline, SpritePipeline, BootstrapHelpers, and IBlitTechDemo. src/__test__/webgpu-mock.ts provides reusable GPU mock factories. (#46, #48, #49)
  • Playwright visual regression suite introduced for pixel-level rendering verification: primitives, sprites, fonts, mixed scenes, post-process (baseline, CRT, CRT+bloom), camera, and software-mode baselines. (#47, #50)
  • CPU benchmark infrastructure added via Vitest bench: pnpm bench and pnpm bench:json scripts; Vector2i, Color32, Rect2i, and BitmapFont benchmark suites with #region organization. (#75, #76, #77, #78, #79)
  • Playwright GPU frame-time performance fixture added (later removed; see CI section). (#80)
  • Visual regression baselines refreshed after software renderer ticker changes. (#117, #141)
  • 80% unit test coverage threshold enforced via pnpm test:unit:coverage. (#50)

CI and Tooling

CI was rebuilt from scratch with DCO enforcement, benchmark automation, and a significant toolchain upgrade.

  • DCO sign-off enforcement added to CI; merge commits skipped in DCO validation. (#8, #10)
  • pnpm version pinned in CI; commitlint configured via environment variables rather than config drift. (#11)
  • Changesets infrastructure removed in favor of manual releases; .changeset/ directory and CHANGELOG.md deleted. (#37, #44)
  • Benchmark CI job added: runs Vitest bench on every push to main and on PRs labeled perf; uploads results as artifacts; compares against the latest main baseline; comments on the PR and fails on regressions greater than 25%. (#81)
  • Tier-2 GPU performance tests (Playwright frame-time benchmarks) removed after consistently timing out on hosted runners with no real GPU. perf-tier-1 CI label renamed to perf. (#82, #108)
  • Run Tests CI job enabled and test command fixed. (#88)
  • Major dev dependency upgrade: TypeScript pinned to 5.9.3 (to align with API Extractor for declaration rollup), Vite 8, ESLint 10, knip 6, cspell 10. (#104, #143)

Documentation

API reference guides (docs/api-core.md, docs/api-assets.md), palette guide, post-process effects guide, and testing documentation were introduced and continuously updated alongside every feature. Architecture maps in CLAUDE.md updated to reflect the final subsystem layout. Node minimum requirement aligned to >=22.18.0 across all docs and CI. (#83, #84, #95, #137, #142, #144, #145, #146, #147, #148)

Full Changelog: 0.2.0...1.0.3