Skip to content

0.10.0 - #19

Merged
YozoraKurage merged 92 commits into
mainfrom
0.10.0
Aug 9, 2026
Merged

YozoraKurage merged 92 commits into
mainfrom
0.10.0

Conversation

@YozoraKurage

Copy link
Copy Markdown
Owner

No description provided.

YozoraKurage and others added 30 commits August 7, 2026 20:21
…l for async sync

Async sync setups can now serve on-demand sync REQUESTS: targets marked
requestable get a local, unsynced "base/Req/target" Bool, and the send cycle
jumps to a requested slot at the next step boundary instead of waiting out the
pass. Redirects carry the ring's exit time, so requests queue at boundaries
(one per step, cycle order) and never shorten a slot's dwell; the serving
driver clears the flag. Costs no synced bits.

Raising a request is packaged as a per-state Sync Request component: records
live in GraphFrameData, materialized as a DaerD-managed localOnly Parameter
Driver, editable from the state inspector, with the sync layer regenerated in
place when it has to start listening. Recipes get .Requestable(...).

Since a generated sync layer's graph is machinery nobody reads, the centre
pane now shows a dedicated settings panel for it (shared form with the
wizard), with the raw graph one click away.

Also:
- default base name is now derived from the controller's asset GUID
  ("DD" + 6 hex digits) so two distributions multiplexing on one avatar don't
  both own "Async/*"; unnamed recipe setups keep a saved "Async" for compat
- parameter store: bulk SetSynced, an "Add All" action adding missing
  controller parameters as unsynced rows (MA Parameters prefab workflow), and
  a one-click fix unsyncing multiplexed targets behind the existing warning
- forward-port of the 0.9.1 fix generating the Empty clip as a controller
  sub-asset

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… time, linear smooth, digit separation, trigonometry

Eight new gadget kinds alongside the existing arithmetic/logic set:

- Reciprocal (1/x): a normalized Direct tree covers x > 1 (an empty motion
  weighing x-1 next to an "output = 1" clip weighing 1), and a supporting
  layer covers 0 < x < 1 with a 1/x curve clip read by motion time.
- Divide: B's reciprocal into output/Inv, multiplied with A.
- Frame Time: a looping clock layer (one unit per second) subtracted from
  its own previous reading — output is the seconds since the last frame.
- Smooth (Linear): constant-speed follow via a delta parameter and a
  step-size weight; pairs with Frame Time for frame-rate independence.
- Separate Digits: splits a 0..1 float into tenths/hundredths/thousandths
  through the subnormal-float division trick (the constants are the
  algorithm and are ported verbatim).
- Sine / Cosine / Tangent: one-second curve clips played by motion time,
  0..1 = one turn; tan is pinned to +100 at its poles.

Supporting layers are appended at the end of the controller on purpose:
they write the same parameters as the blend tree, and only a later layer
overrides an earlier one. Their states keep Write Defaults OFF so an idle
state doesn't stomp the tree's result.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the Project window immediately

Two gadgets that stay entirely inside the blend tree layer:

- LUT (Curve): bakes an arbitrary AnimationCurve into a 1D tree — thresholds
  are evenly spaced samples of the curve's time axis, children AAP clips of
  its value, deduped per distinct value. Preset shapes (Linear / Sqrt /
  Square / Smoothstep / Ease Out) fill the curve field, which stays editable.
- Atan2: a Freeform Directional ring holding its own angle in turns
  (0..1 counter-clockwise from +X, the unit Sine/Cosine consume). The 0/1
  seam is pinned inside a narrow wedge at +X; a value-0 origin child covers
  vanishing vectors. Ring resolution is configurable (8..64 directions).

Also fixes sub-asset visibility: AddObjectToAsset alone leaves new clips and
trees out of the Project window until the next save, so gadget applies, the
graph's blend tree state creation and nested-tree adds now save and reimport
the controller once per batch (DbtBuilder.CommitSubAssets).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e depths

Every blend tree stage costs one frame, so two branches tapping the same
parameter at different depths read different frames of it. The buffer turns
that into a tool: a chain of identity remaps, one per frame (1..8), inserted
on the shallower branch so both signals arrive at the same age. A single
frame is a bare identity remap; longer chains route through intermediate
stage parameters, which go through the same "must be new" gate as the output
so a name collision can't silently corrupt what owned it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKDgZCam5r4RjxbgByEzZM
…ones

