Vega story embed
Create a story in a text file and play it on your own web page. Start with two lines of dialogue, then add a background, sound and a model. These steps use the hosted Vega browser module; you can follow them without installing npm packages.
1. Try the player here
Section titled “1. Try the player here”Keep Complete original example selected, click Load selected content, then Play. You should see a garden and Mira’s first line. Next line continues; Pause stops playback; the slider jumps within the story. Exit this player closes it before you load another story.
Start with the complete original file. Load it, then press Play to read the first line.
Open complete JSON · Download JSONRead the published Treasure story and its referenced game assets. The adapter prepares its runtime and model data for you.
Open the complete game story JSONSample JSON resources still come from the example address. For your own resources, set their directory in Advanced parameters.
Preparing example…
Advanced parameters and complete input — reload to apply
The original example uses this complete story.json and garden.svg. The Our Notes: Treasure option loads a published game story, including its model and sound resources. Settings in Advanced parameters take effect on the next load. Choosing a language selects available authored text; it does not translate a single-language story.
2. Save your first story
Section titled “2. Save your first story”Make a folder called my-story. In a plain-text editor, paste the following complete JSON and save it there as story.json, using UTF-8. Check that its filename is story.json, not story.json.txt. JSON contains data: keep the quotation marks and commas, and leave out JavaScript words such as const and import.
{"localization":{"locales":["en"],"defaultLocale":"en"},"commands":[ {"command":2,"targetName":"Mira","text":"This is my first story."}, {"command":2,"targetName":"Mira","text":"Press Next line to continue."}]}You can also open this complete dialogue JSON and save it as story.json. commands is the ordered list of story actions. Here, 2 means Talk, targetName is the speaker, and text is the line shown on screen. Edit the two lines first; keep the other fields as shown.
3. Make the web page and open it
Section titled “3. Make the web page and open it”Open the complete starter HTML, save it as index.html, and put it next to story.json. You can also download the complete starter folder ZIP and extract it to obtain this directory. The complete copyable source is below.
my-story/ index.html story.jsonServe the folder over HTTP. If Python is missing, install it from Python.org. With Python 3 installed, open a terminal in my-story and run:
python3 -m http.server 8000On Windows, use py -m http.server 8000. Open http://localhost:8000/ in your browser. An editor’s local HTTP preview also works. Double-clicking a file opens a file:// address; the JSON request needs an HTTP address. Stop the server with Ctrl+C when finished.
Click Load / retry, wait for Loaded, then Play. The first line appears; Next line reveals or advances the dialogue. This initial story has no image, so its background is plain. Closing the player frees its resources; you can load it again.
Complete index.html — copy everything into one file
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>My first story</title><style> body { max-width: 60rem; margin: auto; padding: 1rem; font: 1rem/1.5 sans-serif; color: CanvasText; background: Canvas; } #stage { height: 60vh; min-height: 320px; position: relative; } button { font: inherit; min-height: 48px; margin: .25rem; }</style><h1>My first story</h1><p id="status" role="status" aria-live="polite">Press Load to read story.json.</p><div id="stage"></div><button id="load">Load / retry</button><button id="play" disabled>Play</button><button id="pause" disabled>Pause</button><button id="next" disabled>Next line</button><button id="close">Close</button><script type="module">const button = id => document.getElementById(id);const status = document.getElementById("status");let handle, controller, generation = 0;
function controls(enabled) { for (const id of ["play", "pause", "next"]) button(id).disabled = !enabled;}async function close() { generation++; controller?.abort(); controls(false); const old = handle; handle = undefined; await old?.dispose();}button("load").onclick = async () => { button("load").disabled = true; let current; try { await close(); current = ++generation; controller = new AbortController(); const signal = controller.signal; status.textContent = "Loading…"; const { mountStory } = await import("https://haneoka.org/embed/vega.js"); const response = await fetch("./story.json", { signal }); if (!response.ok) throw new Error(`story.json: HTTP ${response.status}`); const storyData = await response.json(); signal.throwIfAborted(); if (current !== generation) return; handle = mountStory(document.getElementById("stage"), { document: storyData, locale: storyData.localization?.defaultLocale ?? "en", assetsBase: new URL("./", response.url).href, signal, onEvent(event) { if (event.type === "error" && current === generation) status.textContent = event.error.message; } }); await handle.ready; if (current !== generation) return; controls(true); status.textContent = "Loaded. Press Play, then Next line."; } catch (error) { if (current === generation) { const failed = handle; handle = undefined; await failed?.dispose().catch(() => {}); if (current === generation) status.textContent = `Loading failed: ${error.message}. Check the file, then retry.`; } } finally { button("load").disabled = false; }};button("play").onclick = () => handle?.play();button("pause").onclick = () => handle?.pause();button("next").onclick = () => handle?.next();button("close").onclick = async () => { await close(); status.textContent = "Closed. Press Load to try again."; };window.addEventListener("pagehide", () => { void close(); }, { once: true });</script></html>The script performs three separate actions:
const response = await fetch("./story.json"); // request the saved fileif (!response.ok) throw new Error(`HTTP ${response.status}`);const storyData = await response.json(); // turn JSON text into a JavaScript objectconst player = mountStory(document.getElementById("stage"), { document: storyData, // pass that object to the player locale: "en", assetsBase: new URL("./", response.url).href,});await player.ready; // the page enables Play only when loading succeedsThis excerpt explains the load section in the full HTML. A filename, URL and parsed object are different inputs:
| What you have | How to use it |
|---|---|
| A story object already created in JavaScript | mountStory(container, { document: storyData }) |
| An HTTP address pointing to a story JSON file | Fetch and parse it as above, or use httpDataSource in step 6 |
| A JSON file selected with a file input | Use fileDataSource; choose where its images and audio come from |
| A public Haneoka game story ID | Use haneokaStorySource in step 7 |
4. Add a background
Section titled “4. Add a background”Save garden.svg next to your files. Replace story.json with the complete background story, or insert this action at the start of its commands array, before the first Talk:
{"command":25,"background":{"url":"garden.svg"}}25 means Stage. Its background.url names the image file. Keep a comma between this action and the following Talk. Your folder now contains index.html, story.json and garden.svg. Reload the player: the garden appears behind the dialogue. The starter’s assetsBase points to this folder, so garden.svg resolves beside story.json.
5. Add one sound, then map several cues
Section titled “5. Add one sound, then map several cues”Save cue-tone.wav beside story.json and replace the JSON with the complete sound story. It plays a quiet, original test tone with the first line. This is the added field on that Talk action:
"voices": [{"soundId":10000001,"categoryName":"Voice","playableUrl":"cue-tone.wav","volume":0.5}]To use spoken dialogue, replace the WAV with your recording and change playableUrl to its exact filename. soundId identifies the cue; playableUrl locates the playable audio. Use Play from a click or tap so the browser can allow sound. The SDK reads declared audio through the shared resource transport and prepares playback URLs automatically. Resource requests use the configured byte budget and deadline; disposal releases the owned playback URLs.
When your existing story already contains cue IDs, put their addresses in one JavaScript map and fill the voice records before mounting. In the starter HTML, add this immediately after const storyData = await response.json();:
const sounds = { 10000001: "./cue-tone.wav" };for (const command of storyData.commands) { for (const voice of command.voices ?? []) { const key = voice.soundId ?? voice.id; if (Object.hasOwn(sounds, key)) voice.playableUrl = sounds[key]; }}This is host-side preparation for the sample’s commands[].voices; sounds is not a mountStory option. BGM and sound-effect actions use their bgm.playableUrl and se.playableUrl records. Public Haneoka stories already resolve voiceRefs, bgmRef and seRef through their sound registry. Other formats need an adapter that translates their cue fields into these records, including cues inside timelines or command groups.
6. Load a different URL or a selected file
Section titled “6. Load a different URL or a selected file”The complete source-selection HTML includes all four methods, visible loading errors, cancellation, playback and a progress slider. Save it as index.html beside the same JSON and assets. Choose Another JSON address, paste the address, then Load. Choose A JSON file on my computer to select the saved JSON instead.
For HTTP, the shorter SDK route replaces the starter’s fetch/JSON/mount block with:
const { mountStory } = await import("https://haneoka.org/embed/vega.js");const { httpDataSource } = await import("https://haneoka.org/embed/core.js");handle = mountStory(document.getElementById("stage"), { source: httpDataSource({ url: new URL("./story.json", location.href).href }), locale: "en", signal,});await handle.ready;httpDataSource fetches and parses the JSON internally. Relative resource paths use the JSON address’s directory; an absolute URL keeps its own origin. Set assetsBase only when your resources live in a different directory.
Selecting a JSON file grants access to that file alone. It does not grant access to neighbouring images, models or audio. The complete HTML uses fileDataSource(file) and serves those resources from the HTML folder. For fully local selected resources, see local files and fileResourceResolver. Model sidecar paths also need to resolve through that resource map.
7. Load a published Our Notes story with one entry
Section titled “7. Load a published Our Notes story with one entry”Use mountHaneokaStory when you have a Haneoka story ID. It selects matching player modules, the theme and Cubism runtime from one release manifest. You provide the story ID, server and language.
Open the complete short-story HTML, save it as index.html, and serve it as in step 3. Click Load, wait for Ready, then Play. This example uses afterlive_10109. Open its complete catalog JSON to inspect the authored commands and asset references.
import { mountHaneokaStory } from "https://haneoka.org/embed/vega-haneoka.js";const player = mountHaneokaStory(document.getElementById("stage"), { id: "afterlive_10109", server: "intl", locale: "en",});await player.ready;// Call from a button click:player.play();The complete HTML supplies the element, buttons, loading stages, cancellation, error display and disposal. Its native player also provides scene controls. The external buttons demonstrate the handle methods; your own page can keep the control set that fits its layout.
Complete index.html for the published short story
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Haneoka story</title><style>body{font:16px system-ui;margin:16px;background:Canvas;color:CanvasText}#stage{height:min(70vh,600px);position:relative;width:100%}button{font:inherit;margin:4px;min-height:48px;padding:8px 16px}output{display:block;margin:8px}</style><h1>Haneoka story</h1><button id="load" disabled>Load</button><button id="cancel" disabled>Cancel loading</button><button id="play" disabled>Play</button><button id="next" disabled>Next line</button><button id="exit" disabled>Dispose</button><output id="status" role="status">Preparing player</output><div id="stage"></div><script type="module">import {mountHaneokaStory} from "https://haneoka.org/embed/vega-haneoka.js";let player, generation=0;const by=id=>document.getElementById(id);const labels={manifest:"Preparing player",modules:"Preparing player",runtime:"Preparing player",data:"Loading story",resources:"Loading assets",ready:"Ready"};const controls=ready=>{by("play").disabled=!ready;by("next").disabled=!ready;};by("load").onclick=async()=>{ const current=++generation; by("load").disabled=true;controls(false); try{ await player?.dispose(); if(current!==generation)return; player=mountHaneokaStory(by("stage"),{ id:"afterlive_10109",server:"intl",locale:"en", onEvent:event=>{ if(current!==generation)return; if(event.type==="stage")by("status").textContent=labels[event.stage]; if(event.type==="error"){controls(false);by("status").textContent=String(event.error?.message??event.error);} }, }); window.storyPlayer=player; by("exit").disabled=false;by("cancel").disabled=false; await player.ready; if(current===generation){controls(true);by("status").textContent="Ready";} }catch(e){if(current===generation)by("status").textContent=player?.snapshot.phase==="cancelled"?"Cancelled":String(e.message??e);} finally{if(current===generation){by("load").disabled=false;by("cancel").disabled=true;}}};by("cancel").onclick=()=>{player?.cancel();};by("play").onclick=()=>{player.play();by("status").textContent="Playing";};by("next").onclick=()=>{player.next();};by("exit").onclick=async()=>{ ++generation;controls(false);by("cancel").disabled=true;by("exit").disabled=true; try{await player?.dispose();by("status").textContent="Disposed";} catch(e){by("status").textContent=String(e.message??e);} finally{by("load").disabled=false;}};by("load").disabled=false;by("status").textContent="Not loaded";window.addEventListener("pagehide",()=>{void player?.dispose();},{once:true});</script></html>The in-page Our Notes: Treasure preset keeps the longer millsage_004_1_02 story. Its complete public JSON contains 728 commands. Both presets use the same short API. Replace id with an ID returned by the story catalog.
Catalog JSON contains references. The Haneoka adapter prepares its runtime, model metadata and media records. To play your own complete Vega story JSON, keep the fetch → response.json() → mountStory({ document }) path in steps 3–6. The Haneoka helper supplies its resource origin; you only set a resource base when intentionally overriding its assets through advanced player options.
8. Prepare a standard Live2D model
Section titled “8. Prepare a standard Live2D model”The Cubism provider accepts a standard *.model3.json and legacy *.model.json. For a standard model, its character descriptor can point to the manifest:
{"runtime":{"format":"cubism3","model":"models/Mira/Mira.model3.json"}}Keep the exported directory structure:
models/Mira/ Mira.model3.json Mira.moc3 Mira.2048/texture_00.png motions/... expressions/...| Component | When it is needed |
|---|---|
model3.json, its referenced .moc3, and every referenced texture | Basic model rendering; names and paths must match the manifest |
| Cubism plugin, Web runtime module and compatible Cubism Core | Executing model rendering; the main story embed does not install a model provider automatically |
| Physics, pose and user-data JSON | When declared in FileReferences; remove unused declarations or provide the referenced files |
| Motion and expression files | When used by the story; preserve the manifest catalog and file paths |
| Extra shader JSON or a shader directory | No external shader configuration is required by this runtime; its rendering shaders are bundled |
See Live2D’s exported model file guide and model3.json specification. The provider uses the manifest to locate sidecars.
Save the complete model story template as story.json. Its command: 0 is the entry action, targets identifies Mira, positionType: 5 places her in the centre, and characterModel points to the model. Set runtime.model to your actual manifest path; keep the model sidecar filenames and directories intact. Use the complete HTML from step 6, expand Advanced: standard Live2D in my own story, enable the Cubism provider, choose the original JSON, Load, then Play. This template requires your own exported model files; its download does not contain the illustrated model directory.
ZIP input is not currently implemented. Extract the model archive, preserve its paths, serve the extracted files, and point the descriptor to its manifest. The existing model export creates ZIP downloads; it does not load ZIPs. The public options also do not include shaders or shadersDir. A custom shader-pack loader would require an additional runtime extension; adding those keys to a story has no such effect.
Fix a failed load
Section titled “Fix a failed load”| What you see | What to do |
|---|---|
story.json: HTTP 404 | Open http://localhost:8000/story.json; check the filename, directory and letter case |
| JSON parse error | Use the complete provided JSON, then edit only one text value; JSON uses double quotes and has no trailing commas |
Failed to fetch | Open the JSON address directly; use an HTTP page and check the browser’s failed request address |
| CORS error for another domain | Serve the files on your page’s origin, or configure that resource server’s response to allow your page’s origin; client request headers cannot grant CORS permission |
| Dialogue works but an image/audio file fails | Check its literal path and assetsBase; selecting one JSON does not select its sidecar files |
| Model runtime fails | Check the runtime module, Core, manifest, binary and texture requests separately; a successful JSON fetch proves only that the story file is readable |
| Load cancelled or player closed | Retry with a new handle after await player.dispose(); the complete examples do this for you |
Playback, cleanup and advanced integration
Section titled “Playback, cleanup and advanced integration”play() starts or resumes; pause() pauses; next() advances dialogue. await player.seek(0.5) moves to a command boundary halfway along the current reachable story path, including choices. It returns the restored command index. player.snapshot supplies state and progress; state events update about every 100 ms, with immediate updates for controls.
ready waits for the renderer, required resources, fonts and seek index. cancel() stops loading; aborting the supplied signal after readiness disposes the player. dispose() is idempotent. Await it before mounting another player in the same element. Resource failures appear through ready rejection and error events; observer errors are isolated through onListenerError.
The runtime resolves declared URLs and leaves dialogue, IDs and source labels intact. Local resource maps use literal paths, including Unicode. The core loader releases owned Blob URLs; model providers and the renderer release their decoded and GPU resources. Transport defaults are CORS, omitted credentials, 64 MiB per response and a 30-second resource deadline. Dynamic runtime modules/Core scripts have their own browser loading paths.
Every player shows a translucent Haneoka icon linking to the main site, initially in the top-left corner. brandingCorner: "top-right" moves it. The overlay keeps the full scene area. Your story and assets retain their own authorship and licenses.
For a bundled application, the package name is @haneoka/embed-vega; theme and Haneoka helpers also have package subentries. Public npm installation is pending. Use the hosted browser modules for this tutorial. When building from the source repository, install its workspace dependencies and build before packing. Keep the theme’s font and sprite assets with its built module. The parameter reference below covers the complete integration surface.
For Engine integrations that supply their own file/native resource port, see host resources: complete types, cancellation and lease ownership.
Every mountHaneokaStory option
Section titled “Every mountHaneokaStory option”The browser entry is https://haneoka.org/embed/vega-haneoka.js; the bundled package subentry is @haneoka/embed-vega/hosted. The hosted entry selects its own release manifest by default. A direct package import defaults to https://haneoka.org/embed/manifest.json. The container is a browser HTMLElement with a nonzero layout size. Missing browser context or a blank/non-string ID throws TypeError; a second hosted mount in the same container before disposal throws Error. The handle returns immediately; keep it while awaiting ready.
| Option | Type; required/default | Effect and constraints |
|---|---|---|
| id | required string | Exact story ID returned by the current catalog. Empty/blank/non-string rejects synchronously; an unavailable ID rejects ready with the source error. |
| server | optional string; intl | Asset server, independent of text language. Uses the existing catalog slug validation and availability. |
| locale | optional string; ja | Startup content and UI language. Public story arrays use ja/en/zh-TW/zh-CN/ko; select one of the story’s authored languages. |
| signal | optional AbortSignal | Cancels bootstrap; abort after ready disposes the player. A supplied already-aborted signal rejects ready. |
| manifestUrl | optional absolute HTTP(S) URL; entry-local release manifest for hosted ESM, stable root for package import | Select a compatible SDK manifest. Invalid URL/protocol throws; malformed/mixed-version metadata rejects ready. Use one complete release. |
| apiBase | optional absolute HTTP(S) directory; https://haneoka.org/api/v1/ | Alternate compatible story catalog. Uses core directory validation; model paths still come from that catalog. |
| runtime | optional CubismEmbedOptions; release runtime descriptor | Advanced runtime override. Required nested fields are moduleUrl and runtime.cubismCoreUrl; runtime.cubism2CoreUrl and runtime.motionSyncCoreUrl are optional. Use compatible licensed module/Core URLs. Factory contract is below. |
| playerOptions | optional object; | Selected mountStory options passed to the assembled player; complete fields and exclusions below. |
| fetcher | optional (Request)=>Promise<Response>; native fetch | Manifest, runtime descriptor, story and resource transport. The default first-party requests use no-referrer. Dynamic modules/Core scripts retain native browser loading. |
| headers | optional HeadersInit; none | Core transport headers; script/module requests use their native paths. |
| credentials | optional omit/same-origin/include; omit | Resource transport credentials. Public reads use omit; credentialed cross-origin responses need appropriate server permission. |
| maxBytes | optional positive safe integer; 67108864 | Per-response encoded-byte limit for bootstrap and child transport. Invalid values throw RangeError; exceeding the limit rejects the relevant read. It is separate from GPU/decoded-memory limits. |
| onEvent | optional (HaneokaStoryEvent)=>void | Receives stage, load, state, seek and error events from construction onward. |
| onListenerError | optional (unknown)=>void | Observer exceptions are isolated and forwarded here; listener failure does not become playback failure. |
playerOptions accepts theme, brandingCorner, shell, plugins, renderer, assetsBase, resolveResource, resourceTimeoutMs, resolveLocalizedText, onListenerError. Their nested fields, defaults, ranges and errors are the same as the mountStory tables below. This helper defaults theme to haneoka, installs theme/Cubism plugins, and appends the supplied plugin array. Its child uses the Haneoka game shell unless shell is explicitly configured. Keep the catalog’s resource base for normal use; override assetsBase/resolveResource only when intentionally replacing resources. Child-option validation runs during bootstrap and rejects ready; top-level onListenerError handles hosted observers, while playerOptions.onListenerError follows the child contract.
playerOptions excludes source, document, server, locale, signal, onEvent, fetcher, headers, credentials and maxBytes. Supply the shared context fields at the top level. This first-party helper accepts a catalog ID. For an authored document, HTTP JSON URL or selected File, use mountStory and the source factories in steps 3–6.
| Handle member | Type/signature | Behavior |
|---|---|---|
| ready | Promise<void> | Waits for selected modules, runtime descriptor and child data/model/font/seek preparation. Rejects the original bootstrap/load error. |
| snapshot | StoryEmbedSnapshot | phase loading/ready/cancelled/error/disposed, playing/paused/finished/seeking, commandIndex/commandCount/progress. Wrapper phase describes this handle. |
| player | VegaPlayerHandle or undefined | Advanced child access; its lifetime is owned by the hosted handle. |
| stage | manifest/modules/runtime/data/resources/ready | Current preparation stage, suitable for your own user-facing loading label. |
| release | string or undefined | Selected immutable SDK release after the manifest is read. |
| play/pause/next | ()=>void | Require ready; otherwise throw Error. Delegated playback errors use the child error event. |
| seek | (ratio:number)=>Promise<number> | Finite 0–1 command-path progress; returns the restored boundary. Child range/lifecycle errors retain their meaning. |
| subscribe | (listener)=>unsubscribe | Receives future HaneokaStoryEvent values; subscribing after disposed throws Error. |
| cancel | ()=>void | Retires loading bootstrap. After ready use dispose. |
| dispose | ()=>Promise<void> | Idempotent; cancels, awaits bootstrap cleanup, disposes the child and loader, releases its container registration. Cleanup failures can reject AggregateError. Await before reuse. |
Stage events are {type:"stage",stage,release?}. Load/state/seek/error shapes match mountStory below. The public module manifest and runtime descriptor belong to the same release; invalid module paths, runtime descriptor hash/length mismatch, runtime paths outside the distribution origin, import failures and resource errors reject ready. Failed bootstrap cleans up its child and registration so the host can create a fresh handle. Continue to dispose the failed handle to close your own lifecycle.
Every mountStory option
Section titled “Every mountStory option”Required positional container is a browser HTMLElement with an ownerDocument/defaultView and nonzero layout dimensions. A nonbrowser container throws TypeError; mounting a second story in the same container before disposal throws Error. It returns the handle synchronously. In TypeScript choose exactly one document or source; the implementation prefers a supplied source, while missing/invalid commands reject ready.
| Option | Type; required/default | Effect, constraints and failures |
|---|---|---|
| document | AdvStory; required alternative to source | In-memory engine document. commands must be an array; malformed document rejects ready with TypeError. Objects are authored input, not a required five-language catalog. |
| source | DataSource<AdvStory>; required alternative to document | load(context) returns the engine document; memory/http/file/custom factories are supported. Source failures reject ready. |
| server | optional string; undefined, public adapter intl | Asset registry passed to source, independent from locale. |
| locale | optional string; en | Author/playback language; startup also sets the player’s locale and shell UI language. |
| assetsBase | optional absolute HTTP(S) directory; source base | No query/fragment; overrides source resource origin. Invalid base TypeError. |
| resolveResource | optional (key,DataContext)→string/URL/Blob or Promise; key unchanged | Maps declared media/model/font paths; already-resolved URLs pass through once. Resolver errors reject ready/resource use. |
| fetcher | optional (Request)→Promise<Response>; fetch | Data and resource bytes use core transport. Modules/Core script loaders have their separate loading path. |
| credentials | omit/same-origin/include; omit | Loader requests; service must allow credentials/origin when applicable. |
| headers | optional HeadersInit; none | Core transport headers, not script/module request headers. |
| maxBytes | positive safe integer;67108864 | Per document/resource decoded bytes; illegal value RangeError. Not a combined GPU/cache budget. |
| onListenerError | optional (unknown)→void | Observer-error sink, isolated from playback results. |
| theme | portable/haneoka; portable | Haneoka requires its theme/shell plugins. Missing contributions reject ready. |
| brandingCorner | top-left/top-right; top-left | Always-visible translucent corner attribution over the full-size scene; no hide option. |
| shell | false or VegaPlayerShellOptions | Portable defaults false; Haneoka defaults {initialScreen:"game"}. Full fields below. |
| plugins | readonly VegaPlugin[];[] | Appended providers/commands after richtext, portable UI, Three and resource adapter. Real plugin setup/manifest/contributions are required; setup/dependency failures reject ready. |
| renderer | ThreeRendererPluginOptions;{} | Full renderer and post-texture settings below. |
| resourceTimeoutMs | positive safe integer;30000 | Total deadline for each adapter resource read. Illegal value RangeError; elapsed resource request rejects with TimeoutError. It is not a whole-story deadline. |
| signal | optional AbortSignal | Cancels boot; abort after ready disposes this player. The per-resource timeout remains separate. |
| onEvent | optional (StoryEmbedEvent)→void | Attached before boot; same event shapes as subscribe. |
| resolveLocalizedText | optional (value:unknown)→{text:string,lang?:string} | Custom text resolution; default Vega localization. Must produce the resolver shape; own exceptions can fail the active operation. |
The nine common loader options use the detailed core transport/resource contract. There is no mount autoplay or general loadTimeoutMs option. Use an external signal/deadline for whole loading; start playback from a user gesture.
Nested shell options and settings
Section titled “Nested shell options and settings”| shell field | Type/default | Meaning |
|---|---|---|
| storage | optional VegaSaveStorage; memory | read(key)→Promise<string|null>, write(key,value)→Promise<void>, remove(key)→Promise<void>, list(prefix)→Promise<readonly string[]>. Caller backend owns persistence; malformed saves fail the relevant save/load operation. |
| projectId | optional string | Trimmed explicit ID, then story.vegaProject.id/story.id, then vega-story. Save namespace identity. |
| settingsId | optional string; projectId | Separate preferences namespace; persisted settings take precedence over initialSettings. |
| initialSettings | optional Partial<VegaNarrativeSettings>; table defaults | Applied during shell creation. Mount then applies outer locale to player/UI; use outer locale for startup language. |
| title | optional string | Trimmed explicit title, then localized project/story title/name, then projectId. |
| initialScreen | title/game | Generic shell defaults title; Haneoka embed’s implicit shell starts game. |
| onExitRequest | optional ()=>void/Promise<void> | Receives shell Exit after it pauses/returns to title. Host chooses navigation/close; callback failure rejects that shell operation. |
| initialSettings key | Type/default | Range and behavior |
|---|---|---|
| textSpeed | number/1 | Clamped .1–5. |
| autoDelay | number/1.5 | Seconds, clamped0–30. |
| masterVolume | number/1 | Clamped0–1. |
| bgmVolume | number/.8 | Clamped0–1. |
| voiceVolume | number/1 | Clamped0–1. |
| seVolume | number/.9 | Clamped0–1. |
| language | string/auto | Narrative language or auto; stored string up to64 characters. |
| uiLanguage | string/auto | UI language, separate from server. |
| reducedMotion | boolean/false | Reduce supported UI motion. |
| highContrast | boolean/false | Supported UI contrast preference. |
| instantText | boolean/false | Complete readable text immediately. |
| webText | boolean/false | Browser text mode for Haneoka Reading UI; version note below. |
| textSize | number/1 | Clamped .5–2. |
| subtitlesEnabled | boolean/true | Display available subtitles. |
| bgmEnabled | boolean/true | BGM enabled preference. |
Nonfinite numeric settings use their fallback; boolean settings are normalized. Unknown shell setting keys throw RangeError when set. webText is present in the published source revisions engine1916c57/theme1d5b20e/shell436ade8; use a matching build or a later CDN receipt containing those revisions. If you explicitly pin the old r22 player, use the settings supported by that revision. On a matching shell, set shell.initialSettings.webText at construction or story.player.shell.setSetting("webText", true) after ready.
Nested renderer options
Section titled “Nested renderer options”| Field | Type/default | Meaning and combinations |
|---|---|---|
| rendererId | vega-three-webgl2/haneoka-three-webgl2; vega-three-webgl2 | Scene provider route; normally leave the embed’s backend intact. |
| profile | vega/unity-adv;vega | Provider scene profile. |
| staticPortraitFallback | boolean/true | Permit static portrait fallback when a model provider is missing. |
| colorGradingMode | ldr/hdr;ldr | Post color pipeline. |
| contributionId | string/haneoka.renderer-three | Installed renderer contribution identity. |
| contributionName | string/Vega Three WebGL2 | Human-readable contribution name. |
| backend | vega-three-webgl2/haneoka-three-webgl2;vega-three-webgl2 | Contribution route. Embed selects vega-three-webgl2; incompatible registration rejects player creation. |
| postTextureResolver | optional ThreePostTextureResolver | Cannot combine with postTextureAssets (TypeError). resolver owns supplied textures and optional dispose is tied to plugin lifetime. |
| postTextureAssets | optional Readonly<Record<string,string>> or ReadonlyMap<string,string> | Authorized URLs keyed by stable renderer texture key. Builds a resolver if supplied alone. |
Custom postTextureResolver has required resolve(reference:unknown,usage)→Three.Texture|null, optional preload(requests,signal?)→Promise<void>, optional dispose(). Each request is {reference,usage}; usages include film-grain, color-lookup and lens-dirt. A null result allows the existing pipeline fallback. Return textures compatible with the selected Three instance; the provider owns their cleanup. Parameter conflicts throw synchronously during plugin creation; load/render/provider errors arrive through ready/events.
Source, Cubism and theme factories
Section titled “Source, Cubism and theme factories”haneokaStorySource({id,apiBase?}) requires a public story ID. apiBase defaults https://haneoka.org/api/v1/ and follows core directory validation. It loads the selected story, story-runtime and only declared Live2D metadata, with server/locale from the mount. A missing story/model or malformed catalog record rejects load; no release/account argument is needed.
cubismStoryPlugin({moduleUrl,runtime}) requires a separately licensed absolute module URL and runtime.cubismCoreUrl. runtime.cubism2CoreUrl? and runtime.motionSyncCoreUrl? are optional version-specific script URLs. The module must export createCubismWebRuntimeAdapter; a missing export/provider capability rejects model preparation. This plugin lazily loads the adapter when a model needs it, preserves the provider lifecycle, and does not bundle Core. Keep compatible adapter/Core versions together.
haneokaStoryPlugins has no options: it is the theme+default-shell plugin list. createHaneokaThemeAssetsPlugin(provider) accepts optional assets(), resolveChatImage(value,kind:icon|stamp), and resolveSourceAsset(path) callbacks. assets returns optional URL fields arrow,auto,chatWindow,chatBackground,chatBackgroundFallback,chatLock,chatTextBox,chatComposerPlus,chatComposerPhoto,chatComposerPicture,chatComposerSmile,chatComposerMicrophone,chatIcons,fast,frame,fullscreen,subtitles,skip. Missing overrides use existing theme assets. HANEOKA_POST_TEXTURE_ASSETS is the fixed authorized renderer asset map; supply it as renderer.postTextureAssets when needed.
Document shape and complete handle/events
Section titled “Document shape and complete handle/events”AdvStory has commands (required by this mount), optional runtime and localization, plus source-defined fields. localization contains optional locales:string, defaultLocale:string, arrayOrder:string; these are author-defined, not a fixed five-language requirement. Runtime is Vega’s Partial<AdvRuntimeConfig>, retained through the engine. Individual command opcodes/resource structures follow the selected command/provider schema; the example uses Stage25 and Talk2 from the public protocol.
| Handle member | Signature | Behavior/errors |
|---|---|---|
| ready | Promise<void> | Renderer/resources/fonts/seek-index boot. Rejection remains available even when onEvent observes error. |
| snapshot | readonly object | phase,playing,paused,finished,seeking,commandIndex,commandCount,progress. phase:loading/ready/cancelled/error/disposed. |
| player | VegaPlayerHandle or undefined | Advanced ready engine handle: id,player,lifetime,root,slots,shell?,dispose(). Let the embed own disposal. |
| play / pause / next | ()=>void | Require ready (otherwise Error). Play resumes; async interpreter failure becomes error event. Next requests readable advancement. |
| seek | (ratio:number)=>Promise<number> | Finite0–1, otherwise RangeError. Returns actual command index; preserves play/pause intent. |
| subscribe | (listener)=>unsubscribe | Future events; subscribing after disposed throws Error. |
| cancel | ()=>void | Only loading boot; after ready it is a no-op. Dispose before retry. |
| dispose | ()=>Promise<void> | Shared/idempotent Promise; release engine before loader/URLs, remove host DOM/listeners, await boot cleanup. Cleanup failures AggregateError. |
Events are {type:"load",event:EmbedEvent}, {type:"state",snapshot}, {type:"seek",commandIndex}, {type:"error",error}. Load event fields follow core; state publishes changed snapshots at100ms and on controls. Listener exceptions are isolated. A disposal completes with phase disposed; its state/load events can be observed by construction listeners. Resource/source/plugin failures keep their actual errors; cancellation rejects signal.reason. This embed does not add a second error-code taxonomy.
This page uses the stable aliases. The older immutable r22 player retains its reserved-row attribution layout. When explicitly pinning a revision, keep engine, theme, shell and runtime versions compatible.