01 / Choose the form
Use the smallest architecture that fits.
SND is excellent for quickly expressing a personal, sequential workflow. A standalone plugin becomes worthwhile when the work needs reusable UI, persistent configuration, release packaging, multiple consumers, cancellation, recovery, or long-lived state.
| Need | Prefer |
|---|---|
| Personal or experimental sequence | SND Lua script |
| Configurable script with an intentional dependency | SND plus documented modules or IPC |
| Reusable UI, state, and release package | Standalone Dalamud plugin |
| Long-running recovery and cancellation | Plugin with a task runner |
02 / Target architecture
Split behavior into four layers.
Configuration, status, controls, and failure messages. It observes the workflow; it does not contain it.
Task-specific decisions and composition. It calls shared operations instead of duplicating them.
Ordered nonblocking steps with enter actions, completion predicates, timeouts, retries, cancellation, and diagnostics.
Chat, input, addon interaction, IPC, navigation, and other operations tested once and reused.
This boundary is the heart of the port. A shared operation belongs in one service, a workflow contains only its own decisions, and the task runner owns sequence mechanics.
03 / Translation
Turn waits into observable state.
A sequential script can suspend itself. A plugin must remain responsive while the framework continues to update and draw.
SND-style pseudocode
yield("/command destination")
yield("/wait 1")
while IsBusy() do
yield("/wait 0.1")
endPlugin-style pseudocode
runner.Enqueue(new TaskStep(
name: "Issue command",
onEnter: () => commands.Execute("destination"),
isComplete: () => true,
timeout: TimeSpan.FromSeconds(2)));
runner.Enqueue(new TaskStep(
name: "Observe transition start",
isComplete: () => gameState.IsBusy,
timeout: TimeSpan.FromSeconds(10)));
runner.Enqueue(new TaskStep(
name: "Wait for transition end",
isComplete: () => !gameState.IsBusy,
timeout: TimeSpan.FromSeconds(45),
maxRetries: 1));The runner polls completion from the framework/update loop. The example shows the contract shape; it is not a drop-in promise of exact API names.
04 / Readiness
A delay is not a readiness check.
After relog, teleport, zoning, or addon reconstruction, require several consecutive ready observations before the next action. Three passes is a practical default.
Reset the pass counter whenever readiness becomes false. Keep the predicate specific: player available, expected territory loaded, not between areas, required addon present, and no occupied state that invalidates the next action.
05 / Step contract
Every step must finish or fail clearly.
Use the same name in UI, logs, reports, and tests.
Run the action once per attempt, not on every poll.
Transient unavailable state returns false instead of crashing.
No step waits forever or silently wedges the sequence.
Retry only when repeating the operation is safe.
Stop, dispose, and reload paths leave no active work behind.
06 / Porting workflow
Move in small, testable slices.
- Inventory the script.Record every command, condition, wait, loop, external call, mutable variable, and exit path.
- Extract shared operations.Reuse or create a service; do not paste command helpers into the workflow.
- Draw state transitions.Include busy-start and busy-end observations, not just the happy path.
- Create named steps.Replace each blocking wait or loop with pollable state.
- Add failure mechanics.Implement finite timeouts and cancellation before retries.
- Gate readiness.Use consecutive checks after relog, teleport, zone, and addon reconstruction.
- Expose diagnostics.Show current step, elapsed time, retries, and last failure.
- Compose gradually.Test commands, short sequences, and finally the complete workflow.
07 / Validation
Prove the behavior at each layer.
- No
Thread.Sleep, busy loop, or synchronous long wait blocks the framework callback. - Stop and dispose cancel active work and unregister events exactly once.
- Every step completes, times out, is cancelled, or emits a specific failure.
- Retries cannot repeat destructive work without an explicit idempotency guard.
- Relog and zone transitions require observed readiness, not elapsed time alone.
- Unavailable player, territory, addon, IPC provider, or dependency state is handled.
- Debug and Release builds use the current toolchain contract.
- The packaged archive contains the intended DLL, manifest, and approved assets only.
- Live in-game validation is recorded separately from compile and package success.
08 / Upstream references