c.Gadgets("Layer") declares the per-frame float math the parameter panel's Add
menu generates — all 22 kinds, each argument taken as a typed parameter handle
or a plain name. The named layer, the supporting layers some gadgets bring, and
every parameter under a declared output name are swept before each Generate: a
gadget refuses to write an output that already exists, so without the sweep the
second Generate would report every one of them as a collision and stack the
layers besides. One sub-asset flush per layer now, instead of one per gadget.

Exported recipes come in two halves of one partial class: "<Name>.Generated.cs"
is rewritten on every export, "<Name>.cs" is the author's and is written only
once. The round trip — export, reshape the code, Generate, export again — threw
the reshaping away every time; now the re-export lands beside it, and its own
git diff is a clean report of what changed in the controller since last time.
Compare on the recipe asset holds the two halves against each other at the IR
level, so reshaping by hand or with an AI is checkable instead of hopeful: it
passes on a loop that replaces three spelled-out states and fails on a state
that quietly changed name. A single-file recipe from before the split migrates
on export, keeping its .meta so existing recipe assets stay bound to it.

Layers a post step generates (async sync cycles, gadget layers) now carry the
recipe's ownership mark, which is what the panels' "add it in the recipe
instead" hints were already written for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKDgZCam5r4RjxbgByEzZM
The skill still told the reader to edit the exported file, which is now the
half every export overwrites. It gets the file map, the delegating one-liner
it is meant to replace, and the fields it must not redeclare (they live in the
generated half, reachable because the class is partial).

Compare leads the verification list now — it holds both halves against each
other without touching a controller, so it is the cheap check that a reshaping
kept its meaning. Two things it cannot see are called out: post steps (Raw,
async sync, gadgets) are outside the IR, and gadget calls in particular must
keep their order, since a later gadget may read an earlier one's output.

Also adds the other half of the workflow the split exists for: after a
re-export only the generated file moves, so its git diff is the change list to
carry into the reshaped half.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKDgZCam5r4RjxbgByEzZM
…errides

A StateMachineBehaviour can sit on a state machine as well as on a state, and
the IR modelled only the latter. Exporting a controller dropped those silently,
Generate deleted them from the controller when it rebuilt the layer, and Verify
reported nothing — the field was missing on both sides of the comparison, so
there was no difference to find. Every other part of DaerD already handles them
(layer copy, cleanup, the analyzer, parameter usage), which is what made this
one easy to miss.

The machine now carries its behaviours through parse, rebuild, diff and export,
and MachineScope grows the same BehaviourJson / Behaviour<T> pair a state has —
recorded unchained, since it returns the base scope and folding it into the
declaration line would retype the variable and break the layout block.

Synced layers get the other half of their overrides too: the behaviours a layer
runs on a source state instead of the source's own. They are instances of their
own rather than borrowed, so applying one creates it and stores it in the
controller, the way AddStateMachineBehaviour would have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKDgZCam5r4RjxbgByEzZM
ParameterStore.Analyze and ClipRepather.Analyze build analyzer issues while
ControllerAnalyzer.Analyze calls them, so the nested Issue/Severity/Kind types
tied those model classes back to the analyzer. Move them to top-level types in
Editor/Model/AnalyzerIssue.cs: AnalyzerIssue, IssueSeverity and IssueKind
(the bare names Severity and Kind are too generic at namespace scope, and
ParameterStore already has a Kind member). Bodies, fields and docs are
unchanged; only the type names at the call sites move.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The iterative Tarjan implementation is a pure graph algorithm with no analyzer
knowledge, so it moves verbatim to Editor/Utils/StronglyConnectedComponents.cs
as StronglyConnectedComponents.Compute. FindTerminalStateGroups stays in the
analyzer — deciding which components are "trapped" is analyzer-domain logic —
and now calls the utility.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Direct tests of StronglyConnectedComponents.Compute pinning the behaviour the
analyzer relies on: singleton nodes are reported as components of their own,
component ids come out in reverse topological order (sinks first), cycles and
self-loops collapse, and an empty graph yields no components.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
…ions

GraphSync's private IsStateInStateMachine / IsStateMachineInStateMachine are
plain direct-child lookups with no graph knowledge. They move to
AnimatorControllerExtensions as ContainsState / ContainsStateMachine, matching
the ContainsTree(this container, target) shape the file already uses; the
bodies are unchanged, only the container becomes the receiver.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
ControllerCleanup, ParameterUsageFinder and StateSearch each carried a
byte-identical private BuildPathLabel (only the parameter name and the leading
comment differed). One copy moves to AnimatorControllerExtensions as
PathLabel(this path, layerName); the three privates are deleted and rewired.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
AsyncSyncBuilder and NetworkSyncBuilder held byte-identical private
EnsureParameter helpers that already delegated to DbtBuilder.FindParameter.
The body moves to DbtBuilder.EnsureParameter unchanged and both call it.

