Skip to content

Home Spot scene embed

@haneoka/embed-home-spot mounts a Three/Spine scene inside a container on your website. It accepts a scene you author or a current public Home Spot selected by ID. The public scene provider owns the camera, authored animation, layer composition, hit testing, decoded assets and WebGL lifecycle. The embed connects portable data sources and encoded resource bytes to that provider.

  1. Scroll to Run and edit a complete scene below.
  2. Keep Authored scene selected, then click Load. A small blue attachment rotates in the scene.
  3. Click the displayed character button to select it; click Clear selection to restore the whole scene.
  4. Click Replay, then Export PNG. Click Download PNG to save the 512×288 image.
  5. Click Exit and dispose to close the scene before starting another example.

The runtime loads after your click. This path uses the public browser module and needs no package installation.

  1. In the same example, click Haneoka ID. The JSON changes to spotId: "40002" and server: "intl".
  2. Click Load and wait for Ready. The character buttons now come from this scene’s actual layers.
  3. Use those buttons for selection, then try Replay, Export PNG and Exit and dispose.

For integration in your own application, provide a container and import the hosted ESM:

<div id="spot"></div>
import { mountHaneokaHomeSpot } from "https://haneoka.org/embed/home-spot.js";
const spot = mountHaneokaHomeSpot(document.querySelector("#spot"), {
spotId: "40002",
server: "intl",
locale: "en",
ariaLabel: "Garage scene",
onEvent(event) {
if (event.type === "error") console.error(event.error);
},
});
await spot.ready;
const character = spot.document.layers.find(layer => layer.characterId > 0);
if (character) spot.update({ selectedCharacterId: character.characterId });
spot.replay();
const png = await spot.exportPng({ width: 512, height: 288 });
// Keep the handle while your scene is visible.
// Call await spot.dispose() when the page/component closes.

This is an integration fragment for a module script or application. Use the working page example above to try its controls directly. The public adapter reads the current stories document and selects homeSpots[spotId].spine; it needs no account or release selection. It requests public assets with referrerPolicy: "no-referrer". A custom fetcher receives the Request with its credentials and signal preserved. When combining haneokaHomeSpotSource with mountHomeSpot yourself, use the same referrer policy for public assets. locale controls request language independently of server.

  1. Download the complete original sample and extract the ZIP. Keep the home-spot folder and its subfolder names.
  2. Click Local files in the example below.
  3. Under Local scene JSON, choose home-spot/scene.json.
  4. Under Local resource folder, choose the entire extracted home-spot folder and approve your browser’s folder selection prompt.
  5. Click Load. You should see the same blue attachment as in the first example.
  6. Try selection, replay and PNG export, then close the scene with Exit and dispose.
home-spot/
scene.json
pièce.glb
人物/
acteur.atlas
acteur.json
acteur.png

The ZIP packages the five sample files for download. Extract it before selecting the folder; the embed receives the JSON and individual files. scene.json references pièce.glb and 人物/acteur.atlas; that atlas references acteur.png in its own directory. Keep these names, including the accent and Chinese folder name, so the resource map matches. The file inputs above let you choose the JSON and folder explicitly. If your browser offers no folder selection, use HTTP JSON to try the same sample and supply local files through your application’s resource map when integrating it.

The examples further down show package imports and application-owned File variables as reference for bundler integrations. Browser applications can use the hosted Home Spot ESM and core ESM. npm packages are prepared separately and their publication is pending. A source build needs matching embed/provider revisions, Three 0.184.x and Spine 4.2.119. The Spine runtime carries its own runtime license; scene data and artwork retain their authors’ licenses.

Provide the descriptor consumed by the public Home Spot scene provider. One scene needs its GLB background, background transform, atlas and Spine layer. Each atlas page and skeleton is supplied through the resource resolver. A self-contained GLB embeds its background images and buffers.

