Skip to content

Releases: zombico/mojulo

v3.1.1

Choose a tag to compare

@github-actions github-actions released this 16 Oct 21:20

The first published release of the 3.1 line. mojulo 3.1.0 reached npm from a stale build of 2026-10-10 and is
deprecated (npm refuses to unpublish a package others depend on); no mojulo-ui 3.1.0 exists. Where this changelog
and the code say 3.1.0 for a change, it first ships here.

3.1 turns mojulo toward games: a sentence becomes a playable game in a named genre, with its own world, its own
people and its own look, played in the browser, shipped as a desktop app or exported to an engine. The detailed
log behind this summary, theme by theme, is CHANGELOG-3.1.md.

Upgrading from 3.0

  • Update the Claude plugin. It runs npx -y mojulo@3.1.1; an unpinned npx -y mojulo moves to 3.1.1 once it
    is npm latest. mojulo-ui 3.1.1 is the matching dashboard. No new Node requirement and no new dependency.
  • Retired from the tool list, still callable by name, so a plan step, script or skill that names one keeps
    working:
    • the office wing: apps, runtime daemons, the contextmap, mcp-orbit, triggers and primitive binding
      (pack_runtime, pack_connected_services; see RETIRED_PACKS in lib/mcp/packs.js);
    • game projects (create_game_project and its siblings): a game's home is its gameworld, through design_game;
    • create_shoot_plan and update_shoot_plan: a shoot plan is forge_motion({ shoot }).
  • The dashboard is view-only. The copy-prompt buttons and the in-page edits (folders, gallery, stash) are gone;
    /apps, /mcp-skills, /map and /graph are removed, and /maker redirects home. Sign-in and Settings stay.
  • Stored rows that render differently (the exceptions to byte-identical re-render):
    • Heroes get a structured midsection by default; core: 'streamlined' restores the 3.0 body byte for byte.
    • Houses, stores, restaurants and malls were the mirror image of their plans in the World and every export;
      they are now built the way the plan reads. House floors are muted. SVG stills were mirrored too and are now
      right-handed.
    • The default animals' silhouettes and proportions are retuned; a minted animal redraws on its next read.
  • The recipe book ships inside the package. MOJULO_RECIPE_BOOK and the separate mojulo-recipe-book repo are
    deprecated: a clone still loads and warns at boot, until 4.0. MOJULO_BUNDLED_BOOK=off detaches the bundled book.
  • Every recipe records the mojulo version that minted it and that last edited it.

Games

  • The doors. design_game makes a game: quick: { goal } for a genre in one call, compose: { prompt } for a
    whole gameworld from a sentence, or the full gameworld (world bible, character book, levels). create_game
    audits it; export_game ships it.
  • Genres (quick.goal):
    • Fighting game: a side-view one-on-one fighter, local two-player, on 3D arena stages; every fighter is a
      character the character loop designs.
    • RPG battles: turn-based fights, a party of up to four switchable classes against groups of foes, with
      battle effects.
    • Arena shooter: deathmatch and team deathmatch against bots, first person or over the shoulder; the arena is
      made from words and one dial runs from realistic to stylized. Also a cover shooter along a trail.
    • Racing: a kart racer on a loop against rival drivers, and supercars on the grid (any paint, liveries, race
      moods, night driving). A true-scale divided freeway is the first piece of a driving world.
    • Flight: a flight sim (fly the ring gates and land), and air combat or a space shooter (shoot with a
      craft), on one flight model; craft are assembled from a shelf of aircraft and spacecraft parts.
    • Also: reach, collect, survive, avoid, sneak (guards and sightlines), rhythm, defend (tower defence), puzzle
      (block pushing) and farm.
  • Levels and places. Buildings and room kits play as levels (a house, a store, a school, a crypt); a city is
    planned on a board and played as levels; towns, fixed-camera walkmesh scenes, and chapters of the same world over
    time.
  • What a game carries: stats, inventory and shops, front menus, a cover and credits, scenes and two characters
    talking, a story bound to its world, weather, destruction, beams and explosions, and a soundtrack scored per level
    with action sounds.
  • Where it plays. A browser shell runs title, levels and result. export_game({ target: 'desktop' }) packs it
    as a desktop app with one seam for a store (Steam, GOG, Epic); mojulo ships no store SDK and uploads nothing.
    The Godot pack carries the event bus, combat and looks, and is the most complete engine translation.
  • What can be made next. The cosmos cube describes a game as six facets, and its sweep measures what the builder
    can and cannot make yet (docs/tool-surface/hillclimb.md).

Characters

  • create_character (as: 'hero' | 'creature' | 'animal') is the first door; update_character revises any
    part.
  • Each game's own people. The Egocube rolls a cast from a game's setting and era: modern and future gear,
    mascots that walk as well as drive, and a palette that keeps its world's era.
  • The look. An anime hero can be drawn painterly, with a painted face that animates. A stylism solver works
    silhouette, then value, then detail. Clothes fit the body, armour and creature apparel read as what they are,
    hair grows as locks, and light is painted onto the surface. Each character is drawn for its target screen.
  • Motion. Showcase poses, ready stances, strikes, sitting and rising, vaults and carrying on the hero door, and
    a walk calibrated against measured human gait.
  • Animals. Sea creatures, insects and other arthropods that move, behaviours said once and done per species,
    and an encyclopedia entry per species.

Worlds and the look

  • The geocube is a level's look as data. The color theorist solves its palette from what the world is
    for (land, sky, HUD and cover from one solve), and vertex contrast lights the whole world.
  • Stage kits in the sixth-gen manner: an isekai meadow, bamboo and sakura groves, a research lab and night, with
    live grass, wind, fire and water.
  • Historic cultures as walkable historic worlds from cited records, with period music and people: Sumer,
    Hellenistic Lindos, Qin Xianyang, Pompeii and Giza. Later eras through the same historic solver.
  • Buildings. The interior kind furnishes one room of a house; houses gain finishes and composed or true-size
    furniture; a structure book documents a house, store, restaurant, mall or school.

Making things

  • Solids. The fabricator resolves a part's job from a shelf of standard parts and sizes it; measure_solid
    reads strength (bending, tension, print infill); mechanisms move with mass and friction; the scad kind gains a
    pinned library for threads, gears and text, and warns where they used to fail silently. Exploded views and teardowns.
  • Asset receipts. sign_asset and verify_asset sign an exported file with a per-install creator key.
  • Audio, film and diagrams. Orchestral strings and brass, a robot-rock style card; a world's shoot plan and a
    Blender film pack from forge_motion; flow charts lay themselves out.

The agent's surface

  • One door per intent. A new capability is an argument and a routing card on the tool that owns its intent,
    not a new listed tool. A pack opens to a menu; create_sketch and cook list routing only and read their manuals
    on demand. A routing eval with a held-out set measures where requests land.
  • Model tiering. An agent task names how much judgment it needs (ride, fill, judge, design), never a
    model.
  • Self-description. Mojulo describes itself by what it makes: things to Play, Print, Read, Hear and Observe.

For contributors

  • Test tiers: test:critical is quick again, the game and world suites that walk levels in headless Chrome are
    test:e2e, and range sweeps are *.deep.test.js. A test picks one case per procedure, never a whole catalog.

v3.1.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 19:37

3.1 turns mojulo toward games: a sentence becomes a playable game in a named genre, with its own world, its own
people and its own look, played in the browser, shipped as a desktop app or exported to an engine. The detailed
log behind this summary, theme by theme, is CHANGELOG-3.1.md.

Upgrading from 3.0

  • Update the Claude plugin. It runs npx -y mojulo@3.1.0; an unpinned npx -y mojulo moves to 3.1.0 once it
    is npm latest. mojulo-ui 3.1.0 is the matching dashboard. No new Node requirement and no new dependency.
  • Retired from the tool list, still callable by name, so a plan step, script or skill that names one keeps
    working:
    • the office wing: apps, runtime daemons, the contextmap, mcp-orbit, triggers and primitive binding
      (pack_runtime, pack_connected_services; see RETIRED_PACKS in lib/mcp/packs.js);
    • game projects (create_game_project and its siblings): a game's home is its gameworld, through design_game;
    • create_shoot_plan and update_shoot_plan: a shoot plan is forge_motion({ shoot }).
  • The dashboard is view-only. The copy-prompt buttons and the in-page edits (folders, gallery, stash) are gone;
    /apps, /mcp-skills, /map and /graph are removed, and /maker redirects home. Sign-in and Settings stay.
  • Stored rows that render differently (the exceptions to byte-identical re-render):
    • Heroes get a structured midsection by default; core: 'streamlined' restores the 3.0 body byte for byte.
    • Houses, stores, restaurants and malls were the mirror image of their plans in the World and every export;
      they are now built the way the plan reads. House floors are muted. SVG stills were mirrored too and are now
      right-handed.
    • The default animals' silhouettes and proportions are retuned; a minted animal redraws on its next read.
  • The recipe book ships inside the package. MOJULO_RECIPE_BOOK and the separate mojulo-recipe-book repo are
    deprecated: a clone still loads and warns at boot, until 4.0. MOJULO_BUNDLED_BOOK=off detaches the bundled book.
  • Every recipe records the mojulo version that minted it and that last edited it.

Games

  • The doors. design_game makes a game: quick: { goal } for a genre in one call, compose: { prompt } for a
    whole gameworld from a sentence, or the full gameworld (world bible, character book, levels). create_game
    audits it; export_game ships it.
  • Genres (quick.goal):
    • Fighting game: a side-view one-on-one fighter, local two-player, on 3D arena stages; every fighter is a
      character the character loop designs.
    • RPG battles: turn-based fights, a party of up to four switchable classes against groups of foes, with
      battle effects.
    • Arena shooter: deathmatch and team deathmatch against bots, first person or over the shoulder; the arena is
      made from words and one dial runs from realistic to stylized. Also a cover shooter along a trail.
    • Racing: a kart racer on a loop against rival drivers, and supercars on the grid (any paint, liveries, race
      moods, night driving). A true-scale divided freeway is the first piece of a driving world.
    • Flight: a flight sim (fly the ring gates and land), and air combat or a space shooter (shoot with a
      craft), on one flight model; craft are assembled from a shelf of aircraft and spacecraft parts.
    • Also: reach, collect, survive, avoid, sneak (guards and sightlines), rhythm, defend (tower defence), puzzle
      (block pushing) and farm.
  • Levels and places. Buildings and room kits play as levels (a house, a store, a school, a crypt); a city is
    planned on a board and played as levels; towns, fixed-camera walkmesh scenes, and chapters of the same world over
    time.
  • What a game carries: stats, inventory and shops, front menus, a cover and credits, scenes and two characters
    talking, a story bound to its world, weather, destruction, beams and explosions, and a soundtrack scored per level
    with action sounds.
  • Where it plays. A browser shell runs title, levels and result. export_game({ target: 'desktop' }) packs it
    as a desktop app with one seam for a store (Steam, GOG, Epic); mojulo ships no store SDK and uploads nothing.
    The Godot pack carries the event bus, combat and looks, and is the most complete engine translation.
  • What can be made next. The cosmos cube describes a game as six facets, and its sweep measures what the builder
    can and cannot make yet (docs/tool-surface/hillclimb.md).

Characters

  • create_character (as: 'hero' | 'creature' | 'animal') is the first door; update_character revises any
    part.
  • Each game's own people. The Egocube rolls a cast from a game's setting and era: modern and future gear,
    mascots that walk as well as drive, and a palette that keeps its world's era.
  • The look. An anime hero can be drawn painterly, with a painted face that animates. A stylism solver works
    silhouette, then value, then detail. Clothes fit the body, armour and creature apparel read as what they are,
    hair grows as locks, and light is painted onto the surface. Each character is drawn for its target screen.
  • Motion. Showcase poses, ready stances, strikes, sitting and rising, vaults and carrying on the hero door, and
    a walk calibrated against measured human gait.
  • Animals. Sea creatures, insects and other arthropods that move, behaviours said once and done per species,
    and an encyclopedia entry per species.

Worlds and the look

  • The geocube is a level's look as data. The color theorist solves its palette from what the world is
    for (land, sky, HUD and cover from one solve), and vertex contrast lights the whole world.
  • Stage kits in the sixth-gen manner: an isekai meadow, bamboo and sakura groves, a research lab and night, with
    live grass, wind, fire and water.
  • Historic cultures as walkable historic worlds from cited records, with period music and people: Sumer,
    Hellenistic Lindos, Qin Xianyang, Pompeii and Giza. Later eras through the same historic solver.
  • Buildings. The interior kind furnishes one room of a house; houses gain finishes and composed or true-size
    furniture; a structure book documents a house, store, restaurant, mall or school.

Making things

  • Solids. The fabricator resolves a part's job from a shelf of standard parts and sizes it; measure_solid
    reads strength (bending, tension, print infill); mechanisms move with mass and friction; the scad kind gains a
    pinned library for threads, gears and text, and warns where they used to fail silently. Exploded views and teardowns.
  • Asset receipts. sign_asset and verify_asset sign an exported file with a per-install creator key.
  • Audio, film and diagrams. Orchestral strings and brass, a robot-rock style card; a world's shoot plan and a
    Blender film pack from forge_motion; flow charts lay themselves out.

The agent's surface

  • One door per intent. A new capability is an argument and a routing card on the tool that owns its intent,
    not a new listed tool. A pack opens to a menu; create_sketch and cook list routing only and read their manuals
    on demand. A routing eval with a held-out set measures where requests land.
  • Model tiering. An agent task names how much judgment it needs (ride, fill, judge, design), never a
    model.
  • Self-description. Mojulo describes itself by what it makes: things to Play, Print, Read, Hear and Observe.

For contributors

  • Test tiers: test:critical is quick again, the game and world suites that walk levels in headless Chrome are
    test:e2e, and range sweeps are *.deep.test.js. A test picks one case per procedure, never a whole catalog.

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 05:16

Upgrading from 2.x

  • 3.0.0 is the release after 2.1.0. A 2.2 was prepared and never published; everything it carried
    is in this release. Removing the chatbot factory is why this is a major version.
  • Node 22.14 or newer. 2.x asked for 22.12, but on Node 22.12 and 22.13 the database library
    (better-sqlite3) crashes the process the first time it opens the database, with no message; 2.1.0
    crashes the same way. 3.0 checks the version at start-up and says which Node to install.
  • The 2.x line is unmaintained. No 2.x release will be patched, and running one is not
    recommended. 2.1.0 and the bot image it deploys (mojulo-bot 0.5.1) have known security issues: an
    open relay on deployed bots (/api/send-webhook), SSRF in upload_document_from_url, path
    traversal in the Office-document parser, Fly credentials in the machine environment, dashboard DNS
    rebinding and cross-site writes, a revoked delegate's dashboard session that outlives the
    revocation, a delegate's dashboard session that holds the operator's authority, and an
    update_sketch patch that reaches the prototype chain
    (SECURITY.md). Bots
    already deployed from 2.x run on their own, on that image, until you take them down.
  • Unpinned installs move to 3.0 on their next start once 3.0.0 is npm latest. A host that runs
    npx -y mojulo (every config 2.x mojulo init wrote, the README's manual lines, and the Claude
    plugin up to 2.0.1) installs 3.0.0 on its next start, whether or not the plugin was updated. The
    3.0 plugin runs npx -y mojulo@3.0.0; update it, and remove any mojulo init or claude mcp add
    registration beside it (two registrations run two servers). Exact pins and a global
    npm i -g mojulo stay on their version until you change them. The first start downloads about
    79 MB (the package and its dependencies); if the host gives up, start Claude Code with
    MCP_TIMEOUT=60000, or run npx -y mojulo@3.0.0 --help once in a terminal. Restart every host
    afterwards so no 2.x server keeps running against the same ~/.mojulo.
  • Saved provider keys become unreadable to 2.x. With API_KEY_ENCRYPTION_KEY unset (the
    default), the first time a 3.0 process reads saved keys (mojulo-config, list included;
    mojulo init's key prompt; the dashboard's key settings; mint_solid via:'prompt' without an
    inline apiKey), it re-encrypts every key a 2.x install saved, the Fly token included, under a
    per-install key at $MOJULO_HOME/secret.key. 2.x cannot decrypt them after that; the change is
    one way. Back up secret.key together with the database: without it 3.0 cannot read the keys
    either.
  • What left, and what it left behind. The chatbot factory is no longer part of mojulo as of 3.0
    and is moving to its own project. Earlier 2.x versions that include it are unmaintained and have
    known security issues. Its tools, packs and dashboard pages are not in 3.0; calling one of its
    tools, mojulo install chatbot or an artifact_materialization commit does nothing and answers
    with that notice. Its tables (deployments, modular_sessions, mcp_jobs) and their rows, and the
    packs/chatbot marker, stay in ~/.mojulo, inert: 3.0 does not use them, and you may delete them.
    ~/.mojulo/data/artifacts/ holds each old bot's .env with its provider key in plain text:
    delete it.
  • Downloaded helpers and draft figure specs move out of the npx cache. 2.1.0 kept Chrome for
    Testing, ffmpeg, gallery stills, turntable strips and draft figure specs inside its package folder;
    3.0 keeps them under $MOJULO_HOME. An unpinned upgrade replaces that folder, so they are not
    carried over; pending figure specs are lost unless another version's npx folder still holds them
    (3.0 copies those across once). The browser (now build 154) is no longer fetched in the
    background: with no browser installed, gallery thumbnails stay blank until one is available (an
    installed one, MOJULO_CHROMIUM, or, outside the Claude plugin build, an explicit render's
    download).
    @puppeteer/browsers 3 has no proxy support, so a host behind an HTTP proxy sets MOJULO_CHROMIUM.
  • Other changes a 2.x setup can notice.
    • mojulo init changes nothing without --yes when stdin is not a terminal: it prints its plan and
      exits 2. With the Claude plugin installed it leaves Claude Code to the plugin.
    • The dashboard is its own package, mojulo-ui (npx -y mojulo-ui); npx -y -p mojulo mojulo-ui
      downloads the matching version on first use (MOJULO_UI_NO_FETCH=1 refuses). It answers 403 to
      a Host other than loopback, MOJULO_UI_HOST or a name in MOJULO_UI_ALLOWED_HOSTS, and to a
      cross-site write, so a LAN, proxy or tunnel setup needs MOJULO_UI_ALLOWED_HOSTS.
    • mint_solid via:'prompt' requires provider: nothing chooses the provider for you any more.
      The key is apiKey, the saved key named by apiKeyId, or else your saved key for the provider
      you name.
    • Exported World pages are self-contained by default again (world.html, no third-party fetch);
      cdn: true writes world.cdn.html, and world.offline.html is no longer written.
    • Stored floorplan and condo-complex rows that relied on the old room defaults re-render
      roomier, and the furnish pass doors sealed rooms, parks interior doors open against the wall and
      moves wall pieces out of a door's way
      (Room livability,
      Furniture audit).
    • chatbot or ops in MOJULO_PACKS is ignored, so MOJULO_PACKS=chatbot alone now leaves the
      creative tools on.
    • MOJULO_MCP_TELEMETRY=off stops only the local tool-call log, no longer the tool timeout, and
      MOJULO_MCP_TOOL_TIMEOUT_MS can raise a tool's budget but not lower it. Render tools get 600 s.
    • mojulo install creative installs nothing (the creative pack always ships), and
      mojulo install recall now works from npm and npx. With the runtime already installed and the
      search model missing, it fetches the model instead of reporting nothing to do.
    • A delegate (roles pack) signed in to the dashboard reads and no longer writes (below).
    • A city minted from now on wears the round street kit (below); stored cities are unchanged, and
      elements: { roundKit: false } keeps the block kit.
    • A material object whose metal is a name ({ metal: 'steel', finish: 'brushed' }) is a metal
      surface (below); beside base or preset it is refused. The shelf's numeric metal: 1 is
      unchanged.