DbtBuilder.EnsureFloatParameter is NOT the same helper — it is Float-only and
assigns an explicit default — so it stays as it is. ToggleBuilder's copy also
stays: it reports back through an out parameter whether it created the
parameter, which its callers use to decide about the default value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
MakeUniqueLayerName was byte-identical to DbtBuilder.UniqueLayerName down to
the local Taken function; drop the panel copy and call the shared one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
ControllerBuilder.cs held all 15 top-level authoring types in 1193 lines.
Each type moves verbatim into its own file under Editor/Authoring/, keeping
the same namespace so no call site changes; the `// ----` section separators
travel with the group they head. No type was nested, so nothing was unnested
and no access path changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
RecipeExporter's nested Driver class (752 lines of the file's 1058) becomes
top-level RecipeDriver in its own file. The class body moves verbatim apart
from de-indenting one level; its internal `// ----` sections and the
`// ---- driving the builder ----` separator travel with it.

The only RecipeExporter member the driver reached for was the Header text
helper, called three times. Access widening required:

  - RecipeExporter.Header(string): private static -> internal static

Those three call sites now read RecipeExporter.Header(...). Nothing else in
the exporter was touched: Export, RegisterAssets, the Result/FieldRef DTOs
and all text composition stay put, and the emitted C# is byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The fold machinery (FoldPlan, PlanFolds, BehaviourSignature, EmitFolds, Fold,
LoopVar, HasWiring, FoldThreshold) answers a question about the shape of the
generated text, not about the controller, so it moves verbatim into a
RecipeFoldPlanner collaborator the driver constructs and holds. Its only
couplings out were the builder — passed to the planner's constructor, so
`_c.Script` reads unchanged — and one call back into EmitBehaviour.

Access changes, all private -> internal, none of them widening past the
assembly:

  - RecipeFoldPlanner.FoldPlan (nested type), .PlanFolds, .EmitFolds,
    .HasWiring: the planner's surface toward the driver
  - RecipeDriver.EmitBehaviour: the one call the planner makes back

BehaviourSignature, Fold, LoopVar and FoldThreshold stay private. Method
bodies changed only in their access paths: EmitBehaviour gains `_driver.`,
and the driver reads RecipeFoldPlanner.PlanFolds / .HasWiring /
.FoldPlan and `_folds.EmitFolds`. The emitted C# is byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
BuildSlots, BuildSchedule, EffectiveWeights, Gcd, RepairAdjacency,
ResolveScheduleOverride and EffectiveSchedule move out of the god class verbatim.
AsyncSyncBuilder keeps same-name delegators, so the tests, the wizard and the
recipe builder see exactly the same surface.

Slot and Request stay on the facade — they are part of that surface — and are
aliased into the new file so every moved signature reads as it did before.
Gcd and RepairAdjacency widen from private to internal: Warnings (still on the
facade) needs Gcd, and the pure math is now reachable for tests of its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The graph selection was sorted into per-kind buckets in three places with
the same type switch. Build it once, in one read-only class, so the command
classes can later be handed plain model lists instead of graph elements.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
ResolveEncoding, ResolveEmptyClip, FloatChannelsUsed, CycleSeconds,
RefreshIntervals, FreeSlots, CompressedBits, DirectBits and ChannelTypes move out
verbatim; AsyncSyncBuilder keeps same-name delegators so the wizard's preview and
the tests call exactly what they called before.

ChannelTypes widens from private to internal — Warnings and GeneratedParameters
stay on the facade and still need it. The IndexEncoding.Auto doc now points at
AsyncSyncCost.ResolveEncoding so the cref keeps resolving.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
IndexParameter, BitParameter, ChannelParameter, FloatChannelParameter,
RequestParameter and both DefaultBaseName overloads move out verbatim;
AsyncSyncBuilder keeps same-name delegators, including the internal
DefaultBaseName(guid, taken) seam the tests call.

RequestParameter's doc now says AsyncSyncBuilder.IsReservedName, which is where
that check stayed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
State / sub-state-machine creation, motion assignment and pack / unpack only
touch the controller asset, so they move to a view-free class that can be
driven headlessly. GraphSync keeps the public entry points and the rebuild
and selection sequencing that follows each command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The five Multi* helpers in InspectorPanel were one skeleton written out five
times: read the first item, mark the control mixed when the others disagree,
then write the new value to every item inside a single undo group. MultiEditGui
holds that skeleton once; Bool / Float / Text / ObjectField / Interruption are
typed wrappers over it with the rendering and undo names they always had.

