Skip to content

Commit 42f604e

Browse files
committed
Add AnimWatch operator next to AnimAlias approach to TLA+ animation guide
- Introduce AnimDoc operator to eliminate viewBox duplication between AnimAlias and AnimWatch - Add comprehensive documentation for AnimWatch (interactive debugging) vs AnimAlias (screencasts) - Update all 6 real-world animation examples to consistently use AnimView/AnimDoc/AnimAlias/AnimWatch pattern - Provide clear usage examples and debugger integration instructions Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
1 parent 7b43c21 commit 42f604e

File tree

1 file changed

+147
-56
lines changed

1 file changed

+147
-56
lines changed

resources/knowledgebase/tla-animations.md

Lines changed: 147 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -38,38 +38,41 @@ TLA+ animations transform behaviors of abstract specifications into visual SVG r
3838

3939
### 1. Create Animation File
4040

41-
For a specification `MySystem.tla`, create `MySystem_anim.tla`:
41+
For a specification `MySpec.tla`, create `MySpec_anim.tla`:
4242

4343
```tla
44-
---- MODULE MySystem_anim ----
45-
EXTENDS TLC, SVG, IOUtils, MySystem
44+
---- MODULE MySpec_anim ----
45+
EXTENDS TLC, SVG, IOUtils, MySpec
4646
4747
\* Define visual representation
4848
AnimView == Circle(50, 50, 20, ("fill" :> "blue"))
4949
50-
\* Enable SVG file generation
50+
\* Wrap view in SVG document (single source of truth for viewBox)
51+
AnimDoc == SVGDoc(AnimView, 0, 0, 100, 100, <<>>)
52+
53+
\* Enable SVG file generation (for TLC model checking)
5154
AnimAlias ==
5255
[state |-> state] @@ \* Include state variables for debugging
53-
[ _anim |-> SVGSerialize(
54-
SVGDoc(AnimView, 0, 0, 100, 100, <<>>),
55-
"MySystem_anim_",
56-
TLCGet("level")
57-
)]
56+
[_anim |-> SVGSerialize(AnimDoc, "MySpec_anim_", TLCGet("level"))]
57+
58+
\* Alternative: For interactive debugging in TLA+ Debugger
59+
AnimWatch ==
60+
SVGSerialize(AnimDoc, "MySpec_anim", 0)
5861
====
5962
```
6063

6164
### 2. Validate Animation
6265

6366
Use the MCP server to check syntax:
6467
```
65-
tlaplus_mcp_sany_parse MySystem_anim.tla
68+
tlaplus_mcp_sany_parse MySpec_anim.tla
6669
```
6770

6871
### 3. Generate Animation SVGs
6972

7073
Create configuration file and use MCP to explore states:
7174
```
72-
tlaplus_mcp_tlc_explore MySystem_anim.tla -depth 10
75+
tlaplus_mcp_tlc_explore MySpec_anim.tla -depth 10
7376
```
7477

