ansible-automation-eng…
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
<!-- Loaded by rive-skeletal-animator when task involves: installing Rive, mounting canvas, useRive hook, useStateMachineInput, Zustand wiring, CombatEngine events, lazy loading, Vite WASM config -->
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
Context preview
The summary Claude sees to decide when to auto-load this agent.
<!-- Loaded by rive-skeletal-animator when task involves: installing Rive, mounting canvas, useRive hook, useStateMachineInput, Zustand wiring, CombatEngine events, lazy loading, Vite WASM config -->
<!-- Loaded by rive-skeletal-animator when task involves: installing Rive, mounting canvas, useRive hook, useStateMachineInput, Zustand wiring, CombatEngine events, lazy loading, Vite WASM config -->
`@rive-app/react-canvas` — React wrapper over Rive Web runtime. Handles canvas lifecycle, WASM loading, resize. Version 4.x supports React 16.8–19. WASM bundle ~150KB gzip.
npm install @rive-app/react-canvas
WASM bundled in npm package, loaded at runtime on first mount.
import { useRive } from '@rive-app/react-canvas';
function PlayerCharacter() {
const { RiveComponent, rive } = useRive({
src: '/assets/characters/player.riv',
stateMachines: 'CombatStateMachine',
autoplay: true,
});
// rive is null until the .riv file loads — always guard before use
useEffect(() => {
if (!rive) return;
// rive instance is ready here
}, [rive]);
return (
<div style={{ width: 400, height: 400, position: 'relative' }}>
<RiveComponent />
</div>
);
}`RiveComponent` fills its container. Wrap in explicit-sized div. Do not set width/height on `RiveComponent` directly.
useRive({
src: string, // path to .riv file, or ArrayBuffer
artboard?: string, // specific artboard (default: first artboard)
animations?: string | string[], // animation clip names to play directly (no state machine)
stateMachines?: string | string[], // state machine name(s) to activate
autoplay?: boolean, // default: false — set true for immediate playback
layout?: Layout, // canvas fit/alignment — import Layout from @rive-app/react-canvas
onLoad?: () => void, // fires when .riv finishes loading
onStateChange?: (event: StateChangeEvent) => void, // fires on state machine transition
onPlay?: () => void,
onPause?: () => void,
onStop?: () => void,
})`stateMachines` name is case-sensitive, must match Rive Editor exactly.
const {
rive, // Rive instance — null until loaded
RiveComponent, // JSX element — mount this in render
setCanvasRef, // ref setter for custom canvas placement
setContainerRef, // ref setter for custom container placement
} = useRive(params);`rive` exposes Web runtime API: `rive.play()`, `rive.pause()`, `rive.reset()`, `rive.stop()`.
`useStateMachineInput` returns a reference to a named input from the active state machine.
import { useRive, useStateMachineInput } from '@rive-app/react-canvas';
const SM = 'CombatStateMachine'; // exact name from Rive Editor
function PlayerCharacter() {
const { RiveComponent, rive } = useRive({
src: playerRiv,
stateMachines: SM,
autoplay: true,
});
// Trigger — one-shot event (attack, hit, signature)
const attackTrigger = useStateMachineInput(rive, SM, 'attack');
const hitTrigger = useStateMachineInput(rive, SM, 'hit');
// Boolean — sustained state (blocking, stunned)
const blockInput = useStateMachineInput(rive, SM, 'isBlocking');
// Number — health, charge level, anger meter
const healthInput = useStateMachineInput(rive, SM, 'health');
// All inputs are null until rive loads — always guard
const fireAttack = () => { if (attackTrigger) attackTrigger.fire(); };
const setBlock = (v: boolean) => { if (blockInput) blockInput.value = v; };
const setHealth = (hp: number) => { if (healthInput) healthInput.value = hp; };
return <div style={{ width: 400, height: 400 }}><RiveComponent /></div>;
}Input names are case-sensitive, must match Rive Editor. Mismatch returns `null` silently — log return values in dev to catch typos.
| Input type | Hook return type | API | |------------|-----------------|-----| | Trigger | `SMITrigger \| null` | `.fire()` — one-shot event | | Boolean | `SMIBoolean \| null` | `.value` (get/set boolean) | | Number | `SMINumber \| null` | `.value` (get/set number) |
**Before:**
<motion.div
animate={{ y: [0, -3, 0] }}
transition={{ duration: 4, repeat: Infinity }}
style={{ width: 400, height: 400 }}
>
<img src="/sprites/player.png" alt="player" style={{ width: '100%' }} />
</motion.div>**After (Rive):**
<div style={{ width: 400, height: 400 }}>
<RiveComponent />
</div>Remove `motion.div` entirely. Idle bob lives in the `.riv` file. Do not wrap `RiveComponent` in `motion.div`.
Zustand is source of truth. Bridge to Rive inputs via `useEffect`. Never call `input.fire()` inside Zustand actions — keep Rive coupling in the component.
import { useRive, useStateMachineInput } from '@rive-app/react-canvas';
import { useCombatStore } from '../stores/combatStore';
import playerRiv from '../assets/characters/player.riv?url';
const SM = 'CombatStateMachine';
function PlayerCharacter() {
const { RiveComponent, rive } = useRive({ src: playerRiv, stateMachines: SM, autoplay: true });
const attackTrigger = useStateMachineInput(rive, SM, 'attack');
const hitTrigger = useStateMachineInput(rive, SM, 'hit');
const blockInput = useStateMachineInput(rive, SM, 'isBlocking');
// Subscribe only to what this component needs
const lastAction = useCombatStore(s => s.playerLastAction);
const isBlocking = useCombatStore(s => s.playerIsBlocking);
useEffect(() => {
if (lastAction.type === 'attack' && attackTrigger) attackTrigger.fire();
if (lastAction.type === 'hit' && hitTrigger) hitTrigger.fire();
}, [lastAction, attackTrigger, hitTrigger]);
useEffect(() => {
if (blockInput) blockInput.value = isBlocking;
}, [isBlocking, blockInput]);
return <div style={{ width: 400, height: 400 }}><RiveComponent /></div>;
}`useEffect` won't re-fire for consecutive identica
Essays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.
Repo: notque/vexjoy-agent
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**:…
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ /…
Universal rules injected by /do at dispatch. Each agent's .md file supplies domain rules.
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix…
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.