import { mountHomeSpot } from "@haneoka/embed-home-spot";
import { fileResourceResolver } from "@haneoka/embed-core";
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
const scene = {
supported: true,
backgroundScene: "room.glb",
backgroundTransform: identity,
atlas: "actors/actor.atlas",
scale: 0.01,
camera: {
position: [0, 0, -5],
startPosition: [0, 0, -5],
target: [0, 0, 0],
fieldOfView: 20,
introDuration: 0,
},
layers: [
{
key: "actor",
runtime: { json: "actors/actor.json" },
animation: "idle",
sortingOrder: 0,
transform: identity,
},
],
};
const resources = new Map([
["room.glb", roomGlbFile],
["actors/actor.atlas", atlasFile],
["actors/actor.json", skeletonFile],
// The name written in actor.atlas is resolved in its own directory.
["actors/actor.png", textureFile],
]);
const spot = mountHomeSpot(document.querySelector("#spot"), {
document: scene,
resolveResource: fileResourceResolver(resources),
locale: "fr",
ariaLabel: "Une scène originale",
});
await spot.ready;

The file variables above are File or Blob objects obtained by your application. Their literal resource keys can contain Unicode. Author the scene in one language; no Haneoka page shell, catalog, release manifest or translation set is needed. JSON skeletons use runtime.json; binary skeletons use runtime.skel. Use data compatible with the installed Spine 4.2 runtime and an animation that exists in that skeleton.

Camera vectors and layer transforms use the provider’s scene conventions: vectors and row-major layer matrices follow Unity coordinates, while backgroundTransform is the column-major matrix for the converted GLB. Identity matrices work for a scene authored around the origin. A characterId and local hitPolygon can be added to a layer for pointer selection. Distinct layers retain their authored sorting order. The provider applies camera entrance, mouse follow and selection alpha when those fields are present.

The HTTP source uses the scene document’s directory as the asset base. For example, a page at https://gallery.example/ can read a scene from https://media.example/projects/cafe/scene.json:

import { mountHomeSpot } from "@haneoka/embed-home-spot";
import { httpDataSource } from "@haneoka/embed-core";
const spot = mountHomeSpot(document.querySelector("#spot"), {
source: httpDataSource({
url: "https://media.example/projects/cafe/scene.json",
}),
ariaLabel: "Cafe",
});
await spot.ready;

With this document, room.glb resolves to https://media.example/projects/cafe/room.glb. An atlas at actors/actor.atlas resolves its actor.png page in projects/cafe/actors/. A root-relative /art/room.glb uses the media origin, and an absolute URL keeps its own origin. Set assetsBase to an absolute HTTP(S) directory when resources live elsewhere. A custom resolveResource can map keys to URLs or Blobs; fetcher(request) can integrate your application’s transport. All requests use CORS mode and omit credentials by default.

The data and asset origins must permit browser requests from the page origin. Public anonymous assets can send Access-Control-Allow-Origin: *. Credentialed resources need an explicit page origin and credential permission; set credentials only when that service requires it. Custom request headers may need a successful OPTIONS preflight. An HTTP failure, unavailable scene, incompatible skeleton or blocked CORS request rejects ready and emits an error. The embed’s byte budget defaults to 64 MiB per document or resource and can be set with maxBytes.

For local JSON, use fileDataSource(sceneFile) together with fileResourceResolver(resources). Relative authored keys then match the local map directly. Resource bytes use the same resolver and transport as the document loader. The original scene provider creates textures and model instances, and releases them with the scene.

mountHomeSpot returns synchronously. Attach a rejection handler to ready and keep the handle available to your component cleanup even while the network is loading:

const spot = mountHomeSpot(host, { source, signal: componentSignal });
spot.ready.catch(showLoadError);
// Component cleanup can run before ready settles.
await spot.dispose();

cancel() cancels the current loading generation; after cancellation, call load({ document }) or load({ source }) to start another scene. A completed scene stays available when cancel() is called. load disposes the previous scene and source, rejects its pending load, then mounts the new generation. ready always refers to the latest load, and the promise returned by load refers to that particular generation. Treat a source with a dispose method as owned by one load; create a fresh source when loading again. dispose() is idempotent, removes the embed’s element and listeners, cancels loading and disposes the source. A disposed handle cannot be reused. Cleanup failures reject dispose() with an aggregate error; cancelled ready promises reject promptly while cleanup finishes.

update({ selectedCharacterId }) changes the selected character without rebuilding resources. update({ replay: true }) restarts the authored entrance and animation; replay() does the same after ready. resize() asks the provider to update its viewport; the scene also observes its container. Use load when replacing scene geometry, assets or camera data.

Events include loading, ready, cancelled, error, selection, contextlost, contextrestored and disposed. Network events appear as { type: 'resource', event }, preserving the embed-core operation ID, key and byte progress. Progress is per resource and includes a total only when the response supplies a reliable decoded length. subscribe(listener) returns an unsubscribe function. Event observers cannot change the load outcome; onListenerError can receive their errors.

exportPng({ width, height }) returns a PNG Blob for the current scene frame. Capture is rendered by the scene provider at the requested dimensions, with its GPU limits, then the interactive viewport is restored. Keep the requested proportions equal to the displayed scene when preserving composition. A failed capture rejects the promise. Export creates no permanent download URL; your application should revoke URLs it creates after their previews or downloads finish.

The embed reserves a 16:9 area from the moment it mounts, so scene loading does not change the page layout. Override aspectRatio to reserve another ratio. The canvas fills that area at the renderer’s supported pixel ratio. The embed owns one child element and leaves other container children to the host. A corner link displays the original Haneoka icon and opens https://haneoka.org/; it stays outside the game canvas and PNG export.

Use your existing Material controls for replay, selection and export. The embed exposes a scene canvas and lifecycle events; labels, progress indicators and toolbars belong to your application. An MD3 host can provide --md-sys-color-surface; the scene clear color follows that token unless clearColor is supplied. Localize button text and the ariaLabel in the author’s language. Selection can also be driven through keyboard-accessible host buttons calling update, alongside the canvas’s pointer hit testing.

The scene uses original PNG resources. Native compressed Spine textures and PMA need explicit support in the scene provider and its asset data. The embed forwards the existing descriptor and creates no model-specific rendering rules.

Load the small original scene below. The initial document is editable JSON, with one rotating Spine attachment, a self-contained GLB and a tiny atlas image. HTTP reads the actual scene JSON; file mode reads a chosen JSON/asset folder, or the original sample as local Blobs. Haneoka ID calls the actual public convenience mount. Choose a character, replay, replace a generation, resize and export512×288 PNG.

Edit parameters and load to create the scene. The authored sample is one tiny scene; selection, replay and PNG export use its real scene controller.

Language

Preparing example…

View example source

The module/renderer loads only after a click. Existing instances are disposed before another scene mounts. input/url are the application’s source-selection fields; options contains actual mount arguments. The download link owns an export Blob URL and revokes it on replacement/exit. Callback/module objects are executable integrations described below, rather than JSON values. UI language choices are ordered ja/en/zh-TW/zh-CN/ko; author locale remains an editable string.

import { mountHaneokaHomeSpot } from "https://haneoka.org/embed/home-spot.js";
const scene = mountHaneokaHomeSpot(element, { spotId: "40002", server: "intl", locale: "en" });
await scene.ready;
const character = scene.document.layers.find(layer => layer.characterId > 0);
if (character) scene.update({ selectedCharacterId: character.characterId });
scene.replay();
const png = await scene.exportPng({ width: 512, height: 288 });
await scene.dispose();

Stable aliases adopt the actual deployed SDK. Pin a verified release through manifest.modules; immutable r22 remains available with its own source/layout behavior. Current shared branding uses a transparent background and55% icon opacity in the scene corner, over the full viewport. Capture exports only the scene canvas. SDK artifacts are produced during deployment from source; npm is a separate publication step.

Required positional host is an HTMLElement with ownerDocument (TypeError otherwise). Choose exactly one document/source for mountHomeSpot and each load call; both/neither throw TypeError. The host owns its scene child and stable aspect-ratio area, and leaves unrelated host children intact.