Interruption keeps its asymmetric naming (group "Edit Transitions", per-item
"Edit Transition"), and MultiBool's refreshEdges flag becomes an afterApply
callback so the transition editor still repaints its edges after a bulk edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The 223-line build and its Apply-only helpers (SlotStateName, AddChannelCopies,
ClearStateMachine) move out of the facade verbatim — same statements, same Undo
calls in the same order, one UndoScope. AsyncSyncBuilder.Apply stays public and
delegates, so nothing that runs a request changes.

The new file imports the facade statically: the moved code goes on saying
Request, Slot, BuildSlots, IndexParameter and so on, exactly as it did inside it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The inspector kept the separator line, the row selection tint and the parameter
lookups behind the condition popups as private helpers, while the layers panel
spelled the same tint out inline. PanelGui holds them once; the bodies move
verbatim, tables and all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Frames and memo notes live in the controller's GraphFrameData holder, so the
whole edit half is asset work. It moves to a view-free class that also takes
over the holder reference: it used to be a GraphSync field assigned from six
places, and a single owner keeps every reader on the same instance. GraphSync
keeps the wrappers that measure node layout, refresh visuals and rebuild.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Three panels reacted to a wizard that had rewritten the controller with the same
four calls: notify parameters, layers and the graph, then open the layer it
landed in. The extension holds that body once. The parameters panel had no layer
to open, and keeps that difference by leaving the index off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Every frame / note field edit was the same four lines around a one-line
change. One private Mutate helper now holds them; the entry points keep their
names and pass their own undo label, so the undo history is untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Apply keeps its statements in the same order; the contiguous slices between them
become private methods with the locals they used passed in — parameter creation,
layer resolution, empty clip, index bit names, the send ring, the decoder, the
parameter store and the saved config. No statement crosses an Undo call, the
UndoScope still wraps the whole run, and every comment stays with its code.

What is left in Apply is the outline: fifteen lines that name the steps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The leftover / exposed sub-asset scan owns the two caches it fills, so both move
with it. The panel still drops them when the controller changes, now through
Clear().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
YozoraKurage and others added 25 commits August 8, 2026 18:14
Every RefreshStateNode / RefreshAllStateNodes / RefreshFrameVisuals /
RefreshNoteVisuals / FindNoteNode().BeginEdit() call an inspector section made
on GraphSync is now a NotifyGraphVisualsChanged / NotifyNoteEditRequested on the
context; AnimatorGraphView makes the same GraphSync call from its subscriber.
The events are synchronous with one subscriber, so each site keeps its exact
former effect, at the same point in the same frame:

- state badge repaints (behaviour add / remove / paste, sync requests, Write
  Defaults, multi-state motion + WD)  -> NotifyGraphVisualsChanged(state)
- frame box, note box                                 -> ...(frame) / ...(note)
- bulk Write Defaults (RefreshAllStateNodes)          -> GraphVisuals.AllStateNodes
- mute / solo / condition edits (the Edges loop)      -> GraphVisuals.AllEdges
- "Edit Text in Graph"                                -> NotifyNoteEditRequested

Two bulk sentinels rather than one: the overview repainted only state nodes and
the multi-transition editor only edges, and neither site is widened.

Sections whose only reason to hold a GraphSync was one of these refreshes take
the context instead (behaviours, multi-state behaviours, VRC drawers, sync
requests, overview); the ones that also issue commands keep both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
TransitionInspector took an AnimatorGraphView for two things: the selected edges
and the Sync behind it. It now takes the GraphSync directly and asks the context
for the selection.

GetSelectedTransitionGroups returns one (isDefault, transitions) entry per
selected edge, so the pool is assembled from model data and no TransitionEdge
crosses into the panel. The "no edge selected in the graph, fall back to what
Context.Selection names" step moved verbatim into the provider on the view side,
where the edge lookup belongs — same edges, same order, same pool.

DeleteTransitionRow and AddTransitionToAnchorEdge stay on GraphSync: both need
the edge object itself (its endpoint nodes), and GraphSync is the sanctioned
command facade. They now call it through the injected _sync rather than reaching
through the view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The last direct panel-to-view reference. The panel now reads the multi-state
selection through DaerDContext.GetSelectedStates() — the provider the graph view
registered — and passes the injected GraphSync down to the sections that issue
commands. DaerDWindow hands it _graphView.Sync.