7578
## Animation File Structure
@@ -88,18 +91,88 @@ EXTENDS TLC, \* TLC built-ins (@@, :>, etc.)
8891
\* Visual representation logic
8992
AnimView == \* Returns SVG elements based on current state
9093
91-
\* File generation configuration
92-
AnimAlias == \* Configures SVG file output
94+
\* File generation configuration (choose one approach)
95+
AnimAlias == \* For TLC model checking (generates numbered frames)
96+
AnimWatch == \* For TLA+ Debugger (live preview)
9397
====
9498
```
9599

96100
### Key Components Explained
97101

98-
1. **AnimView**: The main operator that creates SVG elements based on the current state
99-
2. **AnimAlias**: Configures how SVG files are generated and named
100-
3. **SVG Module**: Provides constructors for visual elements (Circle, Rect, Text, etc.)
101-
4. **IOUtils Module**: Handles file writing operations
102-
5. **State Mapping**: Your logic that transforms state variables into visual properties
102+
1. **AnimView**: The main operator that creates SVG elements based on the current state. This is the core of your animation - it defines how state variables map to visual properties.
103+
2. **AnimDoc**: Wraps `AnimView` in an SVG document with viewBox parameters. This provides a single source of truth for document dimensions, eliminating duplication between `AnimAlias` and `AnimWatch`.
104+
3. **AnimAlias** or **AnimWatch**: Two different approaches for serializing SVG to disk (detailed below)
105+
4. **SVG Module**: Provides constructors for visual elements (Circle, Rect, Text, etc.)
106+
5. **IOUtils Module**: Handles file writing operations
107+
6. **State Mapping**: Your logic that transforms state variables into visual properties
108+
109+
## Serialization Approaches: AnimAlias vs AnimWatch
110+
111+
While `AnimView` defines *what* to visualize, you need a serialization mechanism to *output* the SVG frames. TLA+ animations support two approaches, each suited to different workflows:
112+
113+
### AnimAlias: For Screencasts and Animation Sequences
114+
115+
**Use Case**: Creating animation sequences for documentation, presentations, or screencasts.
116+
117+
**How it works**:
118+
- Add `ALIAS AnimAlias` to your TLC configuration file (`.cfg`)
119+
- TLC automatically evaluates `AnimAlias` at each state during counterexample generation
120+
- Each state generates a **unique numbered SVG file**
121+
- Frame numbering uses `TLCGet("level")` to create sequential files
122+
123+
**Example**:
124+
```tla
125+
AnimDoc == SVGDoc(AnimView, 0, 0, 760, 420, <<>>)
126+
127+
AnimAlias ==
128+
[active |-> active, color |-> color, counter |-> counter] @@
129+
[_anim |-> SVGSerialize(
130+
AnimDoc, \* Reusable document
131+
"MySpec_anim_", \* Filename prefix
132+
TLCGet("level"))] \* Unique frame number
133+
```
134+
135+
**Generated files**: `MySpec_anim_0.svg`, `MySpec_anim_1.svg`, `MySpec_anim_2.svg`, ...
136+
137+
**Configuration file**:
138+
```
139+
INIT Init
140+
NEXT Next
141+
ALIAS AnimAlias
142+
```
143+
144+
### AnimWatch: For Interactive Debugging
145+
146+
**Use Case**: Live visualization during step-by-step debugging sessions.
147+
148+
**How it works**:
149+
- Use as a Watch expression in the TLA+ Debugger
150+
- Each evaluation **overwrites the same SVG file** (frame number 0)
151+
- Pair with a live SVG viewer that auto-refreshes (e.g., [SVG extension](https://open-vsx.org/extension/jock/svg))
152+
- See animation update in real-time as you step through execution
153+
154+
**Example**:
155+
```tla
156+
AnimDoc == SVGDoc(AnimView, 0, 0, 760, 420, <<>>)
157+
158+
AnimWatch ==
159+
SVGSerialize(
160+
AnimDoc, \* Reusable document
161+
"MySpec_anim", \* Filename prefix
162+
0) \* Frame 0 (overwrites each time)
163+
```
164+
165+
**Generated file**: `MySpec_anim00.svg` (single file, continuously updated)
166+
167+
**Debugger usage**:
168+
When debugging `MySpec.tla`, add this watch expression:
169+
```
170+
LET A == INSTANCE MySpec_anim IN A!AnimWatch
171+
```
172+
173+
### Choosing Between AnimAlias and AnimWatch
174+
175+
You don't need to choose—include *both* in your animation file! Use `AnimAlias` for documentation and `AnimWatch` for development/debugging.
103176

104177
## Creating Your First Animation
105178

@@ -146,18 +219,21 @@ AnimView == Group([n \in 1..Len(NodeSeq) |-> NodeElement(NodeSeq[n])],
146219
("transform" :> "scale(1.2)"))
147220
```
148221

149-
### Step 3: Add AnimAlias for TLC
222+
### Step 3: Add Serialization Operators
150223

151224
Enable file generation:
152225

