"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.