Show a web image
register resource [https://example.com/card.png]
as asset [Card]
show asset [Card] on this sprite
The downloaded image is also stored in the browser cache under Card.
TurboWarp custom extension · v0.13.0
Asset Manager registers web images and audio, project costumes, backdrops, sounds, and live text. Your scripts can then display, play, animate, cache, and replace them through one consistent set of blocks.
Kamishibai DSL 4.0 bundle
In the DSL 4.0 runtime palette, find the Asset Manager member heading and click its documentation button. It opens this guide. The bundled blocks keep Asset Manager's behavior and names; the bundle only adds a member-specific namespace and icon.
Quick start
Asset Manager must run without the extension sandbox because it works with TurboWarp's renderer, audio system, and project resources.
In TurboWarp, choose Add Extension → Custom Extension and paste:
https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-asset-manager@0.13.0/dist/asset-manager.js
Enable Run extension without sandbox.
Give the resource a short name that the rest of the project will use.
register resource [https://example.com/hero.png]
as asset [Hero]
The same name now works in display, sound, and animation blocks when its type fits.
show asset [Hero] on this sprite
How it works
Registration records the asset's type and location under the name you choose. Later blocks resolve that name and select the correct renderer, audio, or text path automatically.
Web URLs, project resources, and runtime text enter the register resource block. The asset registry remembers their chosen names and types. A display, play, or animate block then routes each asset to a sprite, stage, sound output, or Animated Text.
| Source | Resource identifier | What registration keeps |
|---|---|---|
| Web image or audio | https://example.com/asset.png |
Downloaded bytes in memory and IndexedDB |
| Sprite costume | costume:Sprite1:costume1 |
A reference to the existing project costume |
| Stage backdrop | backdrop:backdrop1 |
A reference to the existing backdrop |
| Sprite or stage sound | sound:Sprite1:sound1 or sound:@stage:name |
A reference to the existing project sound |
| Live text | text:Narration |
The runtime-variable name; the current value is read when shown |
| Previously cached web asset | Leave RESOURCE_ID empty |
Bytes loaded from IndexedDB under the asset name |
Supplying a URL always fetches fresh data and refreshes the named cache entry. Supplying an empty resource identifier skips the network and reloads that same name from IndexedDB. The per-name generation is checked before a fetched response may update IndexedDB or the in-memory registry, so an older request that finishes late cannot overwrite the latest one.
With a URL, Asset Manager fetches the resource, normalizes its media type, verifies that the request is still the latest generation, writes the named entry to IndexedDB, and registers it in memory. With an empty resource identifier, Asset Manager reads the named entry from IndexedDB and registers it in memory without a network request.
Version 0.8.0 lets composition hosts register a costume, backdrop, or sound with a
structured locator. Scratch names are matched exactly, including leading and
trailing spaces, ., /, :, and control characters.
Structured locators also preserve the logical registration name literally by default.
await assets.registerProjectAsset({
name: 'Costume.1 / presentation',
locator: {
kind: 'costume',
target: 'Actor / presenter',
name: 'Costume.1 / source'
}
});
A sound locator without target selects the Stage. The existing string
resourceId form remains compatible and keeps its historical trimming and colon
grammar. Embedded assets can request literal logical names with
nameMode: 'literal'.
Version 0.6.0 adds a block-free, cache-first path for app shells and composite extensions. It is separate from the legacy name-keyed cache above. The host supplies the network loader and an expected SHA-256, byte size, and Content-Type. Asset Manager returns a valid IndexedDB hit without a network request; otherwise it downloads, verifies, and only then stores the bytes. Source URLs and credentials are not persisted.
import {
createAssetManagerComposition,
createVerifiedRemoteCacheDatabaseName
} from '@kubohiroya/turbowarp-asset-manager/composition';
const databaseName = createVerifiedRemoteCacheDatabaseName({
id: story.cacheId,
label: story.fileName
});
const assets = createAssetManagerComposition(undefined, {
verifiedRemoteCache: {
cacheIdentity: {id: story.cacheId, label: story.fileName, databaseName}
}
});
const result = await assets.resolveVerifiedRemoteBinary(model, {
load: async ({url}, {signal}) => {
const response = await fetch(url, {signal});
return {
bytes: await response.arrayBuffer(),
contentType: response.headers.get('content-type'),
transferOwnership: true
};
}
});
The story manifest supplies a stable story ID and readable filename. Asset Manager uses them to identify a per-story IndexedDB database, verifies downloaded bytes, and returns them to the host. The host materializes an image, sound, or model and releases that in-memory resource according to scene or story retention without deleting the persistent verified bytes.
| Layer | Lifetime and cleanup | Host responsibility |
|---|---|---|
| Returned binary | Owned by the caller after resolution | Drop references after registration; transferOwnership: true avoids an extra full-size JavaScript copy. |
| Materialized memory | retention: scene or retention: story |
Call releaseAsset or releaseAll when the policy expires. |
| Verified IndexedDB bytes | TTL, LRU, origin budget, or explicit clear | Offer cache visibility and clear controls; memory release does not delete this cache. |
| Story catalog and lease | One readable database record per story; active runtimes are protected | Renew while running and release the lease when the story or session stops. |
Story databases use names such as
tw-kamishibai-assets-v1--opening-yaml--story-0001. A small shared catalog stores
only understandable identity, size, timestamps, and runtime leases—not asset binaries—and
lets an app shell list, prune, or delete caches. Cleanup skips every database with an active
lease. The default high-water mark is the smaller of 256 MiB and 20% of the reported origin
quota; inactive story databases and least-recently-used records are removed first. Records
unused for 30 days are eligible for removal. IndexedDB failures and pinned-origin budget
pressure are reported as machine-readable warnings while verified bytes remain usable in
memory.
Use listVerifiedRemoteStoryCaches,
pruneVerifiedRemoteStoryCaches, and
deleteVerifiedRemoteStoryCache for app-level storage management. Use
renewVerifiedRemoteStoryCacheLease and
releaseVerifiedRemoteStoryCacheLease for runtime ownership. The DSL values
retention: scene and retention: story are host lifecycle policy;
Asset Manager does not parse YAML or reinterpret them as IndexedDB TTL.
Version 0.7.0 adds a block-free binary bundle store for self-contained assets such as a Teachable Machine Pose model. Import the composition subpath, choose a story-specific database, and bind every operation to the story/source namespace, asset name, and manifest bundle integrity.
const assets = createAssetManagerComposition(undefined, {
binaryBundleStore: {
databaseName: `${story.cacheDatabaseName}--bundles-v1`
}
});
await assets.putBinaryBundle({
namespace: `${story.id}/${story.sourceIntegrity}`,
name: 'RescuePose',
integrity: pose.bundleIntegrity,
files: [
{path: 'model.json', size: model.size, integrity: model.integrity, bytes: model.bytes},
{path: 'metadata.json', size: metadata.size, integrity: metadata.integrity, bytes: metadata.bytes},
{path: 'weights.bin', size: weights.size, integrity: weights.integrity, bytes: weights.bytes}
]
});
const stored = await assets.getBinaryBundle({
namespace: `${story.id}/${story.sourceIntegrity}`,
name: 'RescuePose',
integrity: pose.bundleIntegrity
});
The host supplies a story-scoped key and all bundle files. Asset Manager verifies every declared size and SHA-256, commits the bundle and metadata in one IndexedDB transaction, and returns the complete verified bundle on a later read. Missing, partial, or corrupt records fail closed.
putBinaryBundle resolves only after IDBTransaction.oncomplete and
defensively owns the supplied bytes. getBinaryBundle rechecks file paths, sizes,
and canonical SHA-256 values before returning data. deleteBinaryBundle removes
data and metadata atomically; releaseBinaryStore aborts that store instance
without deleting persistent records. TTL, LRU, bundle-count, and byte limits are enforced
independently from the verified remote cache and the Standalone asset database. Failures
expose stable ASSET_BINARY_BUNDLE_* codes without payload bytes.
Version 0.10.0 adds a separate composition contract for large binaries that already ship inside the current application package. Unlike the persistent bundle store above, session backing never reuses records at the next startup. It uses one shared versioned database, includes a fresh session ID in every compound key, and removes only that session on normal disposal. The verified remote cache remains unchanged.
const backing = await assets.createSessionBinaryBacking({
policy: 'prefer',
sessionId: crypto.randomUUID(),
assets: embeddedManifest.assets,
source: embeddedPackageSource,
onFatalError(error) {
stopRuntimeAndShowDiagnostic(error);
}
});
const pose = await backing.get(poseAssetKey);
await backing.dispose();
Disabled mode reads the original source without opening IndexedDB. Prefer and required modes write one asset at a time and verify each committed record. Prefer can select direct mode only after a pre-establishment storage availability failure and clean partial records. After session mode is established, every read failure is fatal and never switches source.
disabled never opens IndexedDB. required fails startup when backing
cannot be established. prefer reports a warning and keeps the same source for
direct reads only when IndexedDB is unavailable, blocked, out of quota, or aborted before
establishment; descriptor, size, and SHA-256 failures always fail closed. Session mode
releases the source only after every asset commit and read-back succeeds. A later missing,
corrupt, integrity-failing, aborted, or closed-connection read is fatal and never triggers a
source reread, implicit rewrite, or mode change. Short heartbeat leases and bounded startup
cleanup reclaim crashed sessions without deleting an unexpired sibling tab. Renderer,
audio-decoder, and model-loader failures remain separate materialization diagnostics.
Replacement rollout is controlled by two startup-fixed flags. Both default to
false. Define them before loading the extension bundle when a project is ready
to opt in:
globalThis.__TW_ASSET_MANAGER_FEATURE_FLAGS__ = {
ENABLE_LIVE_ASSET_REPLACEMENT: true,
ENABLE_STRICT_ASSET_KIND_REPLACEMENT: true
};
Asset Manager first prepares the new content, then updates the registry, reapplies it to only the targets still bound to that asset name and public kind, and finally releases the old owned resource. If preparation or display reapply fails, the old registration, display, and cache are restored.
Images and text refresh immediately. Text rereads its latest body and full style, and starts
a configured animation again from the beginning. Audio already playing continues; the next
playback uses the replacement. Same-name commits run serially, so an active commit finishes
atomically and the last-started successful registration remains afterward. A newer invalid
attempt does not cancel an earlier valid one. For images, Asset Manager also verifies the
renderer skin it applied before refreshing a target. A later costume or renderer change
releases that stale display binding instead of being overwritten. With strict-kind
replacement enabled, the public kinds
external, costume, backdrop, sound, and
text cannot replace one another. External image/audio changes are rejected too.
Delete the old registration first when a kind change is intentional.
Upgrading from 0.3.0: update scripts that compare
asset registration error type against the previous lowercase type tokens.
Version 0.4.0 returns the uppercase diagnostic codes below.
User-facing errors begin with [Asset Manager][CODE] and retain the operation,
relevant asset/resource/actor names, a correction hint, nearby candidates, and the original
cause. The registration error reporters expose the latest code and its most relevant label.
Synchronous failures are thrown without duplicate logging; background playback and animation
failures are sent to console.error once.
| Code | What to check |
|---|---|
INVALID_ASSET_NAME | Use a non-empty valid registration name. |
ASSET_NOT_REGISTERED | Register the requested name; inspect suggested nearby names. |
ASSET_TYPE_MISMATCH | Use the image, audio, or text kind required by the operation. |
ASSET_TYPE_CHANGE | Delete first before intentionally changing a public or external media kind. |
SPRITE_NOT_FOUND | Check the actor or target name and that its drawable still exists. |
SPRITE_NAME_AMBIGUOUS | Give actor targets unique names. |
SOURCE_ASSET_NOT_FOUND | Check the costume, backdrop, or sound name. |
RESOURCE_ID_INVALID | Use HTTP(S) or a supported project-resource identifier. |
DEPENDENCY_MISSING | Load the required TurboWarp extension or runtime service. |
STYLE_PROPERTY_INVALID | Use a supported text style property. |
STYLE_VALUE_INVALID | Correct the stored text style value. |
PLAYBACK_FAILED | Check audio data and browser playback permission. |
ANIMATION_FAILED | Check the actor, assets, and Animated Text dependency. |
REPLACEMENT_FAILED | Check loading or display preparation; the old state is retained. |
ASSETS and DURATIONS describe a timeline. Images change the actor's
skin; audio starts playing. A zero duration groups the next asset with the current event.
ASSETS = NoonSkin,Bell,NextSkin and DURATIONS = 0,1,2At time zero, NoonSkin is shown and Bell starts because their separating duration is zero. After one second, NextSkin is shown. In a loop, it waits two more seconds and returns to the first event.
Text assets use two supporting unsandboxed extensions: Temporary Variables stores the live value and style; Animated Text draws it on the destination sprite or clone.
Register text Narration, then set its value and optional style. When it is shown, Asset Manager reads the latest temporary variables, reapplies the complete style, and asks Animated Text to display it on the target sprite. Updating a displayed text value refreshes every target that currently shows that asset.
Practical recipes
register resource [https://example.com/card.png]
as asset [Card]
show asset [Card] on this sprite
The downloaded image is also stored in the browser cache under Card.
register resource [costume:Hero:running]
as asset [HeroRunning]
show asset [HeroRunning] on this sprite
No image is copied. Asset Manager points to the costume already owned by the project.
register resource [sound:@stage:opening]
as asset [Opening]
play asset [Opening] as sound until done
Use the non-waiting play block when the next script step should begin immediately.
register resource [text:Narration]
as asset [Narration]
set text asset [Narration] to [Once upon a time…]
set text asset [Narration] style [animation] to [typing]
show asset [Narration] on this sprite
Load Temporary Variables and Animated Text before using text display blocks.
Block guide
| I want to… | Use this block | Important behavior |
|---|---|---|
| Register any source | register resource … as asset … | A URL refreshes the cache; an empty ID reads it. |
| Inspect registration failure | asset registration error type/label | Returns a stable diagnostic code and relevant name. |
| Check registration | asset … is loaded | True for every registered asset type. |
| Show on a sprite or clone | show asset … on this sprite | Accepts image and text assets. |
| Change the stage | set stage backdrop to asset … | Accepts external images, costumes, and backdrops. |
| Play audio | play asset … as sound | Use “until done” when the script must wait. |
| Animate an actor | loop actor … / play actor … once | Lists can mix image and audio assets. |
| Update live text | set text asset … | Currently displayed targets refresh. |
| Release registrations | delete asset … from memory | Project-owned costumes and sounds are not deleted. |
Troubleshooting
asset registration error type reporter
returns REPLACEMENT_FAILED for a failed web request.
costume:Hero:running when a
shorthand could be ambiguous. Check asset registration error type and
asset registration error label to identify the missing project resource.
The code is SOURCE_ASSET_NOT_FOUND, or SPRITE_NOT_FOUND when the
source sprite itself is missing.
ASSET_TYPE_CHANGE means strict replacement found a different public kind, or
an external image/audio change. Delete the existing in-memory registration first only when
that change is intentional. REPLACEMENT_FAILED means the old registration and
managed display were retained because the new content could not be prepared or reapplied.