grep -rn 'AnimatorGraphView' Editor/Panels/ is now empty: panels and the graph
talk only through the context and the command facade, as DaerDContext.cs:8-38
says they should.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Includes followup/inspector-i18n: the remaining inspector literals localized,
and the inspector decoupled from the graph view via context events/providers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Sine / Cosine / Tangent were a layer and nothing else: a curve read by a
state's motion time, which no blend tree can drive. A 1D tree reaches the
same place without leaving the tree — blending between adjacent thresholds
IS interpolating a table, so children holding f(i/N) at threshold i/N are
the function, sampled. Same trick Lut1D already uses, with the curve fixed.

TrigValue is reused verbatim, poles and all, so tan still pins its two
samples to +100 rather than to whichever side of pi/2 the float rounded to.
Clips are shared by value the way Lut1D shares them: 65 children come out as
56 clips for sin, 62 for cos and tan.

Accuracy is the sample count's business now rather than the curve's: 64
samples per period leave sin and cos within 1.2e-3 of the true value, well
inside the 1/127 a synced float can carry.

UsesDbtLayer is constant true from here — every kind is a blend tree child —
but stays a predicate: it is the question the wizard and the recipe builder
actually ask, and it is public API. SupportingLayerNames keeps returning the
old "Out sin(x)" names so a regenerate reclaims the layer a controller built
by an older version is still carrying, instead of stranding it beside the
new tree with both writing the output. Removing a layer that isn't there is
a no-op, so listing costs nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The exact half of 1/x — a normalized Direct core reading (input - 1) as a
weight — only ever covered inputs from 1 up, because the shift would have to
weigh negative below that and Direct weights clamp at 0. A motion-time layer
at the end of the controller covered the rest. It doesn't have to: what the
core is short of below 1 is exactly (1/u - 1), and that is a function of the
input, so a 1D tree can hold it and a third Direct child can add it back.
Direct children animating the same AAP stack sum, so core + table = 1/u.

The table's top threshold is input 1 holding 0, which is also what a 1D tree
clamps to above it — so from 1 up the table contributes nothing at all and
the exact half stands alone, unchanged.

Its thresholds are a geometric ladder from 1 down to 1/240 in 96 rungs, not
an even one. Linear interpolation of 1/u across a step of ratio r overshoots
by (sqrt(r) + 1/sqrt(r) - 2) relative regardless of where the step sits, so
one ratio buys the same accuracy everywhere: 8.2e-4 measured across the
whole band, against a uniform ladder that would be worthless approaching 0.
Below the floor the tree clamps, which keeps the old 240 output cap. Above
1 the result stays bit-exact.

Trade-off: 97 one-key clips as sub-assets where the layer was one curve clip
plus an empty one — for a layer, two states, two transitions and a whole
motion-time state machine removed. Divide is unchanged structurally; it just
no longer inherits a layer, only the extra frame of lag.

One transient stays, as it was: the core reads last frame's shift while the
table reads this frame's input, so a frame that crosses 1 has the two halves
describing different inputs. The layer handoff had the same artifact.

SupportingLayerNames still reports "Out 1/x" and "Out/Inv 1/x" so that a
regenerate reclaims the layer a controller built by an older version is
still carrying, rather than leaving it to overwrite the new tree's output
from the end of the layer list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
The trig kinds and the reciprocal describe their shape out loud, and the
shape changed: both are lookup tables inside the blend tree layer now, and
the reciprocal is exact only from 1 up. The "this operation is a layer of
its own" help box loses its condition too — no kind answers false to
UsesDbtLayer any more, so only the FrameTime wording is ever shown, and it
is unchanged.

The old msgids stay in ja.po: nothing else uses them today, but a stale
translation costs nothing and dropping one is unrecoverable from the .po
alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
…ocal

Comment-only: Reciprocal/Sine no longer mention a supporting layer or motion
time, and Divide inherits an extra frame of lag rather than a layer. The
generic mentions of supporting layers stay — FrameTime still brings one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Trig and reciprocal gadgets now live entirely inside the Direct BlendTree as
1D lookup trees; FrameTime is the only gadget that still adds a layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Fi8sHWGkXzjBSZoKAA1kM
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VP2yGLXcBYDqW7nDAfaUpj
@YozoraKurage YozoraKurage added the minor up to 0.x.0 label Aug 9, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 9, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
daerd 8c9f20f Aug 09 2026, 06:11 AM

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VP2yGLXcBYDqW7nDAfaUpj

# Conflicts:
#	Editor/Model/AsyncSyncBuilder.cs
#	Editor/Window/AsyncSyncWindow.cs
@YozoraKurage
YozoraKurage merged commit 23cb810 into main Aug 9, 2026
1 check failed
@YozoraKurage
YozoraKurage deleted the 0.10.0 branch August 9, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor up to 0.x.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant