uw-state-machine-ccde40 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited uw-state-machine-ccde40 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Interface-based state machines for game flow, characters, and UI.
docs/ProjectConfig.yaml for:architecture_pattern — "so-first" or "di-first" (affects how states and the StateMachine are created and wired).mcp.unity_mcp — if true, call refresh_unity after creating files.docs/CODING_STANDARDS.md for async patterns (Awaitable + CancellationToken) — state transitions are async..asmdef — create with uw-unity-feature-scaffold if needed.public interface IState
{
Awaitable Enter(CancellationToken ct);
Awaitable Exit(CancellationToken ct);
}For states that need per-frame updates (character movement, AI ticking), extend with:
public interface IUpdatableState : IState
{
void Update();
}Only one state is active at a time. Transitions are async to support loading screens, animations, and cleanup between states.
public class StateMachine
{
private IState _currentState;
public IState CurrentState => _currentState;
public async Awaitable TransitionTo(IState newState, CancellationToken ct)
{
if (_currentState != null)
await _currentState.Exit(ct);
_currentState = newState;
await _currentState.Enter(ct);
}
}If the current state implements IUpdatableState, the owner (a MonoBehaviour or update service) should call Update() each frame:
private void Update()
{
if (_stateMachine.CurrentState is IUpdatableState updatable)
updatable.Update();
}States receive a CancellationToken in Enter and Exit. This token controls the lifetime of async work within that state — when the token cancels, any in-flight awaits should stop cleanly.
Where tokens come from:
// Per-scene token — cancels when the scene unloads
private CancellationTokenSource _sceneCts;
private void OnEnable()
{
_sceneCts = CancellationTokenSource.CreateLinkedTokenSource(
Application.exitCancellationToken);
}
private void OnDisable()
{
_sceneCts?.Cancel();
_sceneCts?.Dispose();
}Pass the token through transitions:
await _stateMachine.TransitionTo(new GamePlayState(_services), _sceneCts.Token);Inside a state, use the token for all async work:
public class LoadingState : IState
{
public async Awaitable Enter(CancellationToken ct)
{
await SceneManager.LoadSceneAsync("Level1", LoadSceneMode.Additive)
.WithCancellation(ct);
await LoadAssetsAsync(ct);
}
public async Awaitable Exit(CancellationToken ct)
{
// Cleanup — this also receives ct in case exit itself is async
}
}LoadingState -> LobbyState -> GamePlayState -> GameOverState
^ |
+------------------------------+Each state manages its own scene loading, UI, and services:
public class GamePlayState : IState
{
private readonly IScoreService _score;
private readonly IAudioService _audio;
public GamePlayState(IScoreService score, IAudioService audio)
{
_score = score;
_audio = audio;
}
public async Awaitable Enter(CancellationToken ct)
{
await SceneManager.LoadSceneAsync("GamePlay", LoadSceneMode.Additive)
.WithCancellation(ct);
_score.Reset();
_audio.PlayMusic(MusicTrack.Gameplay);
}
public async Awaitable Exit(CancellationToken ct)
{
_audio.StopMusic();
await SceneManager.UnloadSceneAsync("GamePlay")
.WithCancellation(ct);
}
}IdleState <-> RunState -> JumpState -> FallState
^ ^ | |
+-----------+-----------+-----------+Character states typically need per-frame updates and transition conditions:
public class CharacterIdleState : IUpdatableState
{
private readonly CharacterController _controller;
private readonly StateMachine _fsm;
private readonly CancellationToken _ct;
public CharacterIdleState(CharacterController controller, StateMachine fsm, CancellationToken ct)
{
_controller = controller;
_fsm = fsm;
_ct = ct;
}
public async Awaitable Enter(CancellationToken ct)
{
_controller.PlayAnimation("Idle");
}
public void Update()
{
if (_controller.MoveInput.magnitude > 0.1f)
{
_fsm.TransitionTo(
new CharacterRunState(_controller, _fsm, _ct), _ct).Forget();
}
}
public async Awaitable Exit(CancellationToken ct) { }
}MainMenuScreen -> SettingsScreen -> ConfirmDialog
^ |
+-------------------------------+Use for screen stacks where each screen manages its own visual state. Each screen state shows/hides its UI root and handles input.
For states with multiple exit paths, check conditions in Update() and transition to the appropriate next state:
public void Update()
{
if (_controller.IsGrounded && _controller.JumpPressed)
{
_fsm.TransitionTo(new JumpState(_controller, _fsm, _ct), _ct).Forget();
return; // Only one transition per frame
}
if (!_controller.IsGrounded)
{
_fsm.TransitionTo(new FallState(_controller, _fsm, _ct), _ct).Forget();
return;
}
}Only transition once per frame — check the most important condition first and return after starting a transition. Multiple transitions in the same frame cause race conditions.
When architecture_pattern: "di-first", register the StateMachine and use a factory to create states with injected dependencies:
// In installer
public void InstallBindings(ContainerBuilder builder)
{
builder.AddSingleton(typeof(StateMachine));
builder.AddTransient(typeof(GamePlayState));
builder.AddTransient(typeof(GameOverState));
}States receive their dependencies via constructor injection — the container resolves them automatically:
public class GamePlayState : IState
{
[Inject] private readonly IScoreService _score;
[Inject] private readonly IAudioService _audio;
// Container creates this with all deps resolved
}See uw-dependency-injection for container setup, installer patterns, and the class taxonomy.
When architecture_pattern: "so-first", states can be MonoBehaviours with serialized transition references, or plain classes created in a MonoBehaviour controller:
public class GameFlowController : MonoBehaviour
{
[SerializeField] private GameEvent _onGameStart;
[SerializeField] private GameEvent _onGameOver;
private StateMachine _fsm;
private void Awake()
{
_fsm = new StateMachine();
}
private async void Start()
{
var ct = destroyCancellationToken;
await _fsm.TransitionTo(new LobbyState(_onGameStart), ct);
}
}Use uw-scriptable-object-arch event channels for communication between states and other systems.
uw-unity-test-runner — test state transitions in EditMode by asserting CurrentState type after TransitionTo.uw-game-feel-integrator for transition animations, screen effects between states.uw-unity-debugging if states aren't entering/exiting as expected.CancellationToken to Enter and Exit — all async work inside states must respect cancellation.Application.exitCancellationToken or scene lifetime.Exit().return after starting a transition..asmdef.[SerializeField] private for any Inspector-exposed fields on MonoBehaviour owners.ProjectConfig.yaml -> mcp.unity_mcp is true, call refresh_unity after creating files.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.