forked from FieryLionite/formamorph
-
-
Notifications
You must be signed in to change notification settings - Fork 2
Changelog
github-actions[bot] edited this page Jul 5, 2026
·
326 revisions
All notable changes to Formamorph. This fork's first line is 2.0.0 — a full TypeScript rebuild of the upstream JavaScript app (FieryLionite's Formamorph, ~v1.2) — with feature parity as the baseline plus new features on top.
✅ 2.0.0 is released (collapsed below). 2.0.1 is the current living line — entries accumulate here as work lands; when its version is bumped, that marks it going live.
Each release groups changes as Major / Minor, then Added / Removed / Fixed, and within those by audience: 👤 user-facing · 🛠️ developer tooling · ⚙️ backend / invisible.
-
👤 User-facing
-
AI image generation — generate images for entities, locations, and world thumbnails right in the World Editor. A Generate with AI button drafts an SD-style prompt from the subject's description (via your text model), lets you tweak it, and renders through your configured image provider. Providers: a local A1111 / Forge server, and — in the desktop app — an OpenAI-compatible cloud image API (proxied through Electron to sidestep browser CORS). Prompts are LLM-refined into stripped danbooru / booru tags via an editable Tag Prompt (with a
<SUBJECT>chip, Settings → Image Gen), plus a positive-prompt prefix for quality/style tags. Characters render at portrait dimensions, locations & thumbnails at landscape. Settings live in named, editable endpoint presets (a "Default" plus your own), switchable in Settings and from the generate popup. For A1111: an ADetailer face/hand-fix toggle and a live progress bar + in-progress preview while it renders.
-
AI image generation — generate images for entities, locations, and world thumbnails right in the World Editor. A Generate with AI button drafts an SD-style prompt from the subject's description (via your text model), lets you tweak it, and renders through your configured image provider. Providers: a local A1111 / Forge server, and — in the desktop app — an OpenAI-compatible cloud image API (proxied through Electron to sidestep browser CORS). Prompts are LLM-refined into stripped danbooru / booru tags via an editable Tag Prompt (with a
-
🛠️ Developer tooling
-
Linux & macOS desktop builds — electron-builder now targets AppImage (Linux) and dmg (macOS) beside the Windows portable exe (
desktop:build:linux/desktop:build:mac). A release workflow builds all three from onev*tag (a Windows / Linux / macOS matrix) and attaches them to the GitHub Release. -
Hosted browser build on GitHub Pages — deploys on every push to
main, with the community-server features (Discover, login, publish) disabled in that build (VITE_ENABLE_COMMUNITY=false) so nothing unmoderated is surfaced by default. Play it at jakejamesdev.github.io/formamorph.
-
Linux & macOS desktop builds — electron-builder now targets AppImage (Linux) and dmg (macOS) beside the Windows portable exe (
-
🛠️ Developer tooling
- Tagged desktop builds no longer fail on the Linux/macOS runners — electron-builder no longer tries to self-publish (missing
GH_TOKEN); the workflow's own publish step handles the upload.
- Tagged desktop builds no longer fail on the Linux/macOS runners — electron-builder no longer tries to self-publish (missing
-
👤 User-facing
- Import tags from an image's metadata — upload a PNG that embeds its generation prompt (A1111/Forge, ComfyUI, InvokeAI, NovelAI, and other JSON-metadata tools) to an entity or location, and Formamorph offers to use that positive prompt as the Image Tags — no retyping. Declined images and plain photos are untouched.
- Authored image tags — entities and locations now have an Image Tags field (under the image, above Generate with AI) that persists your booru tags. A shared ✨ toolbar with Undo / Redo writes tags from the description (and now also generates the AI-Facing Summary the same way). The Generate with AI popup edits those same tags two-way — tweak, generate, adjust, regenerate — and no longer auto-overwrites what you authored.
- Danbooru tag autocomplete — the Image Tags fields, the Generate with AI prompt, and the world Tags editor suggest real Danbooru tags as you type, ranked by how commonly they're used, so you complete a tag with a keystroke instead of guessing spellings. Start typing to filter (an empty field shows nothing); the list loads on demand and works offline. The world Tags editor previously suggested tags scraped from the community browser (with a Refresh button) — it now uses this standardized list, so that button is gone.
- Editable tag chips — double-click a chip in the world Tags editor or a dictionary entry's Keywords to fix it in place (Enter saves, Esc cancels, clearing it removes the chip) instead of deleting and retyping. Editing a world tag still shows the Danbooru autocomplete; chips keep their position, and drag-to-reorder is unchanged.
-
ComfyUI image provider — generate images through a local ComfyUI server alongside the existing A1111/Forge and cloud options. Ships a working SDXL txt2img workflow that's fully editable per preset: paste your own Save (API Format) graph and mark inject points with tokens (
%prompt%,%negative%,%ckpt%,%width%,%height%,%steps%,%cfg%,%seed%,%sampler%). Live progress bar + preview frames stream over ComfyUI's WebSocket, matching the A1111 experience. Launch ComfyUI with--enable-cors-headerso the browser can reach it (default port 8188). - World README — authors can add a markdown Readme (in the World Editor, below System Prompt Addition, using the same editor as the World Description) that greets the player in a popup when they enter the world. Dismiss it with the X, click-outside, or Esc. A per-world Show Readme toggle lives in the main-menu world popup and is mirrored by a Don't Show This Again checkbox in the popup itself — the two stay in sync (one setting, on by default).
- Consent-based image optimization — before an oversized image is re-encoded (on import, on generate "Use image", or via the editor's Optimize Images), a prompt offers Optimize (convert to lossless WebP at the same resolution — no visible quality loss) or Downscale (shrink to fit), with a clear Keep original to do nothing. Already-WebP images skip the redundant Optimize option.
- The world-browser Hidden popover now uses editable tag / author autocomplete boxes, matching the search filters.
-
Dictionaries are books — the World Editor's Dictionary tab now groups lorebook entries into named dictionaries ("books") you add, reorder, collapse, and toggle on/off as a whole, instead of one flat pile. Each book keeps its own Background and Foreground lists, and entries drag between zones or across books; book order sets injection order, and disabling a book mutes all its entries at once. New worlds start with a Default book. A single book exports to its own
.json(Download <name> on a selected dictionary) and imports back as a new book (Import Dictionary) — handy for sharing lore between worlds; importing a full world or save file is rejected with a clear message. -
Dictionary library + lorebook import — the main menu's top-left switcher gains a Dictionaries tab: a library of standalone dictionaries you Import and manage as cards (persisted locally), separate from any world. Import isn't limited to Formamorph files — it also reads and converts open lorebooks: SillyTavern World Info exports and Character Card V2/V3
character_bookdata (keywords, secondary keywords, content, enabled/constant, case-sensitivity, regex, placement, scan depth). Cards with no image (and worlds without a thumbnail) now show a neutral grey placeholder instead of a broken image. Click a dictionary card to open it in a full editor — the same Background/Foreground tree, entry editor, and drag-to-reorder as the World Editor — rename it, edit entries, Download it, and Save back to the library (with an unsaved-changes prompt). Dictionaries also gain a Description (author note, not sent to the AI), and entries gain Whole words matching (keyword boundaries, so "art" no longer fires on "cart") and richer secondary-keyword logic — require any or all secondaries, and an Exclude mode so an entry fires only when certain words are absent. In the World Editor, Add Dictionary pulls copies of one or more library dictionaries into the current world (multi-select), each as an independent copy. - Theme + accent color — Settings → Presentation now has a Theme control (Light / Dark / System, as segmented tab buttons; System is the new default and follows your OS) and an Accent Color picker below it that tints buttons and highlights across the whole app. The picker defaults to the app's signature teal; leave it there and light/dark keep their separately-tuned shades, or pick your own (applied to both modes with an auto-contrasting text color), with a Reset back to default.
- Back button in the enter-world flow — every step of the start-a-world flow (Traits → Location → Characters → Dictionaries → Avatar) now has a Back button, always just to the left of the next/confirm button, that returns to the previous step. On the very first step it's shown but faded/disabled. On the Traits step it also pages back through trait sections before it reaches the flow's start.
- Bring characters into a world when you start it — a new step in the enter-world flow (after location, before dictionaries) lists your library characters with their portraits; check the ones you want and they're placed into your starting location for that playthrough. Only appears when you have saved characters. The chosen characters are copied in with fresh ids and live entirely in the save — they're never written into the authored world.
- Choose dictionaries when you start a world — a new step in the enter-world flow (after location, before avatar customization) lists the world's own dictionaries on top and your downloaded library below, each with a drag handle, an enable/disable checkbox, its description, and an enabled/total entry count. Reorder and toggle any of them — world-authored included — to set exactly which lore applies and in what order for this playthrough; enabling a library dictionary pulls in a fresh copy. Your choice is saved with the game, so reloading keeps it. The step is skipped when there's nothing to choose (a single-book world with an empty library).
-
Character cards — share a character as an image — a single character now exports to a shareable WebP whose picture is the character's portrait and whose text (name, descriptions, summary, type, image tags) rides along invisibly inside the image, the way other games do character cards. In the World Editor, Download <name> on a selected entity saves the card; drop that
.webpback in via the new main-menu Entities tab and the character returns with its portrait and every field intact (the image itself becomes the portrait — no duplicate stored). The Entities tab is a local library of characters you New, Import, edit in a full editor, reorder, and delete — separate from any world; Add Character in the World Editor pulls copies of one or more of them into the world (multi-select), each an independent copy. A character with no picture still exports, using a generated placeholder. Importing a world, save, dictionary, or a plain non-card image shows a clear message. -
Import SillyTavern characters — the Entities Import also reads SillyTavern / Character-Card PNGs (the
ccv3/charaembedded-card format). It brings across the picture and the name plus description + personality + scenario (folded into the AI-facing description, with{{char}}→ the character's name and{{user}}→ "the player"); the chat-only fields (greeting, example dialogue, jailbreak prompts) are skipped as they have no place in a world character. If the card carries its own lorebook, you're asked whether to add it to your dictionary library too.
-
🛠️ Developer tooling
-
Env-seeded image defaults — the whole Image Gen setup (provider, endpoint, dimensions, ADetailer, and multiple named presets via
VITE_DEFAULT_IMAGE_PRESETS) can be seeded from.env.local. -
Unified app icon — one source icon (
public/icon.png) is now both the browser-tab favicon and the desktop app icon (electron-builder converts it per-platform), replacing the default Vite / Electron icons. -
VRAM readout built into the desktop app — the Hardware tab's live GPU readout no longer needs a separately-run
npm run vram-helperin the packaged desktop build: the Electron main process runsnvidia-smiitself and feeds the renderer over an IPC bridge (window.formamorphDesktop.vramStats). The nvidia-smi logic was factored into a sharedelectron/vramCollect.cjsused by both the desktop main process and the standalone dev helper.useVramStatsprefers IPC when in the desktop build, else polls the helper URL. The Hardware tab is now hidden in the production web build (where no VRAM source exists) and shown only innpm run devor the desktop app; on desktop it drops the "run the helper" instructions for a built-in note. - The main-menu GitHub button and the README / wiki now link to this fork's repo and the live web build.
-
API doc comments + tooling — the high-value TypeScript surface (domain types, contexts, services, and the core
libmodules) now carries prose TSDoc comments, so VS Code shows a plain-English summary on hover next to the types. The lint gate validates comment syntax (eslint-plugin-tsdoc), andnpm run docsgenerates a browsable TypeDoc API reference into the gitignoreddocs-api/. -
World shape:
dictionary→dictionaries— the flatdictionary: DictionaryEntry[]becomes an ordereddictionaries: Dictionary[](books of entries). An idempotent load-time migration folds any legacy world (including the bundled defaults) into one Default book, positions preserved — no world is re-persisted until the user saves. Injection stays entry-based via aflattenEnabledBookEntriesbridge, so a single-book world's prompt output is unchanged. New standalone dictionary file format carries aformamorphKind: 'dictionary'discriminator; world and save exports now also carry an optionalformamorphKind: 'world'/'save'label (additive — old files without it still import). Foreign lorebook parsing (lorebookImport.ts) is grounded in the MIT Character Card V3 spec (credited inTHIRD-PARTY-NOTICES.md). The dictionary editing widgets were decoupled from the world store into a scopedDictionaryStorecontext (useDictionaryStoreState), so the World Editor binds them to the current world's books while the library editor binds them to an isolated single book — the same widgets, two hosts, no shared-state leakage.Dictionarygains an optionaldescription;DictionaryEntrygains optionalmatchWholeWords,secondaryAll, andsecondaryExclude(all additive — absent = prior behavior). The importer now maps STmatchWholeWords,selectiveLogic(AND-ANY/ALL, NOT-ANY/ALL), and lorebookdescription. -
Save shape: optional per-playthrough
dictionaries— the save envelope (SaveObject) gains an optionaldictionaries: Dictionary[]written by the new world-entry dictionary step; older saves lack it and fall back to the world's current books (never clobbered). The per-run set is owned byGameplayContext(runtimeDictionaries+ the flattenedruntimeDictionaryfed to injection);saveGame/loadGamestamp and restore it internally, soGameDataContextholds only the authored books. Pure selection helpers live inlib/dictionarySelection.ts(shouldShowDictionaryStep,buildInitialSelection,finalizeSelection). Additive — old saves import unchanged. -
New file format: character cards (
.webp) — a single entity slices out to a shareable WebP with its text fields embedded in the image, mirroring the dictionary slice.lib/entityCard.tsis a pure RIFF/WebP metadata codec (embedEntityCard/readEntityCard): it promotes a simple VP8/VP8L file to extended VP8X and writes the JSON into anXMPchunk (idempotent — re-embedding replaces, never appends), modeled on the existing PNG-chunk reader insdMetadata.ts.lib/entityFile.tsadds theformamorphKind: 'entity'discriminator,buildEntityCardData(dropsimage/model/sound),parseEntityCardData(fresh id, rejects world/save/dictionary payloads), and theexportEntityCard/importEntityCardorchestrators — export omits the portrait from the JSON and import re-sources it from the card's own pixels, so the image is never stored twice. A local library mirrors the dictionary one:EntityStorageService(IndexedDBentitiesDB), a main-menu Entities grid (reusingSortableWorldCard),EntityEditorModal, andAddEntityModal.EntityManager's fields were extracted into a sharedEntityFieldsbody (locations picker optional) so the World Editor and the library editor reuse it. NewEntityMetadatatype. This is a new shareable artifact type; it does not change the world or save.jsonshape — no version bump. -
Entry-step character placement (runtime-only) — the new enter-world character step is gated by
shouldShowCharacterStep(lib/characterSelection.ts) and picked inCharacterSelectionModal. The chosen library characters thread MainMenu →App→GameVieweras aninitialCharactersprop (mirroringinitialDictionaries), and the init effect seeds them intoGameplayContext.discoveredEntitiesanchored to the resolved starting location — reusing the existing runtime-character path (allEntities+mergeDiscoveredIntoLocation). So they show as present, persist in the save, and never touchGameData(the authored-world-immutable invariant). Fresh ids on selection;loadGameoverrides on load. -
SillyTavern character PNG import —
lib/tavernCard.tsreads a Character-Card PNG (base64 JSON in theccv3/charatext chunk, V1 flat / V2-V3dataenvelope), reusing the now-exportedreadPngTextChunksfromsdMetadata.ts. It maps only name + description/personality/scenario →aiDescription(macro substitution{{char}}/{{user}}), drops the chat-runtime fields, and hands anycharacter_bookto the existingconvertLorebook.entityFile.importCharacterFile(file)is the unified import entry — WebP card or ST PNG — returning{ entity, book }; the main menu offers the book via a confirm dialog. The PNG's pixels become the portrait (re-encoded to WebP). -
Authored world is immutable during play — the running session no longer writes runtime state back into the authored world (
GameDataContext). The stat-change handler updates onlyplayerStats(dropping the oldupdateStatwrite-back), and the per-run dictionary set lives inGameplayContext, threaded in from world entry as aGameViewerprop (mirroringinitialTraits/initialLocationId). The mid-game Edit World tool edits the authored world directly; it persists only on Save World, and no longer opens pre-marked dirty. The now-unused flatteneddictionaryderivation was removed fromGameDataContext.
-
Env-seeded image defaults — the whole Image Gen setup (provider, endpoint, dimensions, ADetailer, and multiple named presets via
-
👤 User-facing
- New World / Entity / Dictionary no longer leave a blank behind. Clicking New used to immediately save an empty entry, so backing out of the editor without saving left a stray "New World"/"New Character"/"New Dictionary" in your library. Now New opens the editor on an in-memory draft and nothing is stored until you Save — cancel and it's gone.
- Gameplay no longer edits your world. Playing a world used to quietly drift its authored stat values (and, briefly, its dictionary order/toggles) into the saved world — so opening Edit World mid-run showed changed values and a "dirty" state you hadn't touched, and saving there baked playthrough state into the world. Now the world changes only when you edit it in the World Editor and Save; per-run state stays with the save file.
- The world-browser autocomplete dropdowns no longer close when you click their scrollbar, and the Hidden popover's dropdown now scrolls with the mouse wheel.
- Image Gen polish: the booru tag prompt no longer emits PascalCase / underscored tags the model can't use; the Settings → System Prompts tab is no longer pushed out of place by the Image Gen tab; and generated / imported images are no longer silently optimized without asking first.
✅ 2.0.0 — Released — full TypeScript rebuild + feature parity (click to expand)
Everything below is relative to the point this fork diverged from upstream.
-
👤 User-facing
-
Standalone Windows app — run Formamorph as a desktop
.exe(Electron) with no browser needed. It bundles its own engine, so WebGPU TTS, 3D rendering, and saved-world storage all work the same as in-browser. -
Custom 3D avatars — bring your own
.vrm, or set a per-world custom player model. - In-app media player — a fully themed audio widget for TTS narration, with auto-generated TTS when the model is loaded and a one-click unload of the TTS model to free VRAM.
- Redesigned world browser ("Discover") — card-grid layout, tag & author chip search/filtering, and world-preview image zoom.
- AI output controls — automatic paragraph limits and length-based truncation, an optional "thinking / planning" step before the reply, and a smoothed character-by-character reveal of streamed text.
- Re-generate & rollback — page back through the turn history to re-generate the latest AI response, or roll back to an earlier turn (discarding the turns after it). The per-turn AI-context viewer flags the regenerated and rolled-back turns.
- Markdown narration — the AI can format story text with bold/italic, bullet & numbered lists, tables (e.g. inventory), headings, blockquotes, and code; it renders live as the text streams in.
- Token-based Context Window, auto-detected from your endpoint (LM Studio, OpenRouter, …) with a Detect button and manual override — conversation history now fills the model's real context instead of a fixed character cap, and an over-limit warning flags a value set above the detected maximum.
- Portrait / mobile layout — a single-panel responsive mode alongside the three-panel desktop view.
- Lore Dictionary — a keyword-triggered world-info editor; matching keywords inject lore into the AI prompt.
- Full per-turn AI context viewer — see exactly what's sent to the AI on each turn, plus the raw response before sanitation — useful both for debugging and for authoring/tuning worlds. Each request is its own collapsible section with the prompt and raw output nested inside, and Collapse/Expand all folds every level at once.
- Stat-driven body sliders — in the world editor, bind any stat to one or more of the model's body morph sliders; the stat's value (min→max) drives the morph live in-game. Legacy weight/breasts/stomach stats are auto-bound on import, so existing worlds keep working.
-
Prompt variables are now chips — the AI prompt editors (Settings → System Prompts) no longer use raw
<…>placeholder text. A toolbar inserts each variable as a color-coded chip you can drag to reposition, remove with ×, or use more than once. Clicking a chip opens its options: the Location chip switches between Full / Summary / List (the list of all location names, usable in any prompt — no separate chip needed), and Entities switches between Full / Summary. During a game, an Edit / Preview toggle shows each chip swapped for its live value — color-matched to its chip — so you can see the real assembled prompt. - Entities are their own prompt section — the characters and things at a location are no longer dumped inside the location block (which made the AI treat the whole cast as all-present). They're now a separate Entities chip — a roster of who or what could appear — so the model is less likely to crowd every NPC into the scene.
-
Standalone Windows app — run Formamorph as a desktop
-
🛠️ Developer tooling
-
Automated test suite built from scratch (Vitest + Testing Library) covering the new libraries, services, and components, plus a coverage script (
npm run coverage). - GitHub Actions CI — typecheck, lint, and tests run on every push and pull request.
-
Optional VRAM monitor — a local helper (
npm run vram-helper) and in-app readout that warns before loading TTS would exhaust GPU memory. -
Desktop packaging — a thin Electron shell (
electron/) plus electron-builder config;npm run desktop:buildproduces a portable Windows exe from the same web build. - Project scaffolding:
.env.example,.nvmrc, and a rewritten README. - This wiki, published automatically from
docs/by a GitHub Action.
-
Automated test suite built from scratch (Vitest + Testing Library) covering the new libraries, services, and components, plus a coverage script (
-
⚙️ Backend / invisible
-
Full JavaScript → TypeScript conversion of the entire codebase (10 phases), finishing on strict mode with shared domain types under
src/types. -
World & save versioning — an
APP_VERSIONderived frompackage.json, idempotent import migration (migrateWorld) that upgrades legacy worlds at every import boundary, presence-based save-envelope detection, and version stamps on saves, exports, and the bundled worlds. (See the World Format versioning notes.) - Heavy save conversion and serialization moved to web workers so the UI never freezes on large files.
- Reusable utilities extracted: world import/catalog, output length, AI-response parsing, tag/dictionary/highlight helpers, and a thumbnail cache.
-
Full JavaScript → TypeScript conversion of the entire codebase (10 phases), finishing on strict mode with shared domain types under
-
👤 User-facing
- The fullscreen menu popup, replaced by the redesigned world browser.
- The previous bundled example worlds (
slime,sugarscape,veilwood), swapped for the current set.
-
🛠️ Developer tooling
- In-repo planning notes and server stubs under
src/docs/(publish-feature plan, server README, tags-implementation notes, server-worlds stub).
- In-repo planning notes and server stubs under
-
⚙️ Backend / invisible
- All legacy
.jsx/.jssources, superseded by.tsx/.tsequivalents (views, contexts, workers, prompts, db & utils, UI primitives). -
jsconfig.json(replaced bytsconfig.json) and a strayIdle.fbxanimation file.
- All legacy
-
👤 User-facing
- Custom VRM models exported from v1.2 were silently stripped on import — now migrated and preserved.
- Body-morph sliders now resize clothing meshes instead of clipping through them.
- VRM outline color rendering issue.
- AI response parsing errors that could break choices and stat updates.
- The Settings panel no longer jumps vertically when its contents change.
- Unsaved world edits are detected on exit, with a confirmation prompt to prevent data loss.
-
⚙️ Backend / invisible
- Server-Sent Events streaming now handles chunk boundaries correctly — no dropped or garbled tokens mid-stream.
- Removed side effects from the
setPlayerStatsstate updater. - The recent-stat-change highlight timer is cleared properly, and streaming guards against a missing
response.body.
-
👤 User-facing
- Markdown formatting toggle (Settings → Presentation, default on) — when on, the narration prompt asks the AI to format with bold/italics, lists, and tables (e.g. inventory) using a floor-based rule (one bold + at least one italic per turn) that small models follow reliably; off keeps plain prose. (Reset Prompts to pick it up if you've customized the system prompt.)
- Hide stat numbers from the narrator (Settings → Generation, default on) — the narration, planning, and choices requests receive stat descriptors (e.g. "severely injured") instead of raw values, so the model writes to how a stat feels rather than fixating on the number. Stat-updates still get the numbers; falls back to the number when a stat has no descriptor.
-
Slash commands in the action box (input starting with
/) that bypass the AI; the first,/markdown test, types a rich sample through the real narration renderer to preview markdown formatting. - Use Custom Endpoint toggle in Settings → Endpoint — off uses the built-in default endpoint (fields read-only); on lets you enter and edit your own. Custom values are preserved when toggling off and back on.
- Hide-UI toggle — an eye button in the bottom-left of the game view hides all panels to reveal the background image; it fades out over the background until hovered.
- Drag-to-reorder for worlds in the main menu.
- Additional avatar color options.
- Unified, consistently styled scroll areas, checkboxes, radios, and chips across the app.
- Tag truncation and a centered download control in the world browser.
- LLM request status surfaced next to the status bar.
- Update-aware world browser — downloaded worlds show their status (Downloaded / Update available / out of date) with one-click re-download to pull the latest version.
- World provenance on cards — local world cards show how each world arrived (Created / Downloaded / Imported) with timestamps.
- Main-menu layout toggle — switch the world grid between Grid and Detailed layouts.
- Markdown in world descriptions — world descriptions render markdown, matching the in-game narration.
- In-game entity viewer — the avatar panel gained Player / Entities tabs; the Entities view shows the first detected entity's image, clicking an entity in the list swaps to it, and clicking the image opens a full zoom view (works even for worlds without a player model). The tab fills from the characters that actually took part in the turn (once the narration finishes, so there's no mid-stream flicker); a Staged-mode character the planner invented shows as a plain, non-clickable entry.
- Entity popup — viewing an entity now supports the same pan/zoom image view as world thumbnails, sizes the image to most of the popup with its description below, and shows a muted "No description provided." when there's no text.
- AI-context "current context only" filter — the per-turn context viewer can hide re-generated, rolled-back, and aborted turns so you see only what the AI currently sees (on by default).
- System Prompts editor — Settings → System Prompts has a tab per prompt type (Narration, Thinking, Choices, Stat Updates, Location Change, Summary, plus the Staged director/character/storyboard), and Reset affects only the selected prompt. Each helper request (Choices, Stat Updates, Location Change, Summary) has a System | User toggle, so you can edit the user message it sends — the framing and the "just output X, no story" cue — not just the system prompt, with Player Action and Narration chips to drop the turn's values into your template. A checkbox group in Settings → Generation turns the optional prompts (Choices, Stat Updates, Location Change) on or off — replacing the old "type DISABLED into the prompt" trick — and a disabled prompt hides its editor tab (worlds that used the DISABLED trick migrate to off automatically). A Verbatim turns field in the editor footer sets how many recent turns each prompt sends in full before older ones collapse into the summary recap.
- Organized Settings — gameplay settings are grouped into Presentation (language, music, output length, auto-scroll, markdown, narration audio) and Generation (hide stat numbers, thinking, memory summaries, silent requests), so related settings sit together.
- Staged (director-led) thinking — a new Thinking mode beside Off / Planning / Inline that plans a turn in stages for higher-quality, more consistent scenes: a director stages the scene (a brief description of where you are and what's visible) and casts who's present with each one's placement — where they stand and what they're physically doing right now, always leading with your own position as "Player Character". The director casts only living, acting beings (people, creatures, threats — places and objects stay in the scene description), and gives any character it invents a concrete, reusable name so they stay the same character turn to turn. Each chosen character (up to three) plans its own motivation in a separate pass, speaking in the first person ("I want… / I intend to…"); and a storyboarder combines those into the plan (reacting to what you did, never scripting your next move). When no other characters are present, the motivation and storyboard passes are skipped — the plan is just the scene and your placement — so a solo moment stays fast and doesn't invent filler. The director's scene and cast placements are handed to the narrator alongside the storyboard beats, so the story is grounded in concrete staging and physical interactions stay consistent. The trade-off is several extra requests per turn, so it's best with a fast endpoint. Each stage is visible in the AI-context viewer and never leaks into the story.
- Full-length, progressive TTS narration — narration is split into sentence chunks so audio is no longer cut off at ~26 s (Kokoro's per-call token cap). Playback is progressive: speech starts after the first sentence and plays gaplessly (Web Audio) as the rest generate, with a determinate progress bar (sentence X of N); the seek bar is driven by the same engine, so the clip is scrubbable/replayable with no hand-off.
- Stream Narration Audio (in the Text-to-Speech popup, default off) — when on, text-to-speech begins synthesizing each sentence the moment it finishes streaming from the model, instead of waiting for the whole story; much lower audio latency. Off by default since TTS then runs alongside the model and can compete for the GPU on a single-machine setup.
- Narration speed — a persisted Speed slider (0.5×–2.0×) in the Text-to-Speech popup controls how fast the voice reads. Applies to newly generated audio, so change it and hit the regenerate (↻) button to re-speak the current text at the new pace.
- Karaoke sentence highlighting — while narration audio plays, the sentence currently being spoken is highlighted in place with a soft accent tint, and the highlight follows the playhead when you scrub the seek bar — so you always know where you are in the text. Works across bold/italic/linked words within a sentence and while narration is still streaming in. On by default (toggle "Highlight while speaking" in the Text-to-Speech popup); silently does nothing on browsers without highlight support.
- Frozen audio bar — once TTS audio exists, the play/scrub seek bar is pinned to the top of the story panel (instead of scrolling away with the text) and carries the audio-specific buttons (play, regenerate) on its row; the export and edit buttons stay in the top-right corner.
-
Export the story — a download button beside the narration's edit pencil saves the whole playthrough's narration to a file, one turn per block, with a choice of plain text (
.txt) or Markdown (.md). - Audio settings live with the audio — the TTS controls (voice, speed, Stream Narration Audio, Highlight while speaking) all sit together in the Text-to-Speech popup rather than the main Settings menu, since they only matter once a TTS model is loaded — which only happens from that popup, in-game.
- Non-streaming narration generates before the rest of the turn — with a TTS model loaded and Stream Narration Audio off, the narration's audio is now generated right after the story text and the turn's remaining requests (choices, stat updates, location change) wait for it to finish, rather than racing alongside it — avoiding GPU contention when the model and TTS share one machine.
- Memory Summaries (Settings → Generation, default off) — long stories stay coherent without bloating each request. Older turns are summarized into short fact lines as they age out of recent history and fed back to the model: recent turns stay word-for-word, older turns collapse into a compact "story so far" recap, and any past turn your action references is pulled back to full detail. Recall works two ways: by keyword, and by participant — when your action mentions a character, an older turn that character actually took part in is resurfaced even if its summary shares no words with what you typed (so returning to "talk to Mira" after many turns finds the right scene). The memory meter breaks out how much of the window the recap and rehydrated turns use. Runs an extra request per turn, so it can compete for the GPU on a single-machine setup.
- Link entities to locations from either side — in the World Editor a location's entities are now chosen from a searchable multiselect dropdown (replacing the long checkbox list), and each entity gained a matching Locations dropdown. The two stay in sync, so the relationship can be edited from whichever side you're on.
- Steadier World Editor tabs — the add/search row now sits below the editor tabs instead of above them, so the tab strip no longer shifts when you switch to or from the Overview tab.
-
Audience-labeled description fields — an entity's and location's descriptions are now split by audience: Player-Facing Description (shown in-game) and AI-Facing Description (full text sent to the model), plus a new AI-Facing Summary — a short version for use where the full description is too long, which a ✨ button generates from the AI-Facing Description using your connected LLM (with undo). Existing v1.2 worlds are upgraded automatically (their old
inGameDescription/detailedDescriptionfields are renamed on import). - Planning step uses AI-Facing Summaries — the optional pre-reply planning request now feeds the model each location's and entity's short AI-Facing Summary (falling back to the full description where none is authored), keeping the planning pass lightweight. Narration still gets the full descriptions. The plan also lists each present character with their placement — where they are and what they're physically doing right now — giving the narration concrete staging to work from.
- Smarter next-action choices — the choices request sees the player's most recent action (not just the resulting narration), so suggestions track what you were actually trying to do, and it uses the short AI-Facing Summaries instead of full descriptions. It's scene-aware: it only sees the characters actually present (named in the recent narration, within a short rolling window), not the whole location roster, so it won't suggest actions for people who haven't appeared yet or already left. And the story no longer duplicates the menu — the narrator prompt is a positive contract that writes only prose while the separate Choices step presents the options, so smaller models stop tacking a "What do you do next?" list onto the end of the narration.
- AI-context "Hydrations" view — the AI Context viewer (the per-turn debug popup) gained a Dictionary / Hydrations toggle. "Hydrations" shows the exact words and characters used to decide what older context gets recalled for that turn, as color-coded, clickable chips that highlight their matches — and only within the game-text request, so the choices/stats requests stay clean.
- Duplicate button in the World Editor — every list item (stats, entities, locations, traits, groups, dictionary entries) gains a duplicate button beside delete that makes a deep copy placed right below the original. Duplicating a trait keeps it in the same group; duplicating a group copies the whole nested subtree (subgroups and traits) with fresh ids in the exact same nesting. The copy's name gets a " (Copy)" suffix.
- Markdown toolbar for the world description — the Edit tab gained a formatting toolbar (bold, italic, headings, lists, link, quote, inline code) plus undo/redo (buttons and Ctrl+Z / Ctrl+Y); the Preview still renders through the same engine as in-game narration.
- Trait groups (folders) — the World Editor's Traits tab is now a foldering tree: the + opens a popout to Add Group or Add Trait, and traits/groups drag to reorder and nest — drag a row right to tuck it under the group above, left to pull it back out, with the row indenting live as you drag. New worlds start with World and Player groups. Traits and groups gain Player-Facing and AI-Facing descriptions, and traits gain an Enabled by Default flag. In play, the trait-selection popup is now tabbed by group (an implicit General tab holds ungrouped traits, nested groups add a tab row); default traits start checked, and the buttons are Next (walk the groups), Skip (start with the current selection), and Abort (cancel loading the world). A group's AI description is sent to the model as a header above its selected traits.
-
⚙️ Backend / invisible
- Shared hooks and helpers: a
createWorkerClientworker factory, a single IndexedDB open/promisify helper (idb),usePersistentState,useIsMobile, and a cached-thumbnail hook. - The Community Creations browser was split out of
MainMenuinto its ownCommunityCreationsBrowserview, with shared world-card/detail presentation helpers (WorldDetails). -
Trait group data model — the world format gains a
traitGroupsarray ({ id, name, playerDescription?, aiDescription?, parentId, order }); traits move from a singledescriptiontoplayerDescription/aiDescriptionand gaingroupId,isDefault, andorder. The tree, reordering, and AI trait-context builder live in pure, unit-tested helpers (lib/traitTree.ts); legacy v1.2 worlds rename their traitdescription→playerDescriptionon import. -
Character diaries — each character present in a turn quietly records a short first-person diary entry about it, from their own point of view, generated as turns age out (opt-in via Settings → Generation → Character Diaries; prompt editable under System Prompts → Diary; covers ad-hoc walk-ons too). With "Show Silent Requests" on, each entry is inspectable per turn in the AI-context viewer. In staged planning, a character's own recent diary is now fed back into its motivation pass as private memory, so it acts with continuity and only knows what it recorded — the start of real information asymmetry between characters. Entries are stored per-turn (
AITurnResult.diaries), an additive save-shape change that rolls back with the turn; old saves read as absent.
- Shared hooks and helpers: a
-
⚙️ Backend / invisible
- Dead code and noisy logging, including a leftover
bufferToSentencehelper and an auth-token console log.
- Dead code and noisy logging, including a leftover
-
👤 User-facing
- Mislabeled UI text and icons corrected.
- The memory meter is now an accurate token gauge of the model's context window — prompt + (actually-sent) history + reserved output as a % of the window — fixing the old bar that mixed characters and tokens and counted the full untrimmed log.
-
⚙️ Backend / invisible
- Entity IDs now use
crypto.randomUUID()instead of timestamp-based IDs, avoiding collisions. -
Cleaner AI prompts — blank or unset author fields (an empty entity
type, a missing description, etc.) are no longer padded into the location/entity/trait data sent to the model, and the editor-only starting-location flag is dropped from it. Empty fields previously leaked as blank lines or the literal textundefined, which could confuse smaller models. -
Stat code runs in a real sandbox — world-authored stat formulas (which ride inside downloaded worlds) now execute in an isolated QuickJS WebAssembly VM instead of
new Function, so they can no longer reach the page (fetch,localStorage, the DOM) — closing an untrusted-code hole. A runtime interrupt enforces the 1-second timeout, so awhile(true)loop is actually killed instead of hanging the tab (the old after-the-fact check never fired), with memory and stack caps on top. Only whitelisted stat data crosses into the VM; the numeric result is read back and clamped exactly as before. The engine is lazily loaded on first use, so it doesn't affect page load. - Lint cleanups across the converted codebase.
- Entity IDs now use
Playing
Reference
Project