153226
```tla
227+
\* Wrap view in SVG document
228+
AnimDoc == SVGDoc(AnimView, 0, 0, 400, 200, <<>>)
229+
230+
\* For TLC model checking (generates numbered frames)
154231
AnimAlias ==
155-
[ _anim |-> SVGSerialize(
156-
SVGDoc(AnimView, 0, 0, 400, 200, <<>>),
157-
"DistributedSystem_anim_",
158-
TLCGet("level")
159-
)]
232+
[_anim |-> SVGSerialize(AnimDoc, "DistributedSystem_anim_", TLCGet("level"))]
160233
234+
\* For interactive debugging (live preview)
235+
AnimWatch ==
236+
SVGSerialize(AnimDoc, "DistributedSystem_anim", 0)
161237
```
162238

163239
## Complete Example
@@ -245,18 +321,21 @@ StepNumber ==
245321
AnimView ==
246322
Group(<<Legend, StepNumber>> \o [i \in 1..Len(NodeSeq) |-> NodeVis(NodeSeq[i])], <<>>)
247323
248-
\* TLC animation export
324+
\* Wrap view in document structure
325+
AnimDoc == SVGDoc(AnimView, 0, 0, 100 + Cardinality(Nodes) * NodeSpacing, 200, <<>>)
326+
327+
\* TLC animation export (for screencasts)
249328
AnimAlias ==
250-
[ counter |-> counter ] @@
251-
[ _anim |-> SVGSerialize(
252-
SVGDoc(AnimView, 0, 0, 100 + Cardinality(Nodes) * NodeSpacing, 200, <<>>),
253-
"CounterSystem_anim_",
254-
TLCGet("level")
255-
)]
329+
[counter |-> counter] @@
330+
[_anim |-> SVGSerialize(AnimDoc, "CounterSystem_anim_", TLCGet("level"))]
331+
332+
\* Interactive debugging (for live preview)
333+
AnimWatch ==
334+
SVGSerialize(AnimDoc, "CounterSystem_anim", 0)
256335
====
257336
```
258337

259-
### 3. Configuration File
338+
### 3. Configuration File (for AnimAlias)
260339

261340
```
262341
CONSTANT Nodes = {n1, n2, n3}
@@ -902,13 +981,15 @@ StepNumber ==
902981
903982
AnimView == Group(<<Left, Right, Battery, Chargers, Empty, StepNumber>>, [i \in {} |-> {}])
904983
984+
AnimDoc == SVGDoc(AnimView, 0, 0, 230, 200, <<>>)
985+
905986
AnimAlias ==
906987
[left |-> left, right |-> right,
907988
batteryLeft |-> batteryLeft, batteryLevel |-> batteryLevel] @@
908-
[ _anim |-> SVGSerialize(
909-
SVGDoc(AnimView, 0, 0, 230, 200, <<>>),
910-
"BatteryRelay_anim_",
911-
TLCGet("level"))]
989+
[_anim |-> SVGSerialize(AnimDoc, "BatteryRelay_anim_", TLCGet("level"))]
990+
991+
AnimWatch ==
992+
SVGSerialize(AnimDoc, "BatteryRelay_anim", 0)
912993
====
913994
```
914995

@@ -1062,12 +1143,14 @@ AnimView ==
10621143
Group(<<ProducerLabel, BufferLabel, ConsumerLabel, BufferStatus, FlowArrows, StepNumber>> \o
10631144
Prod \o Buffer \o Conss, <<>>)
10641145
1146+
AnimDoc == SVGDoc(AnimView, 0, 0, 560, 350, <<>>)
1147+
10651148
AnimAlias ==
10661149
[buffer |-> buffer, waitSet |-> waitSet] @@
1067-
[ _anim |-> SVGSerialize(
1068-
SVGDoc(AnimView, 0, 0, 560, 350, <<>>),
1069-
"BlockingQueue_anim_",
1070-
TLCGet("level"))]
1150+
[_anim |-> SVGSerialize(AnimDoc, "BlockingQueue_anim_", TLCGet("level"))]
1151+
1152+
AnimWatch ==
1153+
SVGSerialize(AnimDoc, "BlockingQueue_anim", 0)
10711154
====
10721155
```
10731156

