When everyone wants to write to the same Animator
Hit a good bug this week. I added an item-get cutscene: character faces the camera and raises the item overhead, Zelda-style. Built it, ran it, and the pose just didn’t show. Worse, the character was facing the wrong direction.
Simple on the surface. The root cause was more interesting.
Three writers, one Animator
Three separate systems were writing to the same Animator parameters simultaneously, none aware of the others:
- The carry ability was pushing
Carrying = falseevery frame - The movement system was re-asserting facing direction from the last known velocity (I’d walked north into the pickup, so it kept locking the character north)
- The cutscene set the pose once, on entry, and then stopped
“Set once” always loses to “push every frame.” That’s not a bug in any one system: each is doing exactly what it was built to do. The bug is in the interaction: three writers, one bus, no priority.
The deeper problem is that “push every frame” resolves by execution order. Whoever runs last in a given Update wins, and execution order isn’t a thing you can rely on. It changes with script priority settings, inspector ordering, and Update vs. LateUpdate timing. Fix it by luck one frame. Break it again when you reorder something.
The constraint
The obvious fix: suspend the carry and movement writers when a cutscene is active.
This runs into a design boundary. The movement system lives in CodiushMaximush (the game-agnostic framework layer). The cutscene lives in ClassicRPG (the game layer). The framework isn’t allowed to reach into game-layer state. It can’t ask “is a cutscene running?” because the answer lives in a ClassicRPG type, and the framework doesn’t know ClassicRPG exists.
I could flip the direction: have ClassicRPG reach into the framework and call a Suspend() method. That’s still coupling dressed differently. Now the framework needs a pause API, and every system inside it has to respect an external hold signal. Every new system you add has to opt into that contract. It sprawls.
The design
What I want is an arbiter: one thing all systems submit to, which resolves contention on a fixed schedule by priority.
Two submission kinds:
Value channels are continuous, per-frame. Any system pushes a value to a named channel (Facing, Carrying, Speed) and the channel resolves to whichever submission has the highest priority. The others don’t fail. They just lose. They keep submitting, and if the higher-priority signal clears, they resume winning automatically.
Commands are one-shot. Fire a command to seek a specific animation state. It holds until it completes or until something of higher priority preempts it.
Priority order:
fall > cutscene > ability > movement
Fall is physics. Nothing overrides a ragdoll. Cutscenes take over from active abilities. Abilities override locomotion. Locomotion is the default ground state.
With this in place, the item-get cutscene fires a command at cutscene priority. The carry and movement systems keep pushing their values every frame, they’re just outranked. No suspension, no coupling, no coordination protocols between framework and game code.
Contention stops being a hazard. It becomes the resolution mechanism.
Where it stands
The pose is parked until the arbiter is in. Shipping the cutscene before that would mean either hacking around contention or coupling the framework to game layer state, both of which create problems that compound. This system will outlive the one bug that surfaced it.
Update, 2026-05-25. The arbiter shipped. Wired up the Inspector connections, ran it. Item-get pose on the first try: key raised aloft, player facing the camera, dialogue running at 59fps. The per-frame Carrying=false stomp is gone by construction. Three systems keep submitting their values, contention resolves in LateUpdate, and nothing coordinates anything.