main
John Lauer Fan component engineering assets and verified simulated rotor motion prototype 422c1bc 12d ago

Component motion extension (proposal v1)

This is a tested fan prototype and proposed additive extension to adom/component-states@1, not a claim that all installed wiki or probing viewers support motion. Existing LED state files remain valid. Static STEP geometry is retained for mechanical use. Continuous animation and playback controls live in GLB and the runtime.

A manifest may add variables and a top-level motion object with schema adom/component-motion@1. Keep ordinary states[].models and image entries for old consumers. Off should link a static GLB; on can link the animated GLB. Unsupported consumers display static fallback and explicitly report motion unsupported. Never infer motor speed from supply voltage or an on command.

The motion object names the model and SHA-256, required capability, unique assembly/housing/rotor node names, and bindings. Each binding specifies a unique animation name, target node name, local axis and pivot in metres, variable and unit, cycle duration in seconds, revolutions per cycle, and mapping: rpm-to-cycle-rate. These are data fields, never executable formulas. Resolve nodes/groups within the imported component instance, not across the whole scene; repeated fan instances must not drive each other. Reject missing or ambiguous names, wrong hashes, empty animations, and channels outside the declared rotor subtree. Keep reference overlays and stationary housing outside the rotating branch.

For this fan, Fan.Rotor.Revolution contains one negative rotation about local Z in one second. Five normalized quaternion keyframes at 0, 0.25, 0.5, 0.75 and 1 seconds avoid interpolating an identical start/end quaternion as no motion. glTF LINEAR rotation interpolation uses spherical interpolation. The rotor parent is at the shaft axis; asset units are metres and the mechanical source is Z-up. Viewer unit/axis conversions belong on the assembly root, never on individual blades. Direction and blade shape remain illustrative until physical comparison.

Playback rate = RPM / 60 × cycleDurationSeconds / revolutionsPerCycle. A one-second revolution at 6000 RPM has a rate of 100. A labeled 0.02× slow-motion preview uses 2 while still reporting the supplied 6000 RPM. Never silently cap RPM to make a prettier animation. At high RPM, display sampling can cause apparent reversal; the numeric RPM and source are authoritative for the visualization. This does not validate physical cooling.

State controls supply {state, variables:{speed}, source, valid}. Source is simulated or measured; default is simulated. Real telemetry requires an explicit valid flag and a caller-enforced freshness window. Invalid/stale measurements pause motion and display UNKNOWN, not 0 RPM or verified OFF. off requires speed 0 and pauses at the current phase. on with speed 0 can represent a stalled rotor; animation is stopped without claiming the supply is off. Start/stop inertia and airflow are not modeled. Reset is explicit, not a side effect of every state update.

The reference adapter states/controller.mjs uses the existing Adom Babylon viewer's imported animation group. It has no device I/O. Applying visualization state must never arm hardware, close a relay or set a DAC. Pause rendering when hidden, stop/dispose on teardown, and respect reduced-motion preferences. Downloading the manifest or scrolling a wiki page must not create a WebGL context.

Acceptance: validate GLB node/animation bindings and hash; check stationary transforms while the rotor moves; test 0/3000/7000 RPM and invalid input; compare separate instances; verify off freezes phase; label slow motion and simulation; inspect top/oblique views; record a short action clip. Physical tach testing is a separate hardware test.

Reference: Khronos glTF animation sampler schema.