Skip to content

v0.6.0

Choose a tag to compare

@libraz libraz released this 06 Oct 03:19
· 37 commits to main since this release

A minor release. The wire vocabulary only gains: fates and setupFates are new fields in two responses, and nothing a caller sends has changed shape. Three behaviours did change, and an orchestrator written against 0.5.0 should check them:

  • ok on POST /api/command is false only when a command was lost. A command the server keeps for the next renderer is retained, and with no viewer attached that is still ok: true.
  • The control server refuses a request whose Host is not 127.0.0.1 or localhost, whose Origin is not the address it was sent to, or whose Sec-Fetch-Site is cross-site or same-site. A caller with no browser behind it sends no Origin and is unaffected.
  • wear with a slot and no item leaves the slot alone. Only item: null takes a garment off.

The server's queue is the queue

Every turn goes through the control server's queue, a say posted to /api/command included. POST /api/command answers with one fate per command: delivered to at least one viewer, retained by the server for whichever renderer attaches next, or lost. The setup, the queue verbs, BGM transport and the effect of a cue are all retained, so a renderer that opens mid-show is handed the shot, page, mood and music the others already have. POST /api/scripts/run reports its setup the same way as setupFates, and yarn ctl play, the panel and the native shell warn only about a setup command that was actually lost.

A caller's id on a say is honoured while no turn holds it and replaced with a fresh one otherwise; ids in the response says which. A rewind with mode: from that cuts the line on air puts that line back as well, after the lines it followed. JSON bodies are capped at 4 MiB and refused on their declared length before they are read.

Loopback, checked at the door

Binding to 127.0.0.1 kept other machines out but not a page open in the operator's own browser, which can reach loopback by naming it from another origin or by rebinding its own hostname onto it. Both arrive with a giveaway in Host, Origin or Sec-Fetch-Site, and src/server/guard.ts refuses them with 403 before any route, file or stream is touched. No CORS header is sent on the refusal or anywhere else.

The voice

HASHIDATE_TTS_MODEL picks the sidecar's checkpoint: small (Irodori-TTS v4.1-Small, the default), large (v4-Large) or mf (a MeanFlow distillation of small that samples at 4 steps instead of 16). Large is closer to the reference speaker and takes about twice as long per line, roughly the length of the line itself on an M5 Max, which a queued script hides and a caller sending one turn at a time does not. All three share the codec, so the encoded reference set serves any of them. Any other value is refused at startup.

The model now loads after the socket is up, so /health answers ready: false for the whole load instead of leaving requests hanging, and turns true only once the watermark self-test has passed on a real take. A sidecar whose model cannot load or whose self-test fails prints the error and exits. On the renderer's side, a /health reply without a boolean ready counts as no answer.

A line now waits five seconds plus its own spoken length for its voice, so a long line is no longer discarded against a short line's deadline. A line the voice answered without a take is asked for once more when the voice next succeeds, and a line that leaves the queue unsaid (interrupt, clear, replacement, teardown) has its synthesis aborted rather than finished for nobody.

Motion

  • Every outgoing gesture fades at its own weight, and each gesture slot has its own elbow solution. Switching gestures quickly no longer drops one pose or re-raises another, and an arm no longer snaps between two elbow positions mid-handoff.
  • A sustained pose held past its hold eases out from the moment it is released. It used to drop to rest in one frame.
  • Breath depth follows its target across the end of a line instead of changing 30% in a single frame.
  • The spring solver samples the body at each fixed step's own time. At 50 Hz a frame sometimes takes two steps, and both used to see the same body pose, which made hair and cloth step unevenly (1-1-1-1-2). One step per frame at 60 Hz is unchanged, and a recorded trajectory pins it.
  • The tail's slower wag term keeps its own phase and no longer restarts when the main phase wraps.
  • Lid droop eases in and out, an idle face hands back through the preset fade, and a rig with a single-eye blink shape blinks through it.

What a descriptor can declare

  • armRest sets resting arm directions for a garment that needs the arms held clear of the body, such as a full skirt. Gestures blend from it; slots left out keep the standing pose.
  • anchor on a spring group attaches the group's root to a point skinned across bones that earlier groups already simulate, so a part of a garment follows the skin it sits on. An anchor that cannot be built, or that overlaps another group's bones, is reported in Profile.missing and the group is dropped.
  • reflection on a preserved PBR material sets how strongly it reflects the runtime's studio environment, which is built once and shared between rooms and avatar materials. Without it a metal sees only the scene environment, which is empty without a backdrop.

A third avatar, Yonagi Neru (neru), is registered after Yoka and Manuka, and Yoka stays the default. Its GLB comes from a separate production workspace and is not redistributable either.

Also in this release

  • The MCP status tool returns the expression, performance, gesture and per-arm strain, so a caller can see what react changed and whether an aim was met.
  • yarn ctl refuses a malformed number rather than truncating it or reading it as zero, and --page and --slide take whole numbers. wear takes its item positionally or as --item, with none to take it off. vocab lists backdrops, voice presets and wear presets.
  • A script validation error counts array positions from 1, as the outline numbers lines.
  • GET /api/motions lists a malformed motion, or a file whose name is not a usable id, under errors instead of dropping the whole list.
  • The camera vocabulary is derived from the wire schema and can no longer drift from what the wire accepts.
  • A page command that arrives while a deck is still opening is kept and applied once the page count is known.
  • A stop names the take it ends, so a late stop for an earlier take leaves a newer one running. A page whose browser has not yet allowed audio still records.
  • The console's stop goes through the server's interrupt, so the server queue empties with it.
  • The panel polls state one read at a time, keeps the mood and look sliders under the pointer while the renderer catches up, keeps an open draft when its line goes on air or is dropped, warns about unknown gestures and expressions even when the vocabulary is empty, and names a take only after a script in show/scripts/.
  • The native shell keeps a control server it started that answers just after the startup wait gave up, and stops it on quit.
  • make glb fails on a Blender error, a missing export marker or an empty file. A relative HASHIDATE_TTS_SOCKET is made absolute once in the Makefile, so the sidecar and the control server read one path. make check-assets runs on the bash 3.2 macOS ships and also measures the staged blob.
  • Dependencies: vitest 5, three r186, libsonare 1.8.1, Node 22.23.3 and Yarn 4.18.1. The speech sidecar's Irodori-TTS pin moved one upstream commit; on MPS the voice is unchanged.