FieldType/defaultMeaning, constraints and failures
documentHomeSpotDocument; required alternativeAuthored descriptor below; unsupported/incomplete shape rejects ready.
sourceDataSource<HomeSpotDocument>; required alternativememory/http/file/custom source, same core contracts; source failure rejects ready.
serveroptional string;undefined, public mount intlResource registry independent from locale.
localeoptional string;enAuthor/request language, not limited to UI’s five suggestions.
assetsBaseoptional absolute HTTP(S) directory;source baseDirectory override without query/fragment; invalid TypeError.
resolveResourceoptional (key,DataContext)→URL/string/Blob/Promise;keyLiteral Unicode resource mapper. Atlas image names resolve relative to that atlas. Missing local key Error.
fetcheroptional (Request)→Promise<Response>;fetchEncoded-byte transport. Public convenience mount applies no-referrer preserving signal/headers/credentials.
credentialsomit/same-origin/include;omitCore requests; origin/credential server permission needed.
headersHeadersInit;noneCore shared headers. Source-request headers take precedence by name.
maxBytespositive safe integer;67108864Each JSON/byte response budget, not aggregate decoded/GPU memory. Illegal RangeError.
onListenerErroroptional (unknown)→voidObserver or detached-cleanup error sink.
signaloptional AbortSignalAbort disposes the mounted handle even when ready. No built-in scene-wide timeout; supply a host deadline.
ariaLabelstring/Home SpotAccessible canvas name in author language.
clearColoroptional CSS colorComputed host MD3 surface, then Canvas system fallback. Provider color parse errors can reject ready; missing usable computed color Error.
aspectRatiofinite positive number;16/9Loading/ready host ratio. Nonpositive/nonfinite RangeError. Changing the descriptor’s camera.aspect does not override host resizing.
selectedCharacterIdoptional number;no selection (provider0)Selected layer emphasis;0 clears. Other nonmatching characters get .3 alpha, matched/unspecified1.
modulesoptional HaneokaHomeSpotRuntimeModulesDefault lazy Three/GLTFLoader/Spine imports. Must use compatible module constructors; missing capability TypeError. Full shape below.
onEventoptional (HomeSpotEvent)→voidRegistered before first generation.
spotIdrequired string/number for mountHaneokaHomeSpotStringified nonempty ID; missing scene Error, empty ID TypeError. Not part of authored mount options.
apiBaseoptional string for public mount/sourceDefaults https://haneoka.org/api/v1/; core absolute directory validation.

mountHaneokaHomeSpot(host,options) supplies its source automatically, so do not add document/source there. It reads the current stories document and selects homeSpots[id].spine; a source ID is not a release ID. No account/release selection. haneokaHomeSpotSource({spotId,apiBase?}) supplies the same DataSource for manual mount composition; give no-referrer to public asset transport when composing it manually. The manual source does not itself create a renderer.

FieldType/defaultRequirements and effect
supportedoptional boolean in type; must be true at runtimeFalse/omitted rejects this Three/Spine provider.
atlasstring;required at runtimeDefault atlas key for layers. Empty/missing rejects ready.
backgroundScenestring;required at runtimeGLB resource; a self-contained GLB carries its own buffers/images. Missing or invalid GLB rejects load.
backgroundTransformnumber[];required16 entriesColumn-major converted GLB transform. Wrong length rejects provider creation; use finite matrix values.
animationoptional string;emptyFallback animation for layers. An available authored animation is required for intended movement.
scaleoptional number;.01 provider fallbackDefault Spine units scale, overridden by runtime.scale. Zero falls back rather than hiding.
fadeInDurationoptional number;0Seconds for character entrance opacity, negative clamped0.
cameraoptional camera object;defaults belowUnity-coordinate camera entrance and pointer-follow settings.
layersreadonly layer[];required nonemptyOrdered skeleton/atlas entries; empty rejects provider.

The public shape permits source extension fields. The provider consumes known fields rather than treating every arbitrary field as another mount setting.

FieldDefaultMeaning
position[0,0,-1]Unity coordinate base camera vector.
startPosition[0,0,-2]Unity entrance start.
target[0,0,0]Camera target.
fieldOfView20Degrees; use finite positive values below180. Zero uses fallback.
aspect16/9Initial camera aspect; resize replaces it with actual viewport ratio.
orbitRatio0Camera forward-axis offset in scene units.
introDuration0Entrance seconds; negative clamped0.
introEase09 uses cubic-out, other values use linear progress.
mouseFollowundefinedOptional pointer-angle settings below.
mouseFollow.sensitivityundefinedPreserved source metadata; current pointer math uses maxima/threshold directly, not this field.
mouseFollow.threshold0Angular deadzone, clamped nonnegative.
mouseFollow.maxLeft0Left pointer yaw extent in degrees.
mouseFollow.maxRight0Right pointer yaw extent.
mouseFollow.maxUp0Upper pitch extent.
mouseFollow.maxDown0Lower pitch extent.
mouseFollow.smoothTime.1Seconds; minimum .001.

Camera vectors are3 numbers; layer matrices are Unity row-major, while backgroundTransform is GLB column-major. Missing/invalid vector components follow the provider’s numeric fallback. Use identity matrices for an origin-authored scene, and authored source transforms for real game data.

FieldType/defaultMeaning and selection
keyrequired string in layer typeAuthor layer identity; literal, can contain Unicode.
characterIdoptional number;0Selection ID.
sortingOrderoptional number;0Ascending painter order; stable input order for ties.
animationoptional string;descriptor.animationRuntime animation takes precedence.
skeletonoptional stringInput for provider normalization; use explicit runtime.json/skel for a new scene.
transformoptional number[]Unity row-major layer transform, normally16 finite entries.
hitPolygonoptional readonly [x,y,z][]Local polygon for pointer selection.
formatoptional unknown metadataNormalized by the Spine-entry adapter.
runtimeoptional Readonly<Record<string,unknown>>Exact supported resource keys below.
runtime.jsonoptional stringFirst skeleton source candidate; JSON skeleton data.
runtime.skeloptional stringBinary skeleton source; also selects binary parsing.
runtime.modeloptional stringFallback skeleton source.
runtime.atlasoptional string;descriptor atlasLayer atlas override.
runtime.formatoptional stringspine-binary selects binary; .skel URL extension also selects binary.
runtime.scaleoptional number;descriptor.scale then .01Spine parser scale.
runtime.animationoptional string;layer then descriptorAnimation name.

A missing normalized skeleton throws TypeError. Skeleton version must match installed Spine4.2, atlas must include the referenced regions/pages, and named animation must exist for animation playback. HTTP, parse, texture and module errors reject ready. Native compressed/PMA handling follows provider data; no new renderer/format is selected by this embed.

modules has three:object, GLTFLoader:constructor, spine:object. spine must provide TextureAtlas, ThreeJsTexture, AtlasAttachmentLoader, SkeletonBinary, SkeletonJson and SkeletonMesh constructors. The Three namespace must provide WebGLRenderer, Color, Scene, PerspectiveCamera, Vector2, Vector3, Euler, Matrix4 and MeshBasicMaterial used by the provider. Public package types intentionally keep these opaque and runtime-check consumed constructors. Use one compatible Three instance and Spine4.2.119; importing the embed alone does not initialize WebGL. Licensed runtime distribution stays with the application.

MemberArguments/resultBehavior/errors
readylatest Promise<void>Changes with load. Each returned load Promise is tied to that generation.
stateloading/ready/cancelled/error/disposedLive phase.
elementHTMLElementOwned scene/branding wrapper.
canvasHTMLElement/undefinedAvailable once provider is ready.
documentHomeSpotDocument/undefinedLatest successfully loaded descriptor, not a copied catalog.
update{selectedCharacterId?:number,replay?:boolean}→voidApplies selection/replay without rebuilding; supplied undefined selection clears it. Disposed Error.
load{document} or {source}→Promise<void>Strictly one input, TypeError otherwise. Dispose previous scene/source and cancel its generation. New source owns its cleanup.
replay()=>voidRequires ready; Error if not ready/disposed.
resize()=>voidRequires ready; also automatic ResizeObserver.
exportPng{width?:number,height?:number}→Promise<Blob>Requires ready/intact context. Defaults drawing-buffer size; one omitted dimension preserves current ratio.
subscribelistener→unsubscribeFuture events, disposed Error.
cancel()=>voidCurrent loading generation only; ready scene remains. Reload with fresh source after cancellation.
disposeshared Promise<void>Idempotent, immediate detach/abort, await source cleanups; failures AggregateError.

PNG width/height must be positive safe integers, within min(maxTextureSize,MAX_RENDERBUFFER_SIZE), and total pixels≤16777216 (RangeError). It renders the current frame without advancing time, temporarily resizes the render target, then restores interactive size/pixel ratio/aspect. Unavailable2D copy context/encoding or lost WebGL context rejects Error. The Blob creates no permanent URL: caller owns preview/download URLs. PNG contains scene pixels, not the host overlay.

Events are resource{event:EmbedEvent}, loading/ready/cancelled{generation:number}, error{generation,error}, selection{characterId:number}, contextlost,contextrestored,disposed. Resource events belong to the loading generation; core operation/key/progress fields retain their semantics. Listener errors are isolated. Cancellation uses AbortError; external signal disposes; decode/HTTP/provider errors keep their actual causes. Never reuse a disposed handle/source with released resources.

The application-only sdkBase field selects an absolute HTTP(S) directory for your compatible self-hosted core.js/home-spot.js distribution. It defaults to the official /embed/ directory; it is not a mountHomeSpot option. Use Load to apply constructor/resource-mapping changes; Replace passes only document/source to the existing handle.