|
| 1 | +# Sequence — Deferred / TODO |
| 2 | + |
| 3 | +## Deferred: Token Create/Destroy |
| 4 | + |
| 5 | +Token creation and destruction are not currently supported. Two design approaches are worth considering: |
| 6 | + |
| 7 | +**Approach A — Per-token keyframe types (`create`/`destroy`)** |
| 8 | +Add `create` and `destroy` as keyframe types on a single-token recording. On playback of `create`, call `createObj` using identity data (imgsrc, size, layer, etc.) stored at record time. On playback of `destroy`, call `obj.remove()`. Recording would capture token deletion via `destroy:graphic` and token creation via an `add:graphic` listener (or `--create [n]` flag). Simple extension of the current model but awkward when multiple tokens are involved. |
| 9 | + |
| 10 | +**Approach B — Page-level recording** |
| 11 | +A single handout captures an entire scene: one track per token, each track starting/ending at the timestamps when that token existed. Token appearance and disappearance fall out naturally from track start/end times rather than explicit keyframe types. Better fit for cutscene-style multi-token animations but requires a significant rethink of playback targeting (currently one recording plays on one token). |
| 12 | + |
| 13 | +**Why deferred:** Choreograph will introduce multi-token orchestration and may clarify which approach fits better. Revisit after Choreograph is implemented. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Deferred: Command Keyframe — Token Selection |
| 18 | + |
| 19 | +When firing a command keyframe during playback, there is currently no mechanism to select the playback target token so that receiving scripts can act on it via `msg.selected`. |
| 20 | + |
| 21 | +**Design sketch:** |
| 22 | +- Add a `sel-command` keyframe type (or `selectToken: true` flag) to indicate the playback target should be "selected" when the command fires |
| 23 | +- `!sequence command --select` records with this flag set |
| 24 | +- `!sequence add-command --select` sets it on a saved recording |
| 25 | +- On playback, construct the outgoing message with the token ID injected into `selected` (or use SelectManager's forwarding mechanism if the target script reads `msg.selected` directly) |
| 26 | +- Selection must happen before the command fires |
| 27 | + |
| 28 | +**Multi-selection note:** Some scripts behave differently (or require) multiple tokens in `msg.selected` simultaneously. If combined with a `sync` keyframe, all tokens in the playback group could be injected into `selected` for the subsequent command. This overlaps with the sync/barrier design below. |
| 29 | + |
| 30 | +**Why deferred:** Most scripts can use `{{tokenId}}` substitution instead. Defer until there's a concrete script that requires `msg.selected`. |
| 31 | + |
| 32 | +--- |
| 33 | + |
| 34 | +## Deferred: Sync Keyframe Type (Multi-Token Barrier) |
| 35 | + |
| 36 | +When the same recording plays on multiple tokens simultaneously, a `sync` keyframe type would act as a general-purpose barrier: all tokens in the playback group pause at the sync point until everyone arrives, then all proceed together. |
| 37 | + |
| 38 | +**Design sketch:** |
| 39 | +- Add `sync` as a keyframe type (alongside `change` and `command`) |
| 40 | +- Sessions started from the same `!sequence play` invocation share a playback group ID |
| 41 | +- When a session reaches a `sync` row, it pauses until all sessions in the group have reached (or passed) the same sync point |
| 42 | +- Configurable timeout with "proceed anyway" fallback to avoid deadlocks |
| 43 | +- Whatever follows the sync row (command, change, etc.) benefits from the coordination without needing special flags |
| 44 | + |
| 45 | +**Use cases:** |
| 46 | +- Grouped commands: `sync` row followed by `command` row — all tokens are coordinated before the command fires |
| 47 | +- Synchronized motion: `sync` before a position change ensures tokens move in unison despite non-deterministic timing earlier |
| 48 | +- Combined with token selection: at a sync point, all group members could be injected into `msg.selected` for the next command |
| 49 | + |
| 50 | +**Why deferred:** Requires the "playback group" concept (sessions sharing a group ID). No concrete use case yet. Revisit after Choreograph. |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## Note: Command Side Effects and Double-Recording |
| 55 | + |
| 56 | +Roll20's `change:graphic` events are **only fired for player/GM-initiated changes**. API-initiated `obj.set()` calls do NOT trigger change events. This means if a command keyframe fires during recording and the receiving script modifies the token via `obj.set()`, Sequence will not capture those changes as duplicate keyframes — there is no double-recording problem for core attributes. |
| 57 | + |
| 58 | +The only scenario where double-recording could occur is with **virtual attributes** where an extension calls `Sequence.notifyChange()` directly in response to a command. Two approaches to handle this without burdening extension authors: |
| 59 | + |
| 60 | +1. **Command prefix registration** — extensions register the prefix strings their commands start with (e.g. `"!myscript"`). When Sequence is about to invoke a recorded command matching that prefix, it temporarily unlinks that extension's `notifyChange` calls until the command's effects settle. |
| 61 | + |
| 62 | +2. **Last-command introspection API** — expose `Sequence.getLastCommand()` or similar, returning the last recorded command and its invocation timestamp. Extensions can check this themselves if they want to conditionally skip `notifyChange` calls triggered by a Sequence-fired command. |
| 63 | + |
| 64 | +Either approach (or both) makes the system safe for virtual attributes without requiring extension authors to manually track suppression state. |
| 65 | + |
| 66 | +--- |
| 67 | + |
| 68 | +## Planned: `t` Variable in Expressions (Orbit / Cycle Animations) |
| 69 | + |
| 70 | +Add a normalized time variable `t` (0–1, representing elapsed fraction of the current loop cycle) to the expression scope so users can write orbit-style animations: |
| 71 | + |
| 72 | +``` |
| 73 | +left: =orig_left + cos(t * 2 * PI) * distance |
| 74 | +top: =orig_top + sin(t * 2 * PI) * distance |
| 75 | +``` |
| 76 | + |
| 77 | +Needed for `!sequence generate orbit` and MovementAnimations parity. TBD whether this lives in Sequence's expression scope or in Choreograph. |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +## Deferred: Parameterized Recordings / `!sequence generate` |
| 82 | + |
| 83 | +Rather than a `generate` command that creates handouts from parameters, a better approach may be parameterized recordings — handouts where expressions reference parameters supplied at play time (e.g. `--param distance=140`). This would allow shipping pre-built templates (spin, hover, orbit) that users can customize without editing the handout. |
| 84 | + |
| 85 | +However, Choreograph already has a `{{paramName}}` substitution system and is the natural place for per-token parameter customization. Designing Sequence's parameter system before Choreograph exists risks duplication or conflict. |
| 86 | + |
| 87 | +**Plan:** Implement `t` (normalized loop time) in Sequence now. Defer full parameter substitution until Choreograph clarifies the boundary between the two systems. Ship example handouts (spin, hover, orbit using `t`) after `t` is implemented. |
| 88 | + |
| 89 | +--- |
| 90 | + |
| 91 | +## Planned: MovementAnimations Parity |
| 92 | + |
| 93 | +Spin and hover are straightforward with current Sequence. Orbit requires the `t` variable or Choreograph. Wave stagger offsets (the 8 sort orders from MovementAnimations) belong in Choreograph. |
0 commit comments