Extending the storage overlay from antiparticle-only to full operator-based composition. Interceptors, pokes, and any corrective edit use the same merge operators that crossings uses for its collapse. The overlay reads all records at an address and composes them: seed + operations = current state.
All data is either a map (k/v) or a stream (ordered sequence). Every mutation is an operation record at the same to_addr as the seed. The overlay reads all records, identifies the seed, applies operations in at order, and returns the composed result.
seed record (the original crossing)
+ operation 1 (splice by interceptor A)
+ operation 2 (snip by interceptor B)
+ operation 3 (append by trigger C)
- antiparticle (cancels operation 1)
= current state
Same address, different type_addr values. The seed is any non-operator record. Operations have type_addr values like :types:splice, :types:snip, :types:poke. Antiparticles cancel any record.
Inherited from the crossings operator system, adapted for the storage overlay:
| Operation | type_addr | Payload | Effect |
|---|---|---|---|
$set` | `:types:poke` | `{ path: "result.shape", value: "star", op: "$set" } |
Replace value at path | ||
$merge` | `:types:poke` | `{ path: "result", value: { extra: true }, op: "$merge" } |
Deep merge at path | ||
$inc` | `:types:poke` | `{ path: "result.score", value: 1, op: "$inc" } |
Increment counter at path | ||
$min` / `$max |
:types:poke |
{ path: "result.best", value: 5, op: "$min" } |
Keep min/max |
snip |
:types:snip |
{ path: "result.temporary" } |
Remove key at path |
| Operation | type_addr | Payload | Effect |
|---|---|---|---|
$append` | `:types:poke` | `{ path: "result.items", value: ["new"], op: "$append" } |
Append to array | ||
$prepend` | `:types:poke` | `{ path: "result.items", value: ["first"], op: "$prepend" } |
Prepend to array | ||
$union` | `:types:poke` | `{ path: "result.tags", value: ["new"], op: "$union" } |
Set union | ||
$intersection` | `:types:poke` | `{ path: "result.allowed", value: ["a","b"], op: "$intersection" } |
Set intersection | ||
splice |
:types:splice |
{ path: "stream", index: 3, delete: 1, insert: [token] } |
Array splice at index |
| Operation | type_addr | Payload | Effect |
|---|---|---|---|
| antiparticle | :types:antiparticle |
{ cancels: { at: "...", type: "..." } } |
Cancel a record |
| annotate | :types:annotate |
{ path: "_meta.warnings", value: [...], op: "$append" } |
Add metadata (additive only) |
Each operation is a full crossing record — to_addr, from_addr, type_addr, payload, at, sig. The from_addr is the interceptor/boundary that wrote it. The sig proves who made the edit. The trace links it to the crossing that triggered it.
# Seed: original boundary crossing
to_addr: ":games:abc123:runs:001:vine_right:A2"
from_addr: ":boundaries:vine_right"
type_addr: ":types:tick"
payload: { shape: "circle", color: "red" }
at: "2026-04-09T10:00:00Z"
sig: "aaa"
# Operation: interceptor annotates with timing
to_addr: ":games:abc123:runs:001:vine_right:A2"
from_addr: ":interceptors:request_timer"
type_addr: ":types:poke"
payload:
op: "$merge"
path: "_meta"
value: { elapsed_ms: 3, measured_by: "request_timer" }
at: "2026-04-09T10:00:00.003Z"
sig: "bbb"
trace: "aaa"
# Operation: interceptor flags a warning
to_addr: ":games:abc123:runs:001:vine_right:A2"
from_addr: ":interceptors:result_validator"
type_addr: ":types:poke"
payload:
op: "$append"
path: "_warnings"
value: ["shape value not in expected set"]
at: "2026-04-09T10:00:00.005Z"
sig: "ccc"
trace: "aaa"
The overlay method becomes:
def overlay(addr)
records = at(addr)
records = apply_antiparticles(records)
seed, operations = partition_seed_and_ops(records)
return seed if operations.empty?
apply_operations(seed, operations)
end
def partition_seed_and_ops(records)
ops = records.select { |r| operator_type?(r["type_addr"]) }
seeds = records - ops
# Seed is the most recent non-operation record
seed = seeds.last
[seed, ops.sort_by { |o| o["at"] }]
end
def apply_operations(seed, operations)
result = deep_dup(seed)
operations.each do |op|
payload = op["payload"]
path = payload["path"]
operator = payload["op"]
value = payload["value"]
case op["type_addr"]
when ":types:poke"
apply_merge_op(result, path, operator, value)
when ":types:snip"
apply_snip(result, path)
when ":types:splice"
apply_splice(result, path, payload)
when ":types:annotate"
apply_merge_op(result, path, "$append", value)
end
end
result
end
The operators inherited from crossings have well-defined CRDT semantics:
| Operator | CRDT Model | Commutativity |
|---|---|---|
$set |
LWW Register | No (order matters, at resolves) |
$append |
Grow-only sequence | Yes (both appends produce same result regardless of order) |
$union |
G-Set | Yes (set union is commutative) |
$inc |
G-Counter | Yes (increment is commutative) |
$merge |
OR-Map | Partially (key-level, not value-level) |
$min` / `$max |
Min/Max register | Yes |
splice |
Positional edit | No (index-dependent, requires OT for concurrent edits) |
For map data (k/v), most operations are commutative — two interceptors writing to different paths produce the same result regardless of order. For stream data (positional edits), order matters — splice at index 3 means different things if another splice shifted the indices. This is where OT semantics apply.
Because operations are positional edits on an immutable seed, multiple actors can write operations concurrently:
$append: commutative, both annotations appearThe multiplayer semantics come from the CRDT/OT properties of the operators, not from any locking mechanism. The append-only store guarantees that all operations are visible to all readers. The overlay composes them deterministically.
The remark bridge already implements this model for markdown documents:
:streams:my-doc$append`, `$set operatorscreated_at order = current documentThe wanderland-core overlay generalizes this to all data in the store. Documents, crossings, game state, interceptor edits — all composed the same way. The operator vocabulary is shared. The overlay method is the universal read.
SQLiteDriver#overlay to partition seed/ops and apply operationstype_addr constants for :types:poke, :types:snip, :types:splice, :types:annotate