What 3.0 is

  • A 3D compiler for agents. Your agent builds objects, walkable worlds and games by conversation
    as small deterministic recipes on your machine, compiled back to the same geometry (byte for byte on
    the same platform) and exported to STL / 3MF, glTF, OpenUSD, IFC4, self-contained HTML, Godot,
    Blender, Unity and Unreal. The bot factory, which never fit that, is gone.

Determinism

  • What "the same geometry" means. A recipe compiles to the same geometry every time: byte for byte
    on the same platform (OS, CPU and Node version), and to within floating-point rounding on any other.
    V8's Math is not one function everywhere: its arm64 builds round sin, cos, exp, atan2 and the
    rest differently from x64, and since Node 24 Math.pow and ** call the platform's C library. An
    embedded texture PNG can also differ in its compressed bytes between Node builds (Homebrew's Node links
    a different zlib), never in its pixels.
  • 3.0's generators go further. The vegetation engine, terrain worlds, the anime head and hero, stores
    and construction, metro and canal cities, the metro refacade and the Tian Tan Buddha take their
    transcendental functions from a deterministic math module (fdlibm in plain double arithmetic), so their
    pinned outputs are the same bytes on Linux and macOS, x64 and arm64, Node 22 and 24, and CI checks them
    on every runner. The older helpers they reach switch to it only inside a 3.0 build, so nothing minted
    with 2.x changes. A few older helpers still use the engine's Math (specular shading, sRGB conversion,
    some textures, roads, the metro World's walkers and cars).

Lean install

  • 3.0.0 against the published 2.1.0, one method. A cold start with an empty npm cache and a
    fresh HOME and MOJULO_HOME (npm exec --package=<tarball> -- mojulo) through a local registry
    stand-in, timed from spawn to the initialize answer, on one macOS arm64 machine (M1 Max). Sizes
    are decimal MB; the installed and downloaded rows include the package itself, and "downloaded" is
    the unique tarballs plus the package metadata, estimated gzipped.

    2.1.0 3.0.0
    npm tarball 31.2 MB 6.7 MB
    Unpacked package 121.7 MB 21.3 MB
    Installed, package and dependencies 539 MB, 426 packages 227 MB, 153 packages
    Downloaded on a cold start, package included about 182 MB about 79 MB
    Cold start to initialize, median (p90) 12.7 s (15.7 s), 14 runs 4.5 s (4.6 s), 6 runs
  • Machine caveat. The two timings come from separate sessions on a busy machine: 2.1.0 on
    2026-09-27 (load average 3 to 12), 3.0.0 on 2026-09-30 (7 to 11). In the 3.0.0 session, runs of a
    pre-release 3.0 candidate interleaved with the release's took a median 6.1 s: the release installs
    one copy of sharp instead of two, 26 packages fewer. Trust the sizes more than the seconds, and
    expect the real registry to be slower than the stand-in. npm also keeps the downloaded tarballs in
    its cache, about 140 MB for 3.0.0. 2.1.0's package carried the dashboard build; 3.0.0's dashboard
    is mojulo-ui (12.7 MB packed, 57.0 MB unpacked), fetched only when it is opened.

  • **Th...

Read more

v2.1.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 03:51

CLI orientation

  • mojulo orient — the shell's initialize. An MCP client is handed the server instructions at
    connect (what mojulo is, the five paradigms, "call forward_context when unsure"), and the host
    injects them into the agent's context for free. A shell caller (npx mojulo call … in an agent's
    box with no MCP client) never sends initialize, so nothing told it to orient, and every body it
    read — forward_context, a pack unveil, a vocab card — was written in MCP call grammar with no
    translation to the bin. The local tool ledger showed the gap: sessions driven through mojulo call
    called forward_context once in a month and never first, while the Grok shell run that piped
    JSON-RPC frames into the same bin oriented before minting. mojulo orient prints the same preamble
    byte for byte, the packs mechanic (the CLI listing is the packs shape), and a CLI addendum that
    translates the grammar (tool({…}) → mojulo call tool --json, pack_x({tool,args}) →
    mojulo pack_x tool), points at mojulo call forward_context, and names MOJULO_HOST (the ids
    come from the host registry) and MOJULO_SURFACE=box, the two env vars that stand in for
    clientInfo. All three blocks live in server.js; the CLI still authors no orientation prose of
    its own beyond a one-line footer on mojulo tools and the usage line. orient is one more
    reserved word on the bin. Both READMEs, AGENTS.md and CLAUDE.md name it; the box paragraph now
    tells the operator to have the agent run it first.
  • Recorded stopping point. Surface-aware grammar lines inside forward_context and the pack
    unveils (a surface field on the CLI context rather than branching on the cli session id) were
    considered and left out: the pointer alone may be enough, and that change opens the orientation
    bodies' own pins.

