"The stock PlayerController has no public speed property: you can't scale move speed from outside"
▸ SYMPTOM
You want another component to scale the player's movement speed (a slow effect, a root, a haste buff) and you go looking for controller.Speed or controller.WalkSpeed. They aren't reachable. There is no clean, order-independent way to turn player move speed down from an external component. Verified on engine 26.07.15a.
▸ CAUSE
The stock Sandbox.PlayerController exposes no public speed property. WalkSpeed, RunSpeed, DuckedSpeed, and Speed are private serialized [Property] fields.
You can confirm this from the installed doc XML (bin/managed/Sandbox.Engine.xml): the public members it lists touching movement are WishVelocity, Velocity, AccelerationTime, RunByDefault, AltMoveButton, IsDucking. The four speed fields never appear (only DebugFootsteps does). Meanwhile the Player Controller prefab template does serialize WalkSpeed / RunSpeed / DuckedSpeed / Speed, which proves they exist as private [Property] fields rather than public members.
WishVelocity is public, but the controller recomputes it from input every fixed-update, so setting it from another component is order-dependent and gets overwritten: a hack, not a seam. And reflection is off the table (whitelist), so you can't reach the privates that way either.
▸ FIX
Give yourself a real seam instead of fighting the stock controller:
- Swap in a custom controller that exposes a public speed knob, or
- Expose one via a subclass / authored prefab that surfaces the value you need, or
- Leave player move-speed status as a documented stub if it's not worth the controller swap yet.
Note that NPC brains are unaffected: an NPC that owns its own WalkSpeed / RunSpeed fields reads its own values, so status effects on NPC speed work fine. The gap is specifically the stock player controller.
Whatever you do, don't hunt for controller.Speed / controller.WalkSpeed: they aren't reachable from outside.
▸ WHY IT WORKS
The stock controller was authored with those fields private and serialized, so its speed is a designer-tuned constant, not a runtime API. The only way to get an order-independent runtime knob is to own the field yourself (in a custom controller or a subclass) where you control both the storage and the fixed-update that reads it. That removes the recompute-overwrite race that makes poking WishVelocity unreliable.
- Published.