Loom Engine - v0.10.0
    Preparing search index...

    Loom Engine - v0.10.0

    Loom Engine

    Browser-first 2D / 2.5D game engine for TheWorldTable.ai. Canvas2D primary backend, ECS, render-graph stages, Director-bridge SSE integration. No external engine reuse - built from scratch in TypeScript.

    Repo: sadhaka/loom-engine. API docs: loom-engine.pages.dev. The design spec (LOOM-ENGINE-SPEC.md) lives in the consuming TheWorldTable.ai repo and is the canonical source for phase plans and architectural decisions.

    npm install @sadhaka/loom-engine
    

    Pre-alpha. ESM-only, browser-first. TypeScript types ship in the package (dist/index.d.ts). Node 18+ for the build toolchain; the runtime targets evergreen browsers (Canvas2D + Web Audio + EventSource).

    API reference (TypeDoc) - generated from the public surface in src/index.ts on every push to main: https://loom-engine.pages.dev/

    Build it locally with npm run docs (writes to ./docs/).

    See Docs deploy for the hosting chain and one-time activation steps (Cloudflare Pages, since GitHub Pages is unavailable on private repos for free user plans).

    // 1. Install
    // npm install @sadhaka/loom-engine
    import {
    Engine,
    SpriteRenderSystem,
    InputSystem,
    VeilBudgetSystem,
    SYSTEM_PHASE_INPUT,
    SYSTEM_PHASE_RENDER,
    } from '@sadhaka/loom-engine';

    // 2. Attach to a canvas. Engine.create wires Canvas2DDevice, World,
    // TransformPool, SpritePool, Time + Camera resources, and the
    // default SpriteRenderSystem in SYSTEM_PHASE_RENDER.
    var canvas = document.querySelector('canvas');
    var engine = Engine.create({ canvas: canvas });

    // 3. Register the systems your game needs. Order within a phase is
    // deterministic; phases run INPUT -> LOGIC -> PHYSICS -> ANIMATION
    // -> RENDER -> POST_RENDER per frame.
    engine.world.addSystem(new InputSystem(), SYSTEM_PHASE_INPUT);
    engine.world.addSystem(new VeilBudgetSystem(), SYSTEM_PHASE_INPUT);

    // 4. Drive the frame loop. engine.tick advances Time, beginFrame on
    // the device, world.update across all phases, endFrame.
    function tick(now) {
    engine.tick(now);
    requestAnimationFrame(tick);
    }
    requestAnimationFrame(tick);

    Pre-alpha, productized as of 0.10.0 (Phase 11B.3 - npm publish under MIT). Phases 0 through 9.3 + 11A.2 are shipped; the engine runs the public TheWorldTable.ai pre-alpha. Productization is a fund-raising and distribution decision, not a stability claim - the public API surface will evolve until 1.0.

    Phase Status Surface
    0 shipped scaffolding, package.json, tsconfig, PRIOR-ART log
    1 shipped Canvas2D iso renderer, camera, transform pool (SoA)
    2 shipped ECS World, system scheduler, resource registry, Engine facade, asset pipeline
    3 shipped clip-aware sprite-sheet manifests, AnimationStatePool, AnimationSystem
    4 shipped particle pool, emitter component, three-system VFX pipeline, additive blend
    5 shipped Web Audio bus mixer with VE-budget gating, unified keyboard / mouse / touch input
    6 shipped Director-bridge: SSE event-stream subscription, eventSourceFactory hook, snapshot-recovery
    7 shipped Survivor combat layer (projectile pool, hit resolution, damage application) ported onto Loom Engine
    8 shipped 2.5D ARPG hub-and-spoke per LOOM-CLASS-SYSTEM-SPEC, plaza narrator, mobile + touch input (virtual D-pad, tap-to-walk)
    9.1 shipped perf pass: alloc-churn fixes + bench harness
    9.3 shipped TypeDoc public-API site with auto-deploy
    11A.2 shipped docs hosting migrated to Cloudflare Pages
    11B.3 shipped MIT license + npm publish posture (this release)

    See LOOM-ENGINE-SPEC.md Section 7 for the full phase plan with effort estimates.

    npm install
    npm run build # tsc src/ -> dist/
    npm run build:demo # tsc demo/*.ts -> demo/*.js
    npm run build:all # both
    npm run watch # rebuild src on change
    npm run test # tsx tests/*.test.ts
    npm run clean # remove dist + compiled demo
    npm run build:all
    python -m http.server 8765
    # browse http://localhost:8765/demo/index.html

    Controls:

    • Arrow keys / WASD: pan camera
    • Click: burst 24 particles + play SFX chirp (after first click, AudioContext unlocks)
    • Hover: stats panel shows the iso tile under the cursor
    loom-engine/
    src/
    util/ math, color, typed-arrays
    components/ transform, sprite, particle-emitter
    renderer/ graphics-device, canvas2d-device, camera, iso-projection
    animation/ animation-clip, animation-state-pool
    asset/ sprite-sheet-loader
    audio/ audio-bus
    input/ input-manager
    systems/ sprite-render, animation, particle-{simulation,emitter,render}, input, veil-budget
    vfx/ particle-pool
    entity.ts entity allocator (32-bit handle, generation guard)
    world.ts ECS World class
    system.ts System interface + phase constants
    resources.ts ResourceRegistry + Time + VeilBudget
    engine.ts Engine facade
    index.ts public API barrel
    demo/ browser demo (one tile + animated knight + sparkles + click-to-burst)
    tests/ node-based smoke tests (tsx --test)
    assets/ placeholder game assets (knight walk-cycle PNG + JSON)
    tools/ helper scripts (gen-knight.py - Pillow generator)
    PRIOR-ART.md cumulative inspirations log (clean-room defense)
    package.json tsc + tsx as only dev deps
    tsconfig.json ES2022 strict + noUncheckedIndexedAccess
    dist/ tsc output (gitignored)
    node_modules/ npm install output (gitignored)
    • ECS over god-object scene graph - entities are 32-bit handles, components live in pools indexed by entity index
    • Structure-of-arrays for hot data (TransformPool, SpritePool, ParticlePool, ParticleEmitterPool, AnimationStatePool) - tight iteration over Float32Arrays, no per-entity object allocation
    • IGraphicsDevice abstraction with Canvas2D primary backend (WebGL2 reserved for Phase 2+ if profiling demands)
    • 6-phase scheduler - INPUT -> LOGIC -> PHYSICS -> ANIMATION -> RENDER -> POST_RENDER, deterministic registration order within each
    • VeilBudgetResource - the patent-defensible novelty hook. Single resource with particleBudget, audioBudget, shaderBudget, eventBudget. VeilBudgetSystem propagates updates to ParticlePool, AudioBus, etc. Director-bridge mutates the budget; subsystems read
    • Frame loop - engine.tick(now) runs in this order:
      1. compute dt (clamped to 1/30s)
      2. advance Time resource
      3. device.beginFrame
      4. world.update (walks all phases)
      5. device.endFrame
    import {
    Engine,
    // ECS
    POOL_TRANSFORM, POOL_SPRITE, POOL_ANIMATION, POOL_PARTICLE,
    POOL_EMITTER,
    TransformPool, SpritePool, AnimationStatePool, ParticlePool,
    ParticleEmitterPool,
    SYSTEM_PHASE_INPUT, SYSTEM_PHASE_LOGIC, SYSTEM_PHASE_PHYSICS,
    SYSTEM_PHASE_ANIMATION, SYSTEM_PHASE_RENDER, SYSTEM_PHASE_POST_RENDER,
    // Default systems
    AnimationSystem, SpriteRenderSystem,
    ParticleEmitterSystem, ParticleSimulationSystem, ParticleRenderSystem,
    InputSystem, VeilBudgetSystem,
    // Resources
    RESOURCE_TIME, RESOURCE_CAMERA, RESOURCE_DEVICE,
    RESOURCE_VEIL_BUDGET, RESOURCE_INPUT, RESOURCE_AUDIO_BUS,
    // Renderer
    Canvas2DDevice, ISO_TILE_WIDTH, ISO_TILE_HEIGHT,
    // Asset
    loadSpriteSheet, computeFrameIndex,
    // Audio
    AudioBus, AUDIO_BUDGET_AMBIENT_FLOOR, AUDIO_BUDGET_ESSENTIAL_FLOOR,
    // Input
    InputManager,
    // Math + color
    vec2, vec3, rect, clamp, lerp,
    hexToRgba, rgbaToCssString,
    COLOR_KNOT_STR, COLOR_KNOT_DEX, COLOR_KNOT_INT, COLOR_KNOT_CENTER,
    // Iso
    tileToIso, worldToIso, isoToTile, isoDepthKey,
    } from '@sadhaka/loom-engine';

    const engine = Engine.create({ canvas });
    engine.world.addSystem(new InputSystem(), SYSTEM_PHASE_INPUT);
    engine.world.addSystem(new VeilBudgetSystem(), SYSTEM_PHASE_INPUT);
    // ... game systems ...
    engine.world.addSystem(new SpriteRenderSystem(), SYSTEM_PHASE_RENDER);
    function tick(now: number) {
    engine.tick(now);
    requestAnimationFrame(tick);
    }
    requestAnimationFrame(tick);

    The engine's defensible novelty is in the Loom integration layer, not the rasterizer. Director-driven scene state, Veil Essence economy gating render budget, knot-aware encounter generation, event-sourced rendering. The renderer underneath uses public-domain techniques (sprite batching, isometric projection, ECS) implemented from scratch.

    See PRIOR-ART.md for the cumulative inspirations log (public talks, papers, OSS architecture - took / declined per source).

    Every architectural commit names its inspirations in plain text. No copy-paste from any external engine source. PRIOR-ART.md is the audit trail any future productization or patent dispute would lean on.

    208 / 208 tests pass on Node 24 via tsx --test. Coverage spans all twelve test files in tests/:

    • smoke.test.ts - public API barrel, version stamp
    • world.test.ts - ECS world, system scheduling, sprite pool, sprite render, time
    • asset-loader.test.ts - sprite-sheet manifest, frame stepper, error discriminator
    • animation.test.ts - animation clip math, state pool, AnimationSystem end-to-end
    • vfx.test.ts - particle pool, emitter pool, simulation, emitter system, veil budget
    • audio-input.test.ts - audio bus + ducking, input manager, input system, budget propagation
    • director.test.ts - SSE bridge, eventSourceFactory hook, scene-state derivation
    • combat.test.ts - hit resolution, damage application, knockback
    • projectile.test.ts - projectile pool, lifetime, collision
    • arpg.test.ts - ARPG hub-and-spoke, plaza narrator, encounter scheduling
    • snapshot-recovery.test.ts - SnapshotRecoveryHelper for Director reconnect
    • touch-input.test.ts - virtual D-pad, tap-to-walk, multi-touch arbitration

    Run via npm test. Each suite is fully node-based; no DOM dependency. Browser-only paths (Canvas2DDevice rasterization, AudioContext unlock, DOM event listeners) are exercised via the demo's preview verification, not unit tests.

    The TypeDoc site at https://loom-engine.pages.dev/ is served by Cloudflare Pages from the gh-pages branch of this repo. The chain:

    1. Push to main triggers .github/workflows/docs.yml
    2. Workflow runs npm ci, npm test, npm run docs:ci, then publishes ./docs-build/ to the gh-pages branch via peaceiris/actions-gh-pages
    3. Cloudflare Pages watches the gh-pages branch and auto-deploys on every push, typically within 1-2 min

    GitHub Pages itself is not used: the repo is private and free user plans do not include Pages on private repos. The 422 error from the Pages create API is the canonical signal: "Your current plan does not support GitHub Pages for this repository."

    If the Cloudflare Pages project is ever deleted or the repo is forked to a new owner, re-activate as follows:

    1. Cloudflare dashboard -> Workers & Pages -> Create -> Pages -> Connect to Git
    2. Authorize Cloudflare on the GitHub account that owns the repo (only the engine repo needs to be granted access)
    3. Select loom-engine, name the project loom-engine (default URL becomes loom-engine.pages.dev)
    4. Production branch: gh-pages
    5. Build command: leave empty (the gh-pages branch is already a built static site)
    6. Build output directory: / (root)
    7. Save and deploy. First deploy reads whatever is currently on gh-pages; subsequent deploys auto-trigger on push to that branch
    8. Optional: assign a custom domain (e.g. engine.theworldtable.ai) under the project's Custom domains tab. CF DNS for theworldtable.ai is already on the same account, so this is a one-click CNAME add

    If the workflow ever stops updating gh-pages (CF Pages will keep serving the last successful build but go stale), check gh run list --repo sadhaka/loom-engine --workflow=docs.yml.

    MIT. Copyright (c) 2026 Misha Mitiev.

    You can use the engine in commercial and non-commercial work without royalties. Attribution is appreciated but not required by the license. The patent-defensible novelty claims (see Patent strategy) are about specific architectural patterns documented in PRIOR-ART.md; they are independent of the source-code license and do not constrain typical engine reuse.

    Tagged releases publish to npm via .github/workflows/npm-publish.yml. The workflow runs npm test and npm run build, then npm publish --access public, when a tag matching v* is pushed to main. It needs the NPM_TOKEN repo secret to authenticate.

    Manual publish from a local checkout:

    npm login                       # one-time, npm account named sadhaka
    npm test # 208/208 must pass
    npm run build # tsc -> dist/
    npm publish --dry-run # inspect tarball contents first
    npm publish --access public # scoped packages default to private; flag is required

    prepublishOnly in package.json re-runs npm test && npm run build before any publish, so the dry-run and the final publish always rebuild from a clean source tree.

    This is a single-author project (Misha Mitiev) for TheWorldTable.ai. The MIT license permits forking and modification; pull requests are welcome but not actively triaged - the canonical roadmap is the spec file (LOOM-ENGINE-SPEC.md in the parent repo) and capacity is limited. For bug reports, file an issue with a minimal repro.