Clouds

  • A cloud deck over any world that has fog boxes. The 2026-09-17 smoke-and-cloud spike found that
    a lit, Worley-eroded deck composited through the existing effects[] overlay reads as cumulus with
    lit tops and shaded troughs, and recommended it as a clouds setting; nothing had been wired since.
    Now clouds: true (or a tuning object) on a kind whose registry descriptor declares fogBoxes
    (fractal-city, edifices, subway-station, controllable) adds a deck layer to the live /world
    page. Two modes, chosen by the operator because the frame budget is theirs. undershot (the default)
    is one plane intersection per pixel, no march: coverage and erosion sampled where the view ray meets
    the deck's base (camera below) or top (camera above), slab depth turned into alpha, lit by the sun
    transmitted through the slab plus a normalised phase lobe and a ground-to-sky ambient, faded toward
    the horizon. { mode: 'full' } is the spike's round-3 composite: a volumetric march over the band
    with a sun light-march, dual-lobe Henyey-Greenstein, powder and ambient, and when base is set low
    enough that a solid crosses the band, clipped behind it by the same box occluder the fog uses.
    Tuning: base, top (default: the band starts above the tallest occluder box, so nothing solid
    crosses it), coverage 0..1, sun (defaults to the
    world's light.toLight), color, density, scale, drift, maxDist, fade, steps. The layer
    publishes its band as meta.base / meta.top so cameras and walk eyes can stay out of it. A bad
    tuning object throws by name, so compose_world's render check refuses the mint instead of storing
    a page that fails. Absent ⇒ zero bytes; stills and engine exports ignore it, like fog.
    lib/graph/effects/effects-clouds.js (composeCloudDeck) is fog's sibling; the shared GLSL the
    spike wrote inline (trilinear value noise, fbm, Worley, Henyey-Greenstein, the light march, the
    energy rules) is now lib/graph/effects/volume-lib.js for the next effect to reuse. compose_world
    with base city stores clouds beside fog; the city card documents it.
  • Painted landscapes take the deck too. A registry descriptor can now say clouds: true instead of
    declaring fogBoxes: the kind has no solids to clip, and the band clears the mesh's tallest vertex
    instead (the deck composer's new floor, which every deck also honours beside its boxes). The
    painted-landscape kind declares it, create_painted_landscape and compose_world with base
    painted-landscape store clouds, and the landscape card documents it. The ?render=raymarch
    backend returns before the channel layer and carries no deck, like every other channel.

Library

  • The Library is a folder list, not a gallery. /library no longer renders anything: the
    split-view preview pane, the per-shelf rooms (board, wall, cast, masonry, rows) and the turntable
    tiles of the full folder view are gone from the page. A left rail lists All sketches, Unfiled and
    every folder, then the shelves with their whole-store counts; the body is one table (name with its
    plan/research tags, kind, minted date, ref) with open / move / delete on hover; a strip under the
    table shows the focused row's facts and actions (rename in place, open the detail page in a new
    tab, SVG/PNG download where the kind has a still form, move, delete) and turns into the bulk bar
    when rows are checked. The Name, Kind and Minted headers sort: newest first by default, a column's
    first click sorts the way it reads (names A to Z, kinds grouped, dates newest first), a second
    flips it, ties break newest first, and the choice is remembered per browser. Seeing a sketch is what /sketches/<ref> is for. The shelf model,
    the sketch APIs and the beats gallery are untouched; components/LibraryBrowser.jsx is the new
    body and SketchGallery now exports its folder modals, download links and icons so the two share
    them. New library.* strings in every locale.
  • The dashboard stops serving stale rows. Two causes, both fixed. Recency was mint time only:
    sketches had no edit timestamp, so a recipe iterated in place with update_sketch or
    edit_solid never rose on the bench or in a strip. A sketches.updated_at column (idempotent
    migration, backfilled from created_at) is stamped on mint, retitle and recipe edit, never on a
    folder move or bucket pin; SketchRepository.recent() and newestByBucket() order by last touched,
    so /api/home's head and /api/home/floor's strips and "picked up recently" follow what was
    actually just touched. Rows carry updatedAt beside createdAt. And the floor's two SWR reads had
    focus revalidation switched off, so a tab left open while the agent worked never refreshed: they now
    re-read on focus and every thirty seconds while visible, keeping the previous rows until the new
    ones land. The floor's cards, hero and swatches are unchanged. The library keeps its mint-time
    order; its Minted column is honest about what it sorts.
  • The floor's 3D zone names the workshop's real contexts, and the bench is gone. Two shelves
    join the shelf model. turntables is the illustration bucket's css3d-turntable and
    subway-station kinds: live scenes the viewport orbits, and by far the commonest thing in that
    bucket, which the Images shelf had been hiding on the 2D side of the floor. views is the object
    bucket minus the solids (workbench, scad, assembler, the polygomer's manji-tree): the
    scientific and educational studies, orbited and read, never printed. Models is now the solids alone,
    Images is what is left after characters and turntables, and Scenes is labelled Worlds. The 3D zone
    reads worlds · turntables · models · views · characters · materials; the 2D zone is unchanged; the
    front door's zone counts follow. The floor's bench (the live hero frame and "picked up recently")
    is removed: the head is still what ?ref= and the empty-workshop check read, but nothing on the
    floor mounts a live frame any more. The six per-room "open the … board" links collapse to one
    "Open in the library" string. Shelf and zone tests re-pinned; every locale re-minted.

Docs

  • README and npm README name every box that has run mojulo. The agent-box tier now lists the
    hosts that have installed the package in their throwaway Linux box and driven it to a mint and an
    export: the Claude app (macOS, Windows, web, iOS; Claude Code on the web is that app), ChatGPT
    work mode with Codex, Grok chat, and Meta Muse (iOS, web, macOS app, one session across all
    three). The
    Quickstart splits the box comment by what the box has: an MCP client (init with defaults) or a
    shell only (npx mojulo call). The package.json description carries the same list. The
    "verified on" sentence in both READMEs says which platform verified what, instead of folding the
    box runs into a Linux CI clause.
  • A who-ran-it-where matrix under "Where it runs" in both READMEs: agents down, environments
    across (macOS, Windows, the web agent's Linux box), a green tick for a persistent install on the
    operator's machine and a blue tick for an ephemeral box run (the same glyph, two colours:
    docs/images/tick-green.svg and tick-blue.svg, inline images because markdown cannot colour
    text; the npm README points at the raw GitHub URLs), blank for not yet verified. Cells come from
    the recorded runs: Claude Code and the Claude app on macOS and Windows, Codex, Grok Build and
    Hermes Agent on macOS, and the box runs above. One row per vendor: Codex sits in the ChatGPT row, Grok Build and
    Grok chat share a row, and Claude Code on the web is the Claude app's box. There is no Linux
    column: a Linux run is the box.

v2.0.9

Choose a tag to compare

@github-actions github-actions released this 23 Sep 05:23

cdn-default — the exported World page ships CDN-first

  • The exported World page loads three.js from the CDN by default. export_model({ format: 'html' }) now writes a world.html whose importmap points at the pinned jsdelivr path;
    cdn: false writes the self-contained world.offline.html that opens from file:// with no
    server and no network. The names follow the content, so the default export carries the obvious
    name and nothing silently changes meaning inside the bundle.
  • Why the flip: the constraint was never bytes. 2.0.8 shipped cdn: true as a size lever
    ("for a page door with a byte limit"). The artifact host actually refuses data: scripts under
    its CSP script-src at ANY size — a 2 MiB page clears the 16 MiB door and still renders black.
    A CDN page fails only for a viewer with no network; an inline page fails on every web host,
    every time. The default now points at the rarer failure.
  • The handoff note says it before it bites. pageSentence's artifact case no longer hides
    the CDN advisory inside the over-budget branch, so a page that FITS the door is no longer told
    it is fine when it is about to be blocked. The handoff.box.cdns field that claude-code.json
    has declared since 2.0.8 is finally read instead of merely asserted.
  • The bundle stays offline on purpose. export_model({ format: 'bundle' }) opts its page
    back into the self-contained build: the zip is a download, unzipped and opened from disk, so
    the README's "open world.html from disk" promise still holds inside the archive.
  • export_game gains the same cdn option, defaulting on, threaded to every page role
    (level, menu, preview). An exported game could not be published as an artifact page at all
    before this — emitWorldPage hardcoded inline: true with no flag to reach.

v2.0.8

Choose a tag to compare

@github-actions github-actions released this 22 Sep 20:31

Remote worker exports

  • Every written export says the next move on THIS host. When mojulo runs inside the agent's
    own box (Claude Code on the web, a Codex cloud task, Grok chat's sandbox) the outcome folder is
    on a disk the operator never sees and download_url is loopback; 2.0.7 stopped there. Now
    export_model, export_game and cook results carry handoff — { host, surface, door, next, caveats, verified }, one sentence in the host's own words (publish with the Artifact tool, commit
    the folder and let the PR carry it, hand back a file card, or open the dashboard) — and fits
    against the host's byte limit. On every surface the host model publishes and the server never
    does, so this is a note, never a refusal.
  • Host profiles carry a handoff door table. lib/mcp/hosts/*.json gains handoff: a
    local row (the operator's machine) and a box row (the host's remote box: page door, file
    door, pageMaxBytes, fileMaxBytes, the download-extension allowlist, allowed CDNs, egress,
    ephemeral), plus verified: field | docs | inferred. clientInfo cannot tell Claude Code
    local from the web box from Claude Desktop, so one profile holds both rows and the note states
    both when MOJULO_SURFACE=box|local is unset. New grok-chat profile (wire manual, box only,
    inferred) for the shell-only sandbox that ran the 2026-09-21 city; MOJULO_HOST=<id> picks a
    profile for the CLI, which never sees an initialize. The registry validates the door vocabulary.
  • export_model({ format: 'bundle' }) writes <ref>.zip into the outcome folder — world.html,
    model.glb, model.stl for literal-scale kinds (the print advisories ride the README),
    recipe.json, README.md — deterministic bytes (fixed entry time, sorted names, the README's
    clock stamp dropped), so the same recipe zips byte-identical on any host. It is the one file
    every door accepts: Claude's artifact download allowlist carries zip and not glb, a file card
    and a PR carry one binary. Beside the zip it writes <ref>.courier.html: the zip embedded in one
    small page with a Save button that goes through the viewer's downloads capability, because on
    Claude Code on the web the file door is a page (field, 2026-09-22: the artifact host refuses .glb
    and any archive as a supporting file and blocks page-initiated downloads); opened from file:// the
    same button is a plain download. The outcomes route serves .zip, .glb, .gltf, .stl, .3mf,
    .usdz, .usda with their real types instead of octet-stream.
  • export_model({ format: 'html', cdn: true }) loads three.js from the pinned jsdelivr path the
    emitter already carried (emit-util.js) instead of ~1 MB of inline data: modules — for a page
    door with a byte limit that allows the CDN (Claude's does). Needs network, so not file://; the
    note says so. It writes world.cdn.html, never over the world.html the README promises opens
    from disk. Off by default and the self-contained page is byte-identical to 2.0.7. Measured
    2026-09-22: the seed-91 city page is 8.6 MiB (1,028 KB of it three.js), a dungeon 2.3 MiB, a
    standing figure 4.5 MiB — every fixture already fits the 16 MiB door; geometry-as-embedded-GLB
    (~2× smaller) is deferred because it changes the page runtime.
  • structuredContent on the wire. A tool that returns _structured: true beside its body
    gets the WHOLE body as structuredContent through toMcpToolResult; the text block never
    carries the field. Field, 2026-09-22: Claude Code renders structuredContent in place of the
    text block when both are present, so a subset would have hidden ok, kind and note from the
    agent — the first cut shipped a subset and was corrected the same day. A host whose profile
    declares a result byte cap (Grok Build, 20 KB) gets no structuredContent at all rather than the
    body twice. Protocol version stays 2024-11-05.
  • Cards and docs. claude-code.md, codex.md, grok-build.md, generic.md gain "Handing
    back an export"; AGENTS.md's sandbox section becomes "Inside a box" with a door per host;
    docs/tech-requirements.md records which door claims are field / docs / inferred, from the
    profiles. The 2026-09-21 Grok box run is named for what it was — Grok chat's sandbox shell, not
    Grok Build (the local CLI) — in the READMEs, the substrate drawer and the changelog.
  • Eyes gate, first pass (2026-09-22, a local Claude Code session with the Artifact tool, which is
    the same door the web box has).
    The seed-3 dungeon was minted, exported three ways and published
    as three private artifacts: the self-contained page, the cdn: true page, and the bundle's courier
    page with downloads declared. The operator opened them: the world renders and the courier's
    Save delivers the zip. The claude-code profile is verified: "field"; the other profiles stay
    at docs or inferred.
  • Pins. tool-descriptions.test.js PAYLOAD_CEILING 263,500 → 264,500 (measured 264,054) for
    the bundle enum, the cdn property and the handoff sentence; the export_model description
    stays under its 2,285 allowlist pin.
  • Not in this theme: export_beats (writes no outcome folder) and forge_motion (its viewer bakes
    the loopback base URL) get no handoff yet; the Claude Desktop MCP App viewer is a flagged spike
    still to run; ChatGPT and grok.com need a public endpoint and stay out of scope.

Docs

  • README: "Where it runs, three things you can add." The box comes first as the on-ramp (nothing
    on your machine), named by the hosts the profiles carry (Claude Code on the web, a Codex cloud task,
    Grok chat's sandbox) and by what the tier needs (a Linux box with npm access, not an MCP client);
    the machine tier names its platforms (macOS, Windows, Linux) and is where a recipe from a box comes
    home to. Both pitches and the npm description drop "local MCP server" for "runs wherever your
    agent runs", and the portability claim is the recipe's: it re-mints byte for byte in either place.
    The box install is one sentence per host, each run to a successful install: Claude Code on the
    web and Grok chat ("open a Linux box and install the mojulo npm package in it"), ChatGPT the same
    in work mode. Earlier in this theme:
  • README: "Two places it runs, three things you can add." One section that says where mojulo
    runs (the operator's machine, or the agent's own temporary Linux box on Claude Code web / Grok
    chat's sandbox, both of which have driven a full mint-and-export) and what each opt-in group adds
    (creative by default, recall, chatbot), with what a session inside a box should ask for instead
    of the dashboard and the PNG bake. The npm README (control/README.md) carries the same section
    as "Where it runs", and its Quickstart names the no-keyboard init form and both opt-in installs.

House compose language

A Claude session on the web asked for a house on 2026-09-22, opened pack_object (no house), opened
pack_world (no house), read the edifice card and shipped an institutional block. The house was there
the whole time: create_sketch { manifest: { kind: 'floorplan' } }, homed in pack_diagram behind a
description about flowcharts. Its report called that "a routing bug, not a capability gap". This theme
makes the product match that report point for point.

  • A HOUSE row in forward_context's mint dispatch (house / apartment / cottage / townhouse /
    office floor plan / one furnished room → create_sketch kind floorplan); BUILDING now says
    edifice is the institutional one-off and a dwelling is HOUSE; PICTURE says a picture OF a house
    is sketch_what_possible's architecturalConstruction. The world and diagram form drawers, the
    compose_world, mint_solid and create_sketch descriptions, and the pack_world /
    pack_object / pack_diagram recognizers each carry the same one-line pointer. A house
    routing card and two fixture rows back the retrieval hop.
  • storeys: N on a floorplan manifest (alias floors) stacks the plate N high with a stair
    between consecutive floors — the shorthand the reporter reached for and found silently dropped.
    It lowers to the existing levels[] stack at render time; the recipe stays one field. Absent or
    1 renders byte-identical. A non-integer refuses with the card pointer.
  • The floor-plan card shows the real call — create_sketch({ title, manifest: { kind: 'floorplan', … } }) — instead of the flat form that cost the reporter four attempts, documents
    storeys and the authored levels[] stack (roles, per-level seed / rooms / height, stairs,
    tier, explode), and names export_model({ lit: true }) as the lit handoff. README §3 shows the
    same call.
  • The Blender worker scripts ship. blender-bake, bake-world-gi (and their .py halves) and
    export-blender are in the npm package's files, and mojulo script <name> [args…] runs one from
    the installed package root, so the card's command works where scripts/ is inside the npx cache.
    The card, the render-bay re-bake prompt and the Blender worker doc name both doors.

Release tooling

  • The README install-success smoke (CI) no longer fetches a control-plane model. scripts/smoke.sh
    ran npm run fetch-models and asserted the ONNX file, which 2.0.7 made impossible on a bare clone
    (the runtime is the opt-in recall group) and failed the first CI run after the tag. The control
    half of the smoke now asserts the opposite on Linux: no runtime or model in the tree, mojulo call version answers, and semantic_search answers lexically with three routing cards. The bot
    runtime's own model check (lite-template) is unchanged. fetch-embed-model.js prints one line with
    the install line instead of a stack trace when the group is absent.

Fractal city: operator blocks and the fidelity dial

  • **blocks: operator-laid parcels, reserved before the r...
Read more

v2.0.7

Choose a tag to compare

@github-actions github-actions released this 21 Sep 20:50

The hosted-host release. A Claude cloud sandbox drove 2.0.6 end to end on 2026-09-21 (a 47-part phone
at true scale, exported as glTF) and reported the two things that blocked a clean first run: the
embedding runtime's install step needs a host the sandbox's proxy refuses, and init waits on a
keyboard that is not there. Both are gone. Nothing changes for a desktop install except that it is a
third smaller.

Init on a host with no keyboard

  • npx mojulo init takes its defaults when stdin is not a terminal. CI, a pipe, an agent driving
    the install or </dev/null used to hang at the first prompt and then die with exit 13 and a Node
    "unsettled top-level await" warning when stdin closed. Now a non-TTY stdin is announced once and
    behaves like --yes (--no-ui and --print compose as before); a terminal that closes mid-prompt
    exits with one plain line instead of the internals warning.

Recall

  • The embedding model is an opt-in install group. @huggingface/transformers (and with it
    onnxruntime-node, onnxruntime-web, the ~130 MB lazy model) leaves the package's dependencies.
    mojulo install recall installs the runtime into ~/.mojulo/recall/ (its own package.json and an
    entry.mjs shim; it survives package upgrades the way ~/.mojulo/models/ does) and fetches the model;
    --remove takes it away. mojulo install chatbot installs recall first, because the bot-builder preview
    RAG must behave like the deployed bot. MOJULO_PACKS=recall is the manual override, as for the other groups.
  • semantic_search works without it. The index is now a lexical search (SQLite FTS5, trigram
    tokenizer, already inside better-sqlite3) over the same rows whenever the recall group is absent;
    with the group present it is the vector search it was. One path or the other, never a blend. Results
    carry mode: 'vector' | 'lexical'; a lexical score is the share of query terms the row contains, so
    the weak-match hint keeps its threshold. The index self-populates on the first search of a fresh
    install (text only, no model), so a cold CLI process answers on its first call. Query in English; the
    host translates the operator's ask before the first tool call. Measured: the routing fixture lands its
    entry tool in the top 3 for 32 of 34 phrasings through the lexical path (pinned at 85 % in
    embeddings-lexical.test.js; the vector gate in routing-eval.integration.test.js now needs the group).
  • Text-only rows. meta_embeddings.embedding is nullable (existing DBs are rebuilt in place, rows
    preserved). A row whose vector could not be produced is still indexed by text; installing recall later
    backfills the vectors on the next boot.
  • sharp is an optionalDependency in its own right (it used to arrive through the embedder) and
    stays part of the creative group; sharp-lazy.js already tolerates its absence.
  • Boot and status. The bins no longer preload a model that is not installed; the settings
    embeddings-status route reports the group state and the install line; smoke:tarball runs the cold
    install without the group and asserts a lexical semantic_search result, and --with-recall installs
    the group into the temp dir first. Measured by that gate on macOS arm64: tarball 30 MB, unpacked 116 MB,
    node_modules 472 MB (2.0.6: ~775 MB), about 590 MB total against 885 MB before.
  • adm-zip is a devDependency. The docker deployer test imported it while it arrived through
    onnxruntime-node; it is declared now that the runtime is gone.

Release tooling

  • npm run smoke:tarball parses npm pack --json past prepack's own stdout. Under the current npm
    the Next build banner lands in front of the JSON and the smoke died at the first step with
    "Unexpected token '▲'"; it now parses from the last array-opening line. The gate then passed with
    both WASM kernels in the tree, and a scad row and an exact cut minted, exported and rendered
    (/world, /scene, STL, GLB, PNG) through the fresh install's CLI and dashboard.

Routing

  • Two routing cards learn the words the fixture uses. The world card's When line gains "a little
    town I can wander around" and the motion-waypoints card gains "can the player get from the door to
    the goal": the two phrasings the lexical path missed on the routing fixture were both absent from
    the card vocabulary rather than mis-ranked.
  • The illustration pack names the graphic novel. Its recognizer gains the anchor 'a graphic novel I
    click through page by page', so "present the graphic novel like a slideshow I click through" routes
    to the sketch packs again instead of sitting a few thousandths behind the game and motion packs (it
    had missed on the committed descriptions since before the OpenSCAD work; the eval is max-over-anchors,
    so a pack without a matching quote loses to any pack with a near one).

OpenSCAD front door

  • exact: true on a fields entry or a cuts entry composes it with Manifold (the boolean kernel
    under OpenSCAD 2025, already an optional creative dependency) instead of the surface-net grid: a bore
    has a sharp lip and a 12-unit disc measures 12 in /world, the .glb, the engine packs and the print,
    with no OpenSCAD in the loop. Reach: the nine field shapes, add / subtract / intersect without
    blend, transform, repeat; anything else (blend, stroke, displace, shell, round, expr, harmonic
    lathe, warps) is refused at mint by name. segments (8–256, default 48) facets curved primitives.
    Opt-in: every existing row renders byte for byte. Absent the package, the mint refuses with the install
    line. The kernel loads at the async seams (mint, world resolve, the scene route) before the synchronous
    lowering: the mint, the edit (update_sketch), the world resolve, the scene route, the SVG control
    scaffold, capture_reference and the scad mint, each only when the recipe wants exact. The
    export_model ledger reads an exact cut as exact with edge_rounding: 0 (no grid, no rounding).
    Routing copy no longer says a sharp edge needs OpenSCAD.

  • Fixed: export_model union: true never found Manifold under the dashboard server. manifold-3d
    is ESM-only, and Next's server bundle turned the literal import() into a require() Node refuses
    (ERR_PACKAGE_PATH_NOT_EXPORTED), so the union reported the package missing under next dev and the
    standalone server while the CLI and tests had it. The loader now falls back to the runtime's own
    import when the bundler's require is refused (the same fix the scad kind's loader carries).

  • update_sketch on a scad row answers with a readout. The edit pays planScad's gates as at
    mint (the fence, the parts contract, the embedded fields' audit, OpenSCAD's own errors), re-stamps
    the ledger, and hands back the readout: changed (the default for a patch) lists the parts by
    NAME that are new, moved, or named by a /parts/<name> op, plus new warnings and OpenSCAD log lines;
    summary and full as on the workbench. Before this an edited scad row re-resolved but returned no
    stats.

  • OpenSCAD's default colours never reach the World. Its OFF writer paints uncoloured geometry in
    the preview scheme's yellow and the faces an uncoloured cutter leaves in its green; both now land as
    the card promised — the neutral grey, and in a one-colour part the cut faces wear that colour (a
    coloured body carved by a bare mojulo_field() stays the body's colour). The card also says how
    two overlapping parts weld for the print: export_model { union: true }, which already does.

  • The docs say what the doors do now. The README's examples include a part written in OpenSCAD,
    the tour and the formats tables list .scad beside the print formats, and the substrate drawer,
    tech-requirements and the OpenSCAD worker doc no longer say a sampled field cannot give a sharp edge
    or that the round trip ends at OpenSCAD. The workbench card shows the shaped-cutter idiom (a nested
    sub-solid, subtracted) a D-bore needs.

  • mint_solid kind:'scad': an OpenSCAD program IS the recipe. spec.source is stored verbatim and
    meshed on every read by OpenSCAD itself, running in-process as WebAssembly (openscad-wasm-prebuilt,
    a new optional creative dep, same posture as manifold-3d; OpenSCAD 2025.01.19, Manifold backend,
    version pinned in the ledger). The mesh is the ordinary face list, so the object rides the workbench
    studio (/world, /scene, facing, grid, movers), every export leg (.glb, engines, USD, STL,
    3MF at true size — units defaults to mm) and the gates unchanged. color() is the tint; parts
    ({ name: 'module();' }) render each part as its own group so a hinge can swing it; include /
    use / import() / surface() are refused at mint (the recipe carries its own geometry). Absent the
    package, the mint refuses with the install line. export_model format:'scad' on a scad row returns
    the source verbatim (one exact term, nothing frozen). Card: get_solid_vocab({ id: 'scad' }); routing
    card scad-object. Existing workbench rows are untouched. Why: measured on the iPhone Duo block-in,
    the same object in OpenSCAD reads at about half the tokens of the workbench manifest, needs no
    14k-token card, renders in 0.2 s, and its booleans are exact.

  • mojulo_field("<id>") inside a scad source. A scad spec may carry fields (the workbench's
    field entries, each with an id); the program reaches one as mojulo_field("<id>"), a module mojulo
    prepends holding that field baked as a polyhedron(). Blends, strokes, noise, expressions and warps
    stay mojulo's; the exact CSG around them is OpenSCAD's. A field whose surface net is not a
    2-manifold (a heavy displace) is refused at mint with the edge counts, because OpenSCAD's kernel
    would drop it from a boolean silently; and any OpenSCAD ERROR line fails a scad render even when a
    file wa...

Read more

v2.0.5

Choose a tag to compare

@github-actions github-actions released this 20 Sep 05:45

vajra-sculpt — a character as hand-placed field terms, print-closed

  • Added: a contribution gate that reads fields[].terms (stats.contribution, per term
    { exposure, protrusion, blend, buried, absorbed }). The fields monomer never had one: the
    contribution and jut-shortfall checks that keep a workbench monomer from being swallowed by its
    neighbours are blind to a term list, so the failure they exist to catch — a mass buried inside its
    host, contributing no silhouette — was invisible until a render was looked at. Two failures are
    reported separately because the fixes are opposite. BURIED: the term's own surface lies inside the
    union of the others, so it breaks no silhouette and costs bytes and sampling time while drawing
    nothing — move it out. ABSORBED: the term IS proud of its neighbours, but by less than its own
    blend radius, so the smooth-union bulge swallows what little it clears — raise the protrusion or
    lower the blend. Method is point-in-solid against the union, which is exact here because every term
    already IS a signed distance function: each term is surfaced alone on a coarse net and every face
    centroid is tested against every other term. No AABBs — a bounding box lies about a taper, which is
    how the monomer check first got this wrong. Advisory, never gating; pure and deterministic.
    Retro-run against a 23-term first-pass head (97 ms) it named four real defects with no render, and
    cleared two ears that were in fact unreadable. That miss is the useful result, because it separates
    the classes: a geometric gate finds CALIBRATION defects (buried, absorbed) with no eyes, and is
    blind to IDENTIFICATION defects (the wrong KIND of mass), which are fixed by a better reference and
    not by a better check.
  • Added: attachments on a figure recipe — any workbench recipe mounted on any named landmark,
    scaled to the figure and riding the pose: { recipe, at, t, size, fit, anchor, offset, rotate, align }.
    A held shield was already possible and was welded to one prop and one landmark; a character with a
    helmet, a spear and greaves needs the same move at four more places. This is what lets a character's
    BODY come from the figure family — parametric, posable, nothing sculpted — while its GEAR comes from
    the object lane, where manufactured parts belong. The existing hold path is untouched. A mounted
    recipe carries any monomer but drapes (cloth needs the garment path's two-sided shading) and
    reliefs (carved lettering reads a font from disk, and the figure renderer is reachable from the
    browser bundle, where there is no disk); both remain workbench-path monomers.
  • Added: a connectivity readout on every workbench recipe (stats.components). Closure asks whether
    every edge has a partner; a detached horn tip, a spear that never reached the hand and a crest fin short
    of the helmet all pass it, because each stray island is its own closed shell. Multi-body is not itself a
    defect — superposition is the construction method and a loose part is ordinarily its own solid — so the
    count is reported as a fact and only a declared bodies: N that the measurement contradicts warns.
    Method: parity voxelization along +x then a 6-connected flood fill, run over the WHOLE baked face
    list, so it crosses the field-hand / lathe-spear seam that no per-monomer check can see. It uses the
    NONZERO WINDING rule and not even-odd, and that is the whole point: superposition is the house
    construction method, so two separately closed shells that OVERLAP are the ordinary case, and parity
    fills their two rinds, leaves the overlap hollow and reports the pieces disconnected (measured: a
    shield read 11 bodies, a spear 4; winding puts both at 1). The sign is free — a triangle's det in
    the y–z projection is exactly the x-component of its normal. Two more things the build taught: a
    grid-aligned sample line lands on the shared edge of a dual mesh every time and doubles the crossing
    count, so lines are nudged off-lattice and the barycentric test is half-open; and the count is run at
    two resolutions, because a junction that only GRAZES merges at one grid and splits at the other — the
    instability IS the finding, and it is the same tangency Manifold's union cannot resolve. The gate also
    inherits the geometry's resolution ceiling, so each piece reports across (cells at its thinnest) and
    a split involving a ≤2-cell piece carries a caveat instead of an assertion. A slender member has to be
    verified on its own bounds, which is the same h = longest / cells argument that governs the sculpt.
  • Added: figure-cluster — one scale authority and one identity lock over a character's roles.
    A character built as a body plus mounted gear is already a CLUSTER of independently authored recipes.
    Composing them by hand worked and showed exactly what was missing: the gear took four placement passes
    and came out oversized, because attachments[].size is an ABSOLUTE STAND number and nothing says what
    STAND is — measured at headTop, the default armature stands 0.93 and a chibi 0.52. That happened to
    one author with the whole thing in view; fan the roles out and it happens once per agent, in different
    directions, with nothing to reconcile them. The frame holds four things nothing else did: the unit
    (measureFigure poses the armature and reports its height, so scale is MEASURED rather than guessed),
    the identity lock (≤5 named traits in one sentence, required, riding the minted recipe so a later
    pass restates the same character instead of inventing a neighbouring one), the interface (each role
    declares its mount as validated data instead of a tuned literal), and the ledger (every role's own
    closure / contribution / connectivity gates on its OWN bounds — the only scale at which a slender member
    is resolvable at all). The rule that makes the scale trap impossible rather than merely documented:
    a role declares its size as a FRACTION of the figure, never as an absolute — mount.size is a
    validation error and mount.span is required. This is the object protocol's no-absolute-z discipline
    applied to scale: a spear is 1.19 figures long and a shield 0.34 across, and those are true of a chibi,
    an adult and a brute alike, so a role authored once ports to every cast and two agents cannot disagree
    about what 0.43 means. One unit was not enough — a chibi's head is a far larger share of its body than
    an adult's and a helmet is a fraction of the HEAD — so mount.spanOf: 'figure' | 'head' says which
    level a role answers to; offsets stay figure-relative whatever the span measures against, because an
    offset is a position on the body and positions belong to the body's frame even when a size belongs to a
    part's. Verified by resolving a hand-tuned three-role character to byte-identical absolutes, then
    rendering the same spec, with no number changed, on an adult and a brute: the unit re-measures, every
    span re-multiplies, and the gear arrives at the same proportions. resolveCluster returns a
    kind: 'figure' manifest that mint_solid stores plus the gate ledger; it is a build-path module, and
    there is no cluster dial on the tool surface yet.

limb-mass — the arm and the leg in relation to the torso's weight

  • Added: armWidth and thighWidth — the upper arm's and the thigh's LATERAL breadth, which
    no dial could reach. bicep scales the anterior and posterior upper-arm lobes and quad scales
    the thigh's front lobe, but every one of those lobes adds into the limb's FORE/AFT radius and the
    lateral radius was bare baseR: measured, bicep: 2 moved the upper arm's width by ×1.000 and
    its depth by ×1.363, and quad: 2 moved the thigh's width by ×1.000. A heavy arm and a lean arm
    were the same width head-on and only the profile ever moved. (forearm and calf never had the
    problem — they ride the ring radius itself.) armWidth eases along the lobe profile so the
    deltoid and elbow caps still cover the ends; thighWidth is localized to the thigh band and
    leaves the lower leg to calf. bicep and quad are untouched, so every stored recipe renders
    byte-identically.
  • Added: tricep, hamstring and adductor — the lobes where gained weight actually sits.
    One dial drove both the bicep and the tricep, and the thigh's posterior and medial lobes had no
    dial at all, so the only thing a limb could do as it filled was grow a bigger bicep and a bigger
    quad. Muscle reads anterior and lateral; weight reads posterior and medial. adductor is what
    closes a heavy leg at the top. The fat↔muscle axis is now a choice rather than a side effect:
    { bicep: 1.5, armWidth: 1.2 } and { tricep: 1.5, armWidth: 1.35 } are different shapes at the
    same circumference.
  • Added: deltoid and elbowCap — the joint caps, which had no dial and never saw weight,
    so segments could inflate while the joints they hang from held still. Each sizes its mass and
    leaves its centre on the sex pole, so a cap grows once rather than scaling its radius and sliding
    its centre away too. (The hip already had hipFlare.)
  • Fixed: proto.weight landed on the trunk and stopped there. Across weight 1 → 2 the bust ran
    ×1.371 and the waist ×1.721 while the upper arm managed ×1.209 and the thigh ×1.191, so
    upperArm/bust fell 0.388 → 0.342 and thigh/hip fell 0.504 → 0.474 on both poles — a heavier
    figure read as a heavier TRUNK on the same limbs. It was not a matter of turning the limb gains
    up: there was no lateral axis for them to gain on, and raising bicep or quad builds muscle
    shape rather than fat shape. With the axes above in place the gains were SOLVED against the ratio
    rather than chosen, and both now hold within +5 / −0 % across weight 1 → 2 on both poles. The
    wrist and the ankle still fall away, which is correct — weight is proximal, and distal is bone.
    ...
Read more

v2.0.4

Choose a tag to compare

@github-actions github-actions released this 16 Sep 07:47

workbench-opacity — a translucent monomer (a window pane over a cassette)

  • New: opacity on any lathe / extrude / sweep / loft / drape / relief monomer (0 < opacity < 1)
    stamps a face-level alpha, which the orbitable World and the glTF leg already render per group
    (the cellular-view jelly path). A thin opacity: 0.25 extrude over a pocket reads as a window with
    whatever sits behind it visible — the Walkman's cassette door. Absent or 1 → faces untouched,
    byte-identical; the glass material row's opacity is still not read, so every minted glass part
    stays exactly as opaque as it was. Card: solid-vocab/workbench.md (Groups, a hinge, and a bare studio).
    The SVG still and CSS-3D shots draw the pane opaque (advisory, unchanged).

relief-unmirror — raised outlines read the right way round

  • Fixed: every reliefs monomer rendered mirrored. reliefToFaces negated the in-plane
    right axis on the belief that the raise flipped handedness; it does not (both contour sources
    arrive y-up — text from the font, SVG paths through flipY), so a wordmark embossed on a
    workbench part read backwards from the side it faces. The frame is now the plain right-handed
    (up × normal, up, normal). Emission changes for every already-minted relief, which is the
    point: they were all backwards. relief-faces.test.js pins an L's stem on the left for text,
    for a path, and under an explicit normal / up frame.

bound-mesh-decollide — a GI-baked object keeps its DCC planes in the World

  • Fixed: the World's de-collide pass skipped for bound meshes. emitThreeWorld lifts a
    face stacked on coincident neighbours a hair along its normal, scaled by the face's size and
    by how many it overlaps. A DCC re-triangulates every cap as a fan of large overlapping
    triangles, so a meshRef bind-back (a Blender GI bake placed in a world) had its lid cap
    floated millimetres through the proud plate in front of it. Faces in a mesh:<name> render
    group now bypass the pass (decollideExceptBound); they arrive already resolved. Worlds
    without a bound mesh take the unchanged path, byte-identical.

update-sketch-patch — iterate a stored recipe by naming the part

  • Added: update_sketch { patch } — an ordered list of set / remove / add ops addressed
    by monomer id or JSON Pointer path, applied to the stored manifest and then run through the
    same mint gates a full replacement pays. The stored row is still the whole resolved manifest;
    the archived revision carries the ops as its diff. Exclusive with manifest.
  • Added: update_sketch { readout } — changed (the default for a patch) returns only the
    parts a patch touched or moved (a touched cut operand surfaces its cut's part), removed parts,
    parts_total, plus new warnings (warnings already present on the previous revision collapse to
    one counted line); summary drops parts[]; full is the previous block unchanged, and stays
    the default for a manifest replacement. The previous readout comes from an in-process cache of
    the last edit's stats when this process made it, else one extra plan.
  • Added: id on each stats.parts[] row of a workbench readout when the monomer carries one.
    Additive; the render is untouched.
  • Changed: the tools/list payload pin in tool-descriptions.test.js re-pinned for the two new
    update_sketch input-schema properties (why: a legitimate addition crossed the pin; the
    description itself stays under its ceiling).

pixelizer-godot — the 2D reducer games get a Godot leg, and the 3D leg learns to measure

  • Added: export_game { target: 'godot' } on a pixelizer game (and scripts/export-godot.mjs --ref <ref>) emits a Godot 4 arcade pack instead of refusing. The pack is DATA — game.json,
    skin.json (the bank tiles, legend and palette), probe/replay.json, the groove and SFX WAVs when
    music is on — performed by a separate versioned kernel (godot-arcade-kernel/, 0.1.0):
    brickster.gd is a hand-authored port of the JS reducer (mulberry32 7-bag, SRS kicks, guideline
    hold, the same scoring), arcade.gd the shell (the web shell's key map, gravity timer, start
    gate, HUD, game-over card, music loop, SFX cues). One reducer, two instruments; the web build
    stays the reference. philosophers-stone is not ported yet and refuses with a teaching error.
  • Added: the replay probe, a machine-gate rung. The pack carries a seeded action script and
    the JS reducer's expected outcome; the gate runs godot --headless … -- --mojulo-replay and
    compares board, score, lines, hold, queue and game-over flag field by field. A port that drifts
    from the reducer fails the gate, not the eyes.
  • Added: [mojulo-perf] on both headless probes. Process and physics time plus node and object
    counts at the probe's last frame, stamped advisory under the gate result; the arcade probe adds
    replay_ms, the reducer port's wall time for the whole script. Headless has no renderer, so no
    render time is claimed. --mojulo-shot=<png> on a windowed arcade run saves the drawn viewport
    after the replay, so the eyes gate has a picture without opening the editor.
  • Changed: 3D kernel 0.2.2. The HUD label is assigned only when its text changes; hazard and
    meshless-entity markers share one material and one unit mesh per kind instead of allocating
    their own. No visible change; fewer draw states, nothing allocated per frame.

prepack-heap — the publish build gets an 8 GB heap

  • Fixed: npm publish aborted with JavaScript heap out of memory. The webpack production
    build now needs more than V8's default ~4 GB old space; prepack sets
    NODE_OPTIONS=--max-old-space-size=8192 for the next build --webpack step. Dev and the
    standalone runtime are untouched.

v2.0.2

Choose a tag to compare

@github-actions github-actions released this 11 Sep 23:36

openscad-leg — emit the recipe as an OpenSCAD program, not the mesh as a polyhedron

  • Added: export_model({ format: 'scad' }) + GET /api/sketches/<ref>/model.scad. The one
    ceiling the print leg cannot lift from inside is the field boolean's rounded edge — union: true
    unions SHELLS sharply, but an edge the recipe expressed as a sampled field stays rounded to about
    one grid cell, and the honest answer used to be "export and reach for Blender or OpenSCAD." This
    leg hands over the same part as editable exact solids: OpenSCAD's difference() computes the
    intersection curve, so the bore arrives with a sharp lip. The BOOLEAN is what is exact — curved
    primitives are still faceted by $fn at the head of the file.
  • It transpiles the MANIFEST, not the face payload — and coverage is measured, never claimed.
    Every other emitter consumes payload.faces; one that did the same here would write a baked
    polyhedron() with no sharp edges and no dials. This reads the workbench spec after lowerCuts
    and lowers each TERM: the nine field shapes, the three booleans, the transforms and both arrays
    map exactly. A term with no equivalent (a blend, stroke, displace, expr, a warp, a
    harmonic lathe) is polygonized and frozen with a comment naming what forced it, and the result's
    ledger says how many went each way. A number becomes a named variable only where it provably
    drives the emitted geometry, so a frozen polyhedron carries literals and says it ignores the
    dials. No kind refuses: a world or figure transpiles fully baked with a note naming stl as the
    better file. Details, including the full coverage table: docs/local-openscad-worker.md.
  • Added: scripts/scad-gate.mjs + scad-gate.js, an optional worker in the settled shape.
    MOJULO_OPENSCAD, then PATH, then the macOS bundle, in findSlicer's pure and unit-tested form;
    render, measure the STL, compare against what export_model declared, stamp
    mojulo-scad-gate.json. It compares bounding box always and volume when one was declared, and
    never triangle count — OpenSCAD tessellates exact solids by $fn while mojulo marched a grid,
    so agreement would be coincidence; the stamp says so in-band. Absent the binary it skips with the
    install line. OpenSCAD never enters the render path: a recipe regenerates byte-identically with no
    binary installed, forever.
  • Both gates RUN and GREEN against OpenSCAD 2026.09.10, and the machine gate found a real bug on
    its first run.
    Five fixtures rendered, all exit 0 with Status: NoError, including a fully
    baked figure whose 34,464-triangle polyhedron survived CGAL at an exact size match. Where mojulo
    declared 79.9 mm and OpenSCAD rendered 80, that 0.1 mm IS the prediction — the marched mesh sits
    about half a grid cell inside the ideal solid. The bug: size_mm read facesBounds over every
    face, so a workbench's studio grid was in the number and a 120 mm disc declared 302 mm; it now
    reads printSoup, the printable set the STL writes. Eyes gate: the same recipe emitted as exact
    solids and as mojulo's own field surface and rendered by the same renderer — the exact bore lip is
    a crisp ellipse, the field one carries a visible chamfer all the way round. (Install note: the
    stable openscad cask has been disabled since 2026-09-01 for Gatekeeper; use openscad@snapshot.)
  • Doctrine fit, stated so it is not re-litigated. Not a CSG kernel in-substrate and not a native
    mesh reader — a text emitter, which D0 already permits and scene-usd.js already proves.
    OpenSCAD's boolean kernel since 2025 IS Manifold, which union: true already runs: same math,
    different author, not a rival kernel. The round trip ends there, because OpenSCAD writes no format
    bind_mesh_render accepts. translate_modeler_lingo (boolean-cut, precision-cad,
    procedural-part), the workbench card and get_substrate all name the new exit. Additive
    throughout: every existing format stays byte-identical and no existing pin moved.

field-normals — ask the field for its normal at every corner

  • Added: smooth on surfaceNetFaces. The surface net computed ONE normal per
    quad, at the quad's centre, and litFaces turned that into one flat fill — so an analytically
    smooth surface (a smooth-union of exact SDF primitives) arrived on screen as a field of visible
    flat facets. The gradient was always available at any point; it was simply asked 1,288 times
    instead of 5,152. smooth samples it per CORNER and the face carries cornerNormals.
  • Why it is nearly free: per-vertex colour is already plumbed end to end. faceListToMesh
    already reads cornerFills and uploads per-vertex colour, world-contract already counts it as
    a fill, and two builders already produce it. The only missing piece was a producer of per-corner
    NORMALS. This is the "flat for now — phase 2 smooths it" the figure-render comment has been
    waiting on.
  • Added: cornerFills from the animal's watertight skin. Carried through
    animalSkinWatertight -> animalStacks -> litFaces; the zone decision (which colour a face
    wears, countershading included) stays per face, and only the shading of that colour varies per
    corner. Stripping cornerFills off a smooth payload recovers the flat payload byte for byte.
  • Fixed: the animal vocab card never mentioned skin: 'watertight'. It documented skin as
    a boolean, so the surface-net path — the closed mesh, the print path, and the only mode smooth
    applies to — was undiscoverable to any agent reading the card. Its "Print lane" limit was wrong
    as a result, stating that a closure audit reports EVERY animal as not closed; measured, the
    marched skin gives closed: false (864 boundary edges) and the watertight skin PASSES with zero
    significant holes. Both verdicts are now stated. coat was also described as "fur blades over
    the body" when it is paint — strand fur is reachable only through the ringtail.
  • Opt-in, hard. smooth defaults false, so every existing pin, snapshot and export stays
    byte-identical until a kind deliberately asks; turning it on per kind is its own step and its own
    re-pin. What this does NOT change: the geometry, the polygon count, or the silhouette — smooth
    shading fixes the interior of a form, never its outline.

field-splats — a second emission off the polygonizer's SDF

  • Added: surfaceSplats in lib/graph/polygonizer/field-splats.js. The sibling
    of surfaceNetFaces: same (field, bounds, opts) contract, same uniform-grid walk of the zero
    set, same central-difference gradient — but it emits oriented gaussians instead of quads. Pure and
    deterministic; the coat is placed from the known field, never trained from images.
  • Why: fur is not a surface, and mojulo has the scars to prove it. figure-animal-pelage.js
    is a complete coat builder, rewritten at least twice (its header records flat blades reading as
    "angular spikes"), and coatBlades is reachable from exactly one line in the repo — a tail plume.
    No shipped species wears it on the body; the raccoon picks painted tailBands over fur outright.
    A quad can only be a surface, so every strand minted to fake volume costs ~36 faces that ship to
    every engine, threaten the closure audit, and are unprintable at hair scale.
  • Added: the splats channel. A SETUP_CHANNELS row rendering the coat as
    sorted surfels — quads spanned by the emission's own tangent axes, so they lie in the surface's
    tangent plane instead of facing the camera (which is why this cannot reuse the glow channel's
    sprites). Packed base64 through the page's existing decoders; zero bytes when absent, with every
    pre-existing fixture hash in the char net untouched.
  • Added: coat: { fur } on an animal. opts.coat.fur grows the coat off the
    animal's own resolved body faces — which came from the skin field via the surface net, so the
    coat is field-derived while still inheriting planting, lighting, and the body's final colour.
    That last part is load-bearing: a coat over a differently-coloured body reads as a halo,
    because the opaque body eats the dense inner shells and only the silhouette survives. The body
    is the innermost shell. Fur grows only where the coat PAINT already is, so paw pads and nose
    leather stay bare. No shipped build is re-pointed yet.
  • Proof, not intention: a coat changes nothing mojulo fabricates. With a coat present the
    faces, the STL printable set, the GLB, and the world-contract tier are byte-identical to the
    uncoated animal, and splats is the only payload key added — asserted, so a later change that
    leaks splats into the printable set or the tier ladder fails loudly.
  • Scope: the soft register only. Splats never declare a surface — they contribute nothing to the
    T0-T4 world contract, are ignored by the STL/3MF printable set, and are skipped by the closure
    audit. A coat cannot break a print or move a tier by construction. Rooms and the box worlds are
    explicitly out: measured at 200x-2500x the primitives for a visually identical result, with
    crenellated edges where a quad had an exact straight one.

Docs and registry metadata

  • Changed: Windows is described as verified natively, and the READMEs say what still is not.
    Both READMEs and docs/tech-requirements.md now separate what has been run on Windows from what
    has not, rather than implying parity.
  • Fixed: the registry listing fits the hundred characters it allows. server.json's description
    was over the limit the registry enforces.