Private
Public Access
0
0

digest on computational shapes ssdl

This commit is contained in:
2026-06-08 21:23:11 -04:00
parent 51ecace464
commit 0be9b4f0fb
@@ -0,0 +1,504 @@
# Computational Shapes SSDL — A Digest for Ideation
**Track:** TBD (digest for later pickup)
**Date:** 2026-06-08
**Author:** Tier 2 Tech Lead (synthesis)
**Status:** Draft — not yet wired into any track; for ideation later
> **What this is.** A condensed digest of *computational shapes* thinking — the mental model Ryan Fleury formalized in [A Taxonomy of Computation Shapes](https://www.dgtlgrove.com/p/a-taxonomy-of-computation-shapes) (Feb 2023), the problem it solves in [The Codepath Combinatoric Explosion](https://www.dgtlgrove.com/p/the-codepath-combinatoric-explosion) (Apr 2023), the historical indictment in Casey Muratori's [The Big OOPs: Anatomy of a Thirty-Five-Year Mistake](https://youtu.be/wo84LFzx5nI) (BSC 2025), and the technique to defuse it in Andrew Reece's [Assuming as Much as Possible](https://www.youtube.com/watch?v=i-h95QIGchY) (BSC 2025).
>
> **Why SSDL.** The user asked for an "ASCII SSDL" (Spec/Sketch Description Language) — a small, fixed vocabulary of ASCII primitives that can be composed to express computational shapes. The shapes are inherently visual (data flows, control flow, parallelism, repetition) and ASCII is a tolerable proxy when an actual diagram is unavailable. The vocabulary is intentionally small (~6 primitives + ~5 modifiers) so that sketches are comparable across documents and people.
>
> **Who this is for.** Future work on Manual Slop (or any LLM-driven coding project) where the design conversation would benefit from sketching the *shape* of computation before writing code. The 6-shape vocabulary gives us a shared language for "is this a codepath or a codecycle?" "where's the wide?" "how many effective codepaths does this introduce?" — questions that are otherwise answered in prose and get lost.
---
## 0. The 30-second version
If you only read one section, read this one.
**The problem** (Fleury's combinatoric explosion): every branch in code multiplies the set of possible effective codepaths. If you have 5 branches in a function and the function is called from 10 different sites, you have 50+ codepaths to reason about. Stateful code makes this worse (state combinations multiply too). The result is that modern codebases have so many effective codepaths that you cannot test them all, debug them all, or reason about them all — and the cost of every new branch is the multiplicative product of all preceding ones.
**The historical cause** (Muratori's 35-year mistake): for 35 years, the dominant architectural pattern has been to draw encapsulation boundaries around *compile-time domain hierarchies* (class A inherits from B inherits from C, mirroring real-world taxonomy). This was specifically advocated by the creators of OOP (Stroustrup, Kay, Dahl, Nygaard), and it's the wrong shape. The correct shape is to draw encapsulation boundaries around *systems* (behaviors, data transformations), not *entities* (objects with state). The early evidence for this was right there: Doug Ross's 1956 `plex` (data + function pointers), Ivan Sutherland's 1963 Sketchpad (constraints as systems), Looking Glass Studios' 1998 *Thief: The Dark Project* (Entity-Component-System).
**The technique** (Fleury's "effective codepaths" + Reece's "assume as much as possible"): two complementary moves.
1. **Reduce the number of effective codepaths** by making multiple real codepaths *behave the same way* in the dimensions you care about. This is what nil sentinels, generational handles, immediate-mode APIs, and "more answers, not more questions" all do. Each technique adds an invariant that *applies in all cases*, collapsing N real codepaths into 1 effective codepath.
2. **Assume as much as possible** about your access patterns and exploit those assumptions. The Xar (Exponential Array) is a growable array that uses power-of-2 chunks and bitwise operations instead of `realloc`+copy — but only because the design *assumes* the access pattern is append-heavy with occasional random access. A general-purpose `std::vector`-like structure makes fewer assumptions, hides more from the user, and pays for it with spiky latency and pointer invalidation.
The two moves reinforce each other: every assumption you make lets you remove an abstraction layer; every layer you remove eliminates a class of effective codepaths.
---
## 1. The 6 SSDL primitives
A *computation shape* is a high-level concept, not a physical thing. The diagrams are meant to be sketched, not measured. The vocabulary:
| # | Shape | One-line definition | SSDL symbol |
|---|---|---|---|
| 1 | **Instruction** | A single unit of computation. Reads data, writes data, or both. | `[I]` |
| 2 | **Codepath** | A sequential list of instructions that *terminates*. No loops. | `===>` |
| 3 | **Wide codepath** | A codepath whose execution *causes* several other codepaths to occur simultaneously. | `===>W===>` (codepaths fan out) |
| 4 | **Codecycle** | A circular structure — a codepath that *repeats* at its first instruction after its last. | `o==>` (arrow returns to start) |
| 5 | **Wide codecycle** | Multiple codecycles performing the same task simultaneously. | `oo==>oo` (parallel cycles) |
| 6 | **Codecycle graph** | Multiple codecycles + the data they read and write. | `boxes + arrows` |
**Modifiers** (not shapes, but used to annotate them):
| Modifier | SSDL | Meaning |
|---|---|---|
| `[T]` | terminator | The instruction that *ends* a codepath (return, exit, etc.) |
| `[B]` | branch | A point where control flow forks based on a condition |
| `[M]` | merge | A point where control flow re-converges |
| `[S]` | stateful | Marks an instruction that *mutates* persistent state |
| `[Q]` | query | Marks an instruction that reads persistent state |
| `[N]` | nil sentinel | A special value that satisfies "is this OK to use?" in all cases |
| `───` | data | A line representing data being read or written (not a codepath) |
**Legend**:
```
[I] = single instruction
===> = codepath (linear, terminates at T)
===>W===> = wide codepath (causes parallel codepaths)
o==> = codecycle (loops back to start)
oo==>oo = wide codecycle (parallel codecycles doing the same task)
[T] = terminator (return/exit)
[B] = branch (if/else/switch)
[M] = merge (control flow reconverges)
[S] = state mutation
[Q] = state query
[N] = nil sentinel (defuses branches)
─── = data (read or write)
[•] = codepath that is *defused* (collapses to 1 effective codepath)
```
---
## 2. The combinatoric explosion (before / after)
### 2.1 Before: the "obvious" code
A function with two `if` statements and one nested call. Looks like one function. Read the SSDL:
```
[I:FunctionA]──┐
[B:check A]──────┐
╱ ╲
╱ ╲
╱ ╲
▼ ▼
[I:FunctionB] [I:FunctionC]
╲ ╱
╲ ╱
╲ ╱
[B:check X]──────┐
╱ ╲
╱ ╲
╱ ╲
▼ ▼
[I:DoA] [I:DoB]
╲ ╱
╲ ╱
╲ ╱
[I:FunctionD]
[T]
```
This is **4 real codepaths**:
```
1. [A]; [B]; [DoA]; [D] (A true, X true)
2. [A]; [B]; [DoB]; [D] (A true, X false)
3. [A]; [C]; [DoA]; [D] (A false, X true)
4. [A]; [C]; [DoB]; [D] (A false, X false)
```
Now imagine `FunctionB`, `FunctionC`, and `FunctionD` each have their own internal branches. Say each has 3 branches. Then the call site has 3 × 3 × 4 = 36 effective codepaths. Add state (a global config, a user session), and each effective codepath is also conditioned on the state at the time of the call. Add another caller — the multiplication repeats.
This is the **combinatoric explosion**. It is not a bug; it is a *property* of stateful, branchy, multi-caller code. The 35-year mistake is designing code that *amplifies* this property unnecessarily (Muratori's hierarchical OOP), instead of designing code that *defuses* it (Fleury's effective codepaths, Reece's assumed-away abstractions).
### 2.2 After: defusing techniques in SSDL
Each technique below is a transformation. Read the SSDL to see the *shape* of the change, not just the diff.
#### Technique 1: Nil sentinel (collapses "is this valid?" to "yes")
**Before** (the SearchTreeForInterestingChain bug from Fleury's article — null pointer dereference):
```
[Q:root]
[B:root != 0?]
├─ no ─────► [T] (return 0)
└─ yes
[I:ChildFromValue(root, 1)]
[B:result != 0?]
├─ no ──► [T]
└─ yes
▼ (×3)
[I:ChildFromValue(n_prev, n)]
...
```
This is **8 effective codepaths** (2 × 2 × 2 from the three nested `if (nX)` checks), and the bug is that 7 of them are *not tested* (the test only exercises the happy path).
**After** (with nil sentinel — `nil_node` is a reserved, valid, dereferenceable node):
```
[Q:root] (root itself is now guaranteed valid)
[I:ChildFromValue(root, 1)]
[I:ChildFromValue(n1, 2)]
[I:ChildFromValue(n2, 3)]
[I:ChildFromValue(n3, 4)]
[T] (return n4; always valid because nil_node is valid)
```
**1 effective codepath.** The nil sentinel is a `[N]` in the SSDL:
```
nil_node: [N] = { &nil_node, &nil_node, &nil_node, 0 } // self-referential sentinel
```
Because the sentinel is valid (its `first`, `last`, `next` point to itself, so loops terminate), the "is this 0?" branch *never arises*. Every codepath terminates with a usable pointer. The 8 effective codepaths collapse to 1.
#### Technique 2: Generational handle (collapses "is the entity still alive?" to "yes")
**Before** (the carrier_entity problem from Fleury's article — pointing to a freed entity):
```
[Q:carrier_entity->is_active]
[B:active?]
├─ no ──► bug! (treated as valid, but the slot is reused)
└─ yes
[Q:carrier_entity->position]
[I:draw_at(position)]
```
**After** (with generational handle):
```
Handle jar_handle = ... // { entity*, generation }
[Q:HandleFromEntity(jar_handle)]
[I:check generation]
[B:gen matches?]
├─ no ──► [I:use nil_node (a sentinel, like above)]
└─ yes
[Q:entity->position]
[I:draw_at(position)]
```
Same shape as the nil-sentinel technique: a generation mismatch is *not* a branch in the user's code, it's a *defused* branch where the answer is "use the sentinel." The user's drawing code never has to ask "is the entity still alive?" — the handle subsystem has already defused that question.
#### Technique 3: Effective codepath (the abstract pattern)
The pattern in both techniques is the same: **introduce a subsystem that returns a value which is valid in all cases**. The user's calling code becomes a single straight-line codepath (no `[B]`, no `[M]`, no exception paths). The subsystem is *itself* a complex codepath, but it's *encapsulated*.
In SSDL, the pattern looks like:
```
USER CODE: SUBSYSTEM:
[Q:key]
[B:hash collision?] │
├─ yes ──► [I:resolve] ▼
│ │ [B:slot occupied?]
│ ▼ ├─ yes ──► [I:compare keys]
│ [I:use value] │ │
└─ no ──► [I:use value] │ ▼
│ [B:match?]
▼ ├─ yes ──► [T:return existing]
[T] └─ no ──► [T:return new node]
[S:insert]
[T]
```
The user's code is now `===> [T]` (one straight line, one terminator). The subsystem absorbed the branches. **The number of *user-visible* effective codepaths went from 4 to 1.** The total number of codepaths in the program didn't decrease — but the *exposed surface area* did, and that's what matters for the caller's cognitive load, testing burden, and bug surface.
#### Technique 4: Immediate-mode API (collapses "did I create/destroy this?" to "no, it's managed for me")
Reece's `TextureFromKey` example from the codepath-combinatoric-explosion article:
**Before** (retained-mode `LoadTexture`):
```
MAIN LOOP: ASSET SUBSYSTEM:
(called once at init)
[Q:texture]
│ [I:allocate texture memory]
▼ [S:store in registry]
[B:texture valid?]
├─ no ──► [B:is it reloading?] [B:is active?]
│ ├─ yes ──► [I:unload] ├─ yes ──► [T:return existing]
│ │ [I:load] └─ no ──► [T:load]
│ ▼
└─ yes (called every frame)
▼ [Q:is texture key valid?]
[I:DrawSprite(texture)] ├─ no ──► [I:reload]
└─ yes ──► [T:use cached]
```
The main loop has at least 3 effective codepaths (texture valid, texture needs reload, texture just loaded). Worse, these *compound* with state — the texture may be in the process of loading, may be queued for unload, may have a pending reload. The user code has to know about all of these.
**After** (immediate-mode `TextureFromKey`):
```
MAIN LOOP: ASSET SUBSYSTEM:
(called every frame)
[Q:texture key]
│ [Q:key in cache?]
▼ ├─ yes ──► [T:return cached]
[I:TextureFromKey(key)] └─ no ──► [I:load] (deferred/backgrounded)
[I:DrawSprite(texture)] [S:insert in cache]
│ [T:return]
[T]
```
**1 effective codepath** in the main loop. The cache subsystem manages lifecycle entirely. The user code never has to ask "is the texture ready?" — it's always ready (or always being loaded; either way, the user code does the same thing). This is the same trick Reece plays with hash tables: hide the load/evict logic behind an interface that returns a usable value in all cases.
#### Technique 5: Assume-away (Xar)
Reece's Xar is a growable array that:
- **Assumes** you don't need to copy on growth (use a new chunk, leave the old one in place) → eliminates `[B:realloc?]`
- **Assumes** you have a known upper bound on chunks (32 for 64-bit address space) → fixed-size metadata, no `[B:metadata resize?]`
- **Assumes** chunk sizes are powers of 2 → bitwise divmod, no `[B:divmod fallback?]`
- **Assumes** pointers don't need to be stable on growth → free, since each chunk is independent
Each assumption is a branch that *would have existed* in a general-purpose structure. Reece's Xar eliminates them by saying "we don't support the case where this assumption is violated." For a dynamic-array workload where those assumptions hold, the Xar is dramatically better. For a workload where they don't, the Xar doesn't work — but Reece argues that's the right tradeoff (the workload is the *common* case; users with weird workloads can use a different structure).
In SSDL, the Xar is a codepath graph where *the metadata subsystem is a small fixed-size codepath* (no allocation, no resize, no exception paths) and *the data subsystem is a codecycle* (chunks grow as needed):
```
METADATA (fixed, allocation-free):
[Q:chunk_index = index >> log2(chunk_size)]
[Q:offset = index & (chunk_size - 1)]
[T:return chunks[chunk_index] + offset] // 2 instructions, both bitwise
```
```
DATA (chunked, growable):
[Q:count < capacity?]
[B:?]
├─ yes ──► [I:return chunks[count++]]
└─ no
[S:allocate new chunk of size 2^n]
[S:store in chunks[log2(n)]
[I:return chunks[count++]]
```
Both subsystems are simple. The data subsystem has 1 effective codepath per chunk-size *n*. The metadata subsystem has 1 effective codepath period. Compare to `std::vector`'s growth:
```
[Q:count == capacity?]
[B:?]
├─ no ──► [I:return data[count++]] // fast path
└─ yes
[S:allocate new buffer of size 2*capacity]
[S:copy old data to new buffer]
[S:deallocate old buffer]
[I:return data[count++]]
```
Same shape, but the copy + deallocate are the `[B:realloc may invalidate pointers]` problem in disguise. The Xar doesn't have them because it doesn't try to maintain a single contiguous buffer — it just adds another chunk. **Same algorithmic shape, fundamentally different effective-codepath count for the user.**
---
## 3. The "domain vs systems" lens (Muratori)
The historical piece. The 35-year mistake:
```
DOMAIN HIERARCHY (OOP):
┌──► [Animal]
│ │
│ ├──► [Dog]
│ ├──► [Cat]
│ └──► [Bird]
[LivingThing]──┐
│ │
│ ├──► [Tree]
│ └──► [Mushroom]
[Entity] (root)
└──► ...
Each node has methods. [Dog].Bark() works because Dog inherits from
Animal which has a virtual Speak() method. [Bird].Speak() is also a
virtual Speak() call. [Tree] is "LivingThing" too but doesn't Speak()
— it Photosynthesizes().
Number of effective codepaths: every combination of (type × method call).
If you have 20 types and 15 methods, the type system is fine but
the runtime dispatch creates 20 × 15 = 300 effective codepaths to
reason about.
```
```
SYSTEM-ORIENTED (ECS):
┌──► [PhysicsSystem]
│ │ operates on: [Position] + [Velocity]
│ ▼
│ [B:entity has both components?]
│ ├─ no ──► [T]
│ └─ yes
│ ▼
[Entity] ──────┤ [I:integrate_velocity]
(a bag of │ [I:update_position]
components) │
│──► [CollisionSystem]
│ │ operates on: [Position] + [BoundingBox]
│ ▼
│ [B:overlap?]
│ ├─ no ──► [T]
│ └─ yes
│ ▼
│ [I:emit collision event]
└──► [RenderSystem]
│ operates on: [Position] + [Sprite]
[I:draw_sprite_at(position)]
Number of effective codepaths: each system has 1 effective codepath
(its own B+action). The total is (#systems × 1) = #systems effective
codepaths, independent of the number of entity types.
```
In Muratori's framing, the OOP version *amplifies* the codepath count by a factor of *type count*; the ECS version is *invariant* in type count. Adding a new entity type in OOP is "free" at compile time but explodes the runtime codepath surface. Adding a new entity type in ECS is "free" at runtime (it's just a new bag of components) but doesn't change the codepath surface. Adding a new *system* in OOP is hard (you need to add a virtual method to every type) but doesn't change the codepath surface. Adding a new *system* in ECS is the natural place to add new behavior — and it adds 1 new effective codepath, not N.
**The right question to ask when designing a feature**: "am I adding a new *kind of thing* (then ECS, components, no new codepaths in the existing systems) or am I adding a new *behavior that operates on existing things* (then ECS, a new system, +1 codepath)?" Most features in real codebases are the second kind. ECS is the natural shape for them.
---
## 4. The "assume as much as possible" lens (Reece)
Reece's contribution is the *engineering discipline* for how to find and exploit the assumptions that make ECS, nil sentinels, generational handles, immediate-mode APIs, and Xar-style structures all possible. The pattern is:
```
For every design decision in your system:
Q1: What does the user need to do with this?
Q2: What can I assume about how they do it?
Q3: If I assume Q2 is true, can I eliminate a layer of indirection?
Q4: What's the cost of being wrong about Q2?
If the cost of being wrong is low (e.g., the user has a different
workload, can use a different structure), and the benefit of
assuming is high (no copy, no pointer invalidation, no cache miss,
no branch), then assume.
If the cost of being wrong is high (e.g., the structure is
load-bearing for the whole program, the user has no alternative),
then don't assume — keep the generality.
```
Reece's `WhiteBox` debugger is full of these. The Xar is one. The `KeylessHashMap` (no key storage, hash IS the key) is another. The `MultiKeyHashMap` (parameters passed through registers, not wrapped in a struct) is a third. Each one is a case of "I know what my user is doing, so I'll strip the layer they don't need."
**The general principle** is the inverse of the OOP heuristic. OOP says: *be general, anticipate all use cases, encapsulate the variation.* Reece says: *be specific, know your use case, expose the variation.* OOP adds layers; Reece removes them. OOP maximizes abstraction; Reece maximizes *exposed mechanics*.
Both are valid. The 35-year mistake was OOP-defaulting when neither was justified.
---
## 5. Implications for Manual Slop
Concrete applications of the 4-source synthesis, ordered by implementation cost.
### 5.1 Low-cost, high-value (could be done in an afternoon)
**Apply nil-sentinel pattern to `SearchTree`-style chains in the codebase.** Look for nested `if entity: if entity has X: if entity has Y:` patterns. Each nesting is N effective codepaths. The fix is usually a single class-level invariant: "entity is always valid; if not, here's the null entity." This applies to:
- Discussion entry iteration (the per-entry renderer in `gui_2.py:3770` already uses `entry in app.disc_entries` checks before `disc_entries.remove(entry)` — could be tightened with a sentinel)
- Context file aggregation (`aggregate.py:142 build_file_items` — does it ever need to ask "is this a real file or a sentinel?")
**Add generational handles to the `TrackDAG` and `Ticket` system.** The MMA workers hold ticket references across the lifecycle of a track. If a ticket is *removed* (status change, replacement, merge), the worker should not be able to act on a stale reference. Currently this is implicit (the worker's loop just re-reads the ticket each turn). Making it explicit (handle + generation) is a small refactor with high robustness benefit.
**Audit the `MCPController` dispatch (per the `mcp_architecture_refactor` track) for nil-sentinel opportunity.** When a tool is not found, the controller returns `Result(data="", errors=[ErrorInfo(NOT_FOUND, ...)])`. This is a 2-codepath system: "is the tool there?" + "execute the tool." The user code at the call site is forced to check `result.ok` for every call. Could the result type be improved so that *most* call sites are a single straight-line codepath?
### 5.2 Medium-cost, high-value (a track's worth of work)
**Replace `realloc`-style growable buffers with Xar-like chunked arrays for chat history, log buffers, and the comms log.** Per Reece's talk, this eliminates the spiky latency of reallocation+copy and gives pointer stability. The `SummaryCache` in `src/file_cache.py` and the `LogRegistry` in `src/log_registry.py` are obvious candidates.
**Refactor MMA ticket storage toward an ECS shape.** Tickets are currently dicts (per `metadata.json` Ticket schema). If you decompose them into components (Status, Priority, CommitSHA, BlockedBy, Description) and operate on them via systems (DAGSystem, ExecutionSystem, WorkerPoolSystem), the architecture becomes Muratori-style ECS. This is a *data-migration* of the existing ticket model — no new code, but a structural shift in how tickets are stored and accessed.
**Apply immediate-mode patterns to the Hook API.** Per the codepath-combinatoric-explosion article, retained-mode APIs (caller manages the lifecycle) are codepath amplifiers; immediate-mode APIs (subscriber gets events) are codepath deflators. The current `POST /api/session` is retained-mode (caller sends the full session state). An immediate-mode alternative would be `WS /api/session_events` (subscriber receives a stream of session mutations). The caller doesn't manage the state; they just observe it. This collapses several test scenarios (the test just subscribes and watches).
### 5.3 Higher-cost, transformative (would reshape the project)
**Adopt the "assume as much as possible" principle as a code_styleguides entry.** This is the meta-change: add a `conductor/code_styleguides/assume_as_much_as_possible.md` that documents the principle, lists the existing places where Manual Slop already applies it (the `CommsLogCallback` is essentially immediate-mode; the `ContextPreset` is an assumption about which files are in scope; the `RunSubagentSummarization` is a single-function API that assumes a specific summarization contract), and gives the Tier 3 worker a checklist to apply when designing new structures.
**Build a "codepath surface" metric for the codebase.** A script that takes a function and returns: number of real codepaths, number of effective codepaths (after the function's nil-sentinel / immediate-mode / generational-handle defusing is accounted for), and a "codepath density" (codepaths per line of code). This would be the *measure* that tells you which functions are the highest-value refactor targets. Inspired by Fleury's "predictive power" framing: the goal is to *quantify* the combinatorial explosion, not just describe it.
---
## 6. The meta-skill: sketching in SSDL
The 6 primitives + 7 modifiers are enough to sketch any computational shape. The convention:
1. **Top to bottom is time** (instructions happen in order, top first).
2. **`[B]` branches fan out, `[M]` merges reconverge** (control flow).
3. **`[N]` collapses a branch** (the branch exists in the subsystem but not in the user's codepath).
4. **`o==>` means "this is the main loop, it repeats forever"** (codecycle).
5. **`===>W===>` means "this codepath causes parallelism"** (wide).
6. **A subsystem that returns a value valid in all cases** is a black box that the user never has to inspect.
When sketching a feature, *start* with the user's codepath. If it has branches, the question is: "where does the branch live, in user code or in a subsystem?" If the answer is "in a subsystem," sketch the subsystem separately. If the answer is "in user code," *reconsider* — is there a way to push it into a subsystem?
This is the *practice* of computational shapes thinking. It's not a rule; it's a habit. The skill develops over time as you sketch more designs and see which ones are simpler, more testable, more debuggable, and more amenable to incremental change.
---
## 7. References
- **Casey Muratori, "The Big OOPs: Anatomy of a Thirty-Five-Year Mistake"** — BSC 2025 talk. [https://youtu.be/wo84LFzx5nI](https://youtu.be/wo84LFzx5nI). Transcript unavailable; analyzed via Casey's own notes, an AI-generated timestamped summary, and the Lobsters/HN comment threads (138 commenter-eyes across 138 comments). The historical indictment of the OOP compile-time-domain-hierarchy pattern; the Looking Glass Thief ECS origin story.
- **Andrew Reece, "Assuming as Much as Possible... But No More"** — BSC 2025 talk. [https://www.youtube.com/watch?v=i-h95QIGchY](https://www.youtube.com/watch?v=i-h95QIGchY). Transcript unavailable; analyzed via Reece's own blog post (azmr.uk/bsc25/), a Medium article, and the WhiteBox documentation. The Xar data structure, the "byte-first thinking" principle, the aggressive-assumption technique.
- **Ryan Fleury, "A Taxonomy of Computation Shapes"** — Feb 17 2023, Digital Grove newsletter. [https://www.dgtlgrove.com/p/a-taxonomy-of-computation-shapes](https://www.dgtlgrove.com/p/a-taxonomy-of-computation-shapes). The 6-shape vocabulary: instruction, codepath, wide codepath, codecycle, wide codecycle, codecycle graph. The mental model for thinking about computation as data flow.
- **Ryan Fleury, "The Codepath Combinatoric Explosion"** — Apr 12 2023, Digital Grove newsletter. [https://www.dgtlgrove.com/p/the-codepath-combinatoric-explosion](https://www.dgtlgrove.com/p/the-codepath-combinatoric-explosion). The "effective codepath" concept (collapse N real codepaths into 1 effective codepath via invariants), the nil-sentinel pattern, the generational handle pattern, the retained-mode vs immediate-mode dichotomy, the ValFromKey / TextureFromKey examples.
- **Ryan Fleury, "Data-Oriented Design and Avoiding OOP"** (referenced in the discussion thread) — the "if you're writing a particle system, stop thinking about particles" formulation that grounds all of the above in a concrete anti-OOP heuristic.
- **Casey Muratori's Handmade Hero / Data-Oriented Design talks** — the broader context; the SSDL digest is a digest of these ideas as formalized by Fleury.
- **Mike Acton, "Data-Oriented Design and C++" (cppCon 2014)** — the foundational DOD talk; Reece's "know your data" principle is a direct descendant.
- **Ryan Fleury, "Error Codes are Data" / "The Easiest Way To Handle Errors..."** (with R. Fleury credits) — the Result/ErrorInfo data shape is itself a computational-shapes defusing technique (errors as a side-channel list rather than a tagged union or control-flow exception).
---
*End of digest. Pick this up when you want to ideate on a feature's shape; the SSDL vocabulary + the defusing techniques + the 4-source synthesis is enough to ground a design conversation in this material.*