@@ -1142,12 +1225,14 @@ RiverElem ==
11421225
AnimView ==
11431226
Group(<<SideElem(1), SideElem(2), SuccessElem(2), RiverElem, BoatElem>>, <<>>)
11441227
1228+
AnimDoc == SVGDoc(AnimView, 0, 0, 530, 420, <<>>)
1229+
11451230
AnimAlias ==
11461231
[banks |-> banks, boat |-> boat] @@
1147-
[ _anim |-> SVGSerialize(
1148-
SVGDoc(AnimView, 0, 0, 530, 420, <<>>),
1149-
"CabbageGoatWolf_anim_",
1150-
TLCGet("level"))]
1232+
[_anim |-> SVGSerialize(AnimDoc, "CabbageGoatWolf_anim_", TLCGet("level"))]
1233+
1234+
AnimWatch ==
1235+
SVGSerialize(AnimDoc, "CabbageGoatWolf_anim", 0)
11511236
====
11521237
```
11531238

@@ -1216,12 +1301,14 @@ RingFork ==
12161301
AnimView ==
12171302
Group(RingPhil \o RingFork, <<>>)
12181303
1304+
AnimDoc == SVGDoc(AnimView, 0, 0, 270, 270, <<>>)
1305+
12191306
AnimAlias ==
12201307
[philosophers |-> philosophers, forks |-> forks] @@
1221-
[ _anim |-> SVGSerialize(
1222-
SVGDoc(AnimView, 0, 0, 270, 270, <<>>),
1223-
"DiningPhilosophers_anim_",
1224-
TLCGet("level"))]
1308+
[_anim |-> SVGSerialize(AnimDoc, "DiningPhilosophers_anim_", TLCGet("level"))]
1309+
1310+
AnimWatch ==
1311+
SVGSerialize(AnimDoc, "DiningPhilosophers_anim", 0)
12251312
====
12261313
```
12271314

@@ -1292,13 +1379,15 @@ TextElems == RMTextElems \o TMTextElems
12921379
AnimView ==
12931380
Group(RMElems \o <<TMElem>> \o TextElems, <<>>)
12941381
1382+
AnimDoc == SVGDoc(AnimView, 0, 0, 180, 200, <<>>)
1383+
12951384
AnimAlias ==
12961385
[rmState |-> rmState, tmState |-> tmState,
12971386
tmPrepared |-> tmPrepared, msgs |-> msgs] @@
1298-
[ _anim |-> SVGSerialize(
1299-
SVGDoc(AnimView, 0, 0, 180, 200, <<>>),
1300-
"TwoPhase_anim_",
1301-
TLCGet("level"))]
1387+
[_anim |-> SVGSerialize(AnimDoc, "TwoPhase_anim_", TLCGet("level"))]
1388+
1389+
AnimWatch ==
1390+
SVGSerialize(AnimDoc, "TwoPhase_anim", 0)
13021391
=============================================================================
13031392
```
13041393

@@ -1429,14 +1518,16 @@ AnimView ==
14291518
Group(cs \o labels \o termLabels \o logElems \o
14301519
safetyViolationElems \o configVersionTermTitleLabel, <<>>)
14311520
1521+
AnimDoc == SVGDoc(AnimView, 0, 0, 720, 350, <<>>)
1522+
14321523
AnimAlias ==
14331524
[currentTerm |-> currentTerm, state |-> state, log |-> log,
14341525
immediatelyCommitted |-> immediatelyCommitted, config |-> config,
14351526
configVersion |-> configVersion, configTerm |-> configTerm] @@
1436-
[ _anim |-> SVGSerialize(
1437-
SVGDoc(AnimView, 0, 0, 720, 350, <<>>),
1438-
"MongoRaftReconfig_anim_",
1439-
TLCGet("level"))]
1527+
[_anim |-> SVGSerialize(AnimDoc, "MongoRaftReconfig_anim_", TLCGet("level"))]
1528+
1529+
AnimWatch ==
1530+
SVGSerialize(AnimDoc, "MongoRaftReconfig_anim", 0)
14401531
=============================================================================
14411532
```
14421533

0 commit comments

Comments
 (0)