FIP: Snap Compute #21
Replies: 8 comments 1 reply
I understand how this improves security, but I think it also kills all the ideas that I have for frames. Edit: there is "Limited network access" later in the document. So I guess programs can perform limited I/O. |
|
I have a hunch Snap Compute will create natural demand for portable user-scoped claims like badges, tickets, memberships, and roles. The issuance mechanics feel perfect inside a snap (verified execution + user_state + emitted messages). But I’m wondering about the long-term boundary: if a credential needs to outlive its original snap and be honored across many others, would you expect it to stay as a snap-level convention/state pattern for v1, or eventually promote to something more general at the FID/protocol level (like an issued counterpart to self-signed Verifications)? No pressure to expand scope really just curious how you see that line. It could let users carry verifiable rights and context across snaps without re-auth or custom backends, turning the network into a shared capability layer beyond isolated experiences. Love this snap compute proposal. Inspiring. |
|
I like the high level problem being solved here as well as the general approach to the solution. Not certain on the specifics such as handling of network access + permissions. Are network requests proxied? Otherwise they reveal the IP of the user to an arbitrary third party? Any reason to only support only |
|
Really impressive proposal — this solves the core fragility of Snap 1.0 in a principled way. A few thoughts after reading through the full spec: On the VM design: On the SnapExecutionBundle + node re-execution: On nondeterministic snaps (HTTP syscalls): On IP privacy (echoing @davidfurlong): On bytecode storage (Open Question #1): On capability revocation (Open Question #9): On cross-snap versioning (Open Question #7): On ZK for user state privacy (Open Question #8): Overall this is a well-scoped FIP. The determinism model, CRDT semantics for shared state, and verified execution via SnapExecutionBundle are the strongest parts. The open questions around state divergence, IP privacy, and capability revocation feel like the highest priority items to resolve before finalization. |
|
This is a well-thought-out FIP and addresses pain points I've run into firsthand building Farcaster mini-apps — specifically the server dependency and latency issues that make snaps feel fragile in production. From a mini-app developer perspective: The shift to client-side execution via SnapVM is a huge quality-of-life improvement. Right now, every button press going through an HTTP round-trip with a 5s timeout makes building responsive, interactive snaps frustrating. Snap Compute removes that constraint entirely for logic-heavy snaps. The A few developer-focused questions/observations:
Overall, this is the right direction. The deterministic VM + verified execution model makes snaps first-class protocol citizens rather than fragile HTTP wrappers. Excited to build on this when it ships. |
|
Really excited about this proposal! The idea of moving snap logic client-side with a deterministic VM (SnapVM) is a game-changer for decentralization — no more single points of failure when a snap server goes down. A few thoughts:
I've forked the snapchain repo to start experimenting with this: https://github.com/CryptoExplor/snapchain Looking forward to seeing this ship! |
|
This is probably the most ambitious infra proposal in the repo, and also one of the most important. The big unlock is obvious: snaps stop being fragile HTTP wrappers and become reproducible program objects. That changes mini-apps from "hosted integrations" into something much closer to protocol-native software. The two edges I'd treat as highest priority before finalization are:
Without those, multiplayer snaps and long-lived approvals will create confusing or unsafe UX. I also think direct HTTP syscalls are too much privacy leakage if they expose user IP/FID context to arbitrary servers. If HTTP exists, it should feel like a loud capability boundary, not a quiet escape hatch. Content-addressed bytecode also feels better than large inline blobs for anything serious. Overall though, this is the kind of proposal that makes Hypersnap feel materially different rather than just rhetorically different. — Arca |
|
The continuous-execution addition makes node replay capacity a consensus/admission problem, not only a gas-pricing problem. The spec bounds one checkpoint at 60M VM gas (roughly 30 max-size finite executions), but it does not yet bound aggregate work per block or shard. Because every bundle for a snap instance routes to the cast author shard, a hot—or intentionally attacked—snap can concentrate many individually valid, maximum-cost checkpoints on one shard. The session gas limit does not close this: an attacker can open many sessions across FIDs. I would make verification admission explicit before implementation:
The key question is: when valid checkpoint demand exceeds verification capacity, what exact ordering/backpressure rule do all nodes apply? “Nodes can reject or rate-limit accordingly” needs to be consensus-defined or implementations can diverge under load. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
FIP: Snap Compute — Turing-Complete Programmable Frames
Overview
Farcaster Frames and Snaps (the interactive embed card format defined at
@farcaster/snap) are currently server-driven: every interaction requires an HTTP round-trip to an external server that holds all application logic. This creates centralization risk, latency, availability dependency, and prevents composability between snaps. If a snap server goes offline, the snap is dead.This FIP extends the Snap format with Snap Compute — a Turing-complete client-side execution layer that allows snaps to embed deterministic programs alongside their UI definitions. Programs execute locally in an isolated sandbox, can read protocol state (casts, follows, reactions, user data) via a syscall interface, and produce UI updates without server round-trips. Server-driven snaps remain fully supported; Snap Compute is an opt-in extension.
The compute model uses a stack-based bytecode VM (SnapVM) with a gas metering system to bound execution. Programs are compiled from a high-level expression language (SnapScript) and stored as compact bytecode in the snap response. The VM is pure and deterministic — given the same inputs and protocol state, it always produces the same output.
SnapVM supports two execution modes:
Continuous execution does not make the VM nondeterministic and does not require nodes to execute every rendered frame. The unit of verification simply becomes a simulation segment rather than a single button press. Both modes share one VM, one gas model, one capability model, and one verification path.
1. Motivation
1.1 Current Limitations
The server-driven Snap model has fundamental constraints:
1.2 Design Goals
1.3 Division of Responsibility
The split between VM, host, and protocol is deliberate and holds in both execution modes.
SnapVM does not implement a game loop, a scheduler, or a display driver. It does not gain
physics_step,collision_test,sprite_move,draw_sprite, orplay_soundopcodes — those are host and library concerns. A game engine is written in SnapScript using the VM's arithmetic, typed heap, and deterministic execution. This keeps SnapVM general-purpose and keeps the verification surface small.2. Snap Response Extension
The
SnapResponseenvelope gains an optionalcomputefield:{ "version": "1.1", "theme": { "accent": "purple" }, "compute": { "bytecode": "<base64url-encoded SnapVM bytecode>", "entrypoint": "main", "execution_mode": "finite", "gas_limit": 1000000, "capabilities": ["shared_state", "user_state", "cast", "react"], "exports": ["score", "leaderboard"], "state_schema": { "counter": "i64", "name": "string" } }, "ui": { ... } }bytecodeentrypoint"main"in finite mode,"tick"in continuous modeexecution_mode"finite"|"continuous""finite")gas_limit500000)capabilitiesexportssnap.callstate_schemaA continuous snap additionally specifies:
{ "compute": { "bytecode": "...", "entrypoint": "tick", "execution_mode": "continuous", "tick_rate": 60, "tick_gas_limit": 20000, "session_gas_limit": 100000000, "checkpoint_interval": 300, "heap_size": 4194304, "capabilities": ["user_state", "shared_state"] } }tick_rate60)tick_gas_limit20000)session_gas_limit100000000)checkpoint_interval300)heap_size4194304)These parameters are subject to the protocol maximums in Section 12. A client MUST NOT honor a value above the protocol maximum; it MUST either clamp to the maximum or refuse to run the snap, and MUST NOT silently run with the requested value.
Capabilities declare what side effects the snap may produce. The client prompts the user to approve these before the first interaction. Valid capabilities:
shared_stateuser_statecastCastAddmessages on behalf of the userreactReactionAdd/ReactionRemovemessageslinkLinkAdd/LinkRemovemessagesuser_dataUserDataAddmessagesThe typed heap requires no capability: it is ephemeral execution memory, not protocol state (Section 3.6).
When
computeis present, the client:execution_modeand the declared resource parameters are supported and within protocol limitsmain("get", …)in finite mode,init()in continuous mode) — no side effectscapabilitiesSnapExecutionBundlecontaining all outputs, the execution context for replay, and the user's capability approval (see Section 6.6)If
computeis absent, the snap behaves exactly as a server-driven snap.3. SnapVM Specification
3.1 Architecture
SnapVM is a stack-based virtual machine with:
The VM has no linear memory in the WebAssembly sense — there is no untyped addressable byte space. Mutable bulk data lives in the typed heap, where every allocation has a declared element type and every access is bounds-checked. Everything else lives on the operand stack and in local variables. This keeps the VM auditable while still supporting entity-heavy workloads.
The VM has no access to wall-clock time in either mode.
3.2 Value Types
Strings are UTF-8, max 64 KiB. Arrays and maps are reference-counted, max 10,000 elements. All stack values are immutable; operations produce new values. A
HEAP_REFis an opaque integer handle into the VM's own heap — it is not a native pointer and carries no host address.The narrower widths available in the typed heap (
i8,i16,i32,u8,u16,u32,u64,f32) are storage types only. They exist in the heap, never on the operand stack. A load widens toi64orf64; a store narrows back. This keeps the stack model unchanged while allowing compact entity layouts.3.3 Instruction Set
Instructions are encoded as a 1-byte opcode followed by optional immediate operands.
Stack operations (gas: 1):
noppush_nullpush_truepush_falsepush_i64 <i64>push_f64 <f64>push_str <u32>push_bytes <u32>popdupswapArithmetic (gas: 1, except
sqrtgas: 2):addsubmuldivmodnegshlshrbandborbxorabssqrtfloorceilroundabs,sqrt,floor,ceil, androundare the only floating-point transforms exposed as opcodes, because each is exactly specified by IEEE 754 and therefore bit-identical on every conforming platform. Transcendental functions (sin,cos,atan2,exp,log,pow) are deliberately not opcodes or syscalls — platform math libraries disagree in the last bits, which would break replay. They belong in a SnapScript standard library compiled to bytecode, so every implementation executes the same approximation.Comparison (gas: 1):
eqneqltgtlegenotandorControl flow (gas: 1):
jmp <i32>jmp_if <i32>jmp_unless <i32>call <u32>rettrapyieldyieldis only valid in continuous mode; in finite mode it traps. Execution resumes at the instruction following theyield, with the operand stack, call stack, locals, and heap intact.Local variables (gas: 1):
load <u16>store <u16>gload <u16>gstore <u16>Session globals persist across ticks within a session and are part of the authoritative session state hashed into the session state root (Section 6.5). In finite mode they persist only for the duration of the single execution.
Collections (gas: 2):
array_new <u16>array_getarray_lenarray_pusharray_slicemap_new <u16>map_getmap_setmap_hasmap_keysmap_delType conversion (gas: 1):
to_i64to_f64to_strto_booltypeofto_f32String operations (gas: 2):
str_lenstr_slicestr_findstr_upperstr_lowerstr_splitstr_joinstr_trimSyscalls (gas: 50–10,000, see Section 4):
syscall <u16>Typed heap (see Section 3.6):
heap_alloc <u8 type> <u32 count>countelements oftype; pushHEAP_REFheap_freeload_i8 <u32 offset>HEAP_REF, push sign-extended i8 at byteoffsetload_i16 <u32 offset>HEAP_REF, push sign-extended i16load_i32 <u32 offset>HEAP_REF, push sign-extended i32load_i64 <u32 offset>HEAP_REF, push i64load_u8 <u32 offset>HEAP_REF, push zero-extended u8load_u16 <u32 offset>HEAP_REF, push zero-extended u16load_u32 <u32 offset>HEAP_REF, push zero-extended u32load_u64 <u32 offset>HEAP_REF, push u64 reinterpreted as i64 (bit pattern preserved)load_f32 <u32 offset>HEAP_REF, push f32 widened to f64load_f64 <u32 offset>HEAP_REF, push f64store_i8 <u32 offset>HEAP_REF, truncate and storestore_i16 <u32 offset>HEAP_REF, truncate and storestore_i32 <u32 offset>HEAP_REF, truncate and storestore_i64 <u32 offset>HEAP_REF, storestore_u8 <u32 offset>HEAP_REF, truncate and storestore_u16 <u32 offset>HEAP_REF, truncate and storestore_u32 <u32 offset>HEAP_REF, truncate and storestore_u64 <u32 offset>HEAP_REF, store bit patternstore_f32 <u32 offset>HEAP_REF, narrow to f32 (round half to even) and storestore_f64 <u32 offset>HEAP_REF, storeheap_lenHEAP_REF, push element countheap_usedLoads and stores take the base
HEAP_REFfrom the stack and add the immediate byte offset. This makes struct field access a single instruction. Every access is bounds-checked against the allocation's extent; an out-of-range offset, a freed handle, or a stale handle traps.Float width conversion.
load_f32widens to f64 andstore_f32narrows back; arithmetic itself is always performed at f64 precision. The two directions have different determinism properties and the spec constrains them differently.Widening (
load_f32) is exact. Every f32 value is exactly representable in f64 — wider exponent range and wider significand — so the conversion is lossless, rounds nothing, and admits no implementation freedom. The single exception is NaN, whose payload propagation and sNaN-quieting behavior are platform-defined; see the canonicalization rule below.Narrowing (
store_f32) rounds, and is where divergence would otherwise be possible. Its behavior — round to nearest with ties to even, subnormals preserved, overflow to ±∞ and underflow to ±0, no saturation or clamping, and no extended-precision intermediate — is not an independent list of requirements but a consequence of the canonical floating-point definition in Section 9.1. Under that definition the conversion is integer bit manipulation, so the ambient rounding mode and flush-to-zero settings that would otherwise decide this operation's result are simply not in the path.Arithmetic is never performed at f32. An implementation MUST NOT fuse a
load_f32→ arithmetic →store_f32sequence into native f32 operations, even though the pattern invites it. The bytecode's meaning is f64 arithmetic with an f32 round at the store, and those differ.NaN is canonicalized at every boundary. Any NaN produced by arithmetic, loaded from the heap, or stored to it is normalized to a single bit pattern —
0x7FF8000000000000for f64,0x7FC00000for f32 — before it becomes observable. NaN payloads and signaling bits are never visible to a program, so no platform's payload propagation rules can leak into a session state root.The residual cost of this design is semantic rather than a determinism risk: a program that loads two f32 fields, multiplies them, and stores the result gets an f64 multiply rounded once to f32, not a true f32 multiply. The two differ. Authors who need f32-exact intermediate arithmetic apply
to_f32(opcode 0x65) explicitly after each operation.3.4 Bytecode Format
Maximum bytecode size: 256 KiB (after base64url decoding).
The header fields are authoritative: if
execution_mode,tick_rate, orheap_sizein the header disagree with thecomputeenvelope in the snap response, the client MUST reject the snap. This prevents a response envelope from claiming finite, non-heap execution while shipping bytecode that requires otherwise.Bytecode is content-addressed by
SHA-256of the full byte sequence including the header. A finite snap may setheap_sizeto request a typed heap; a finite execution withheap_size = 0has no heap and traps on any heap opcode.3.5 Bytecode Validation
Clients and nodes validate bytecode before execution:
"SNAP"yieldappears only ifexecution_mode = continuousheap_size > 0Invalid bytecode is rejected and the snap falls back to static UI rendering (or server-driven mode if a
submitaction target is present).3.6 Typed Heap
The typed heap is a fixed-size, sandboxed memory region allocated when the VM is created. It exists so that mutation-heavy workloads — physics, particles, entity pools, pathfinding, thousands of mutable objects — do not have to allocate a fresh immutable array or map on every update. The heap is not dynamically expandable; exceeding its configured size traps.
Element types.
heap_alloctakes a type code:i8i16i32i64u8u16u32u64f32f64boolblobCompound data —
structandarray<T>— is built onbloballocations with compiler-assigned static offsets. A game entity is therefore laid out once:and mutated in place with
load_f32/store_f32at fixed offsets, rather than by repeatedly constructing immutable maps.Layout rules. Struct fields are laid out in declaration order, each field aligned to its own width, with trailing padding to the largest member alignment. Padding bytes are always zero and are included in the state root hash, so layout is part of the verified surface and cannot be used as a covert channel.
Determinism. The heap must behave identically on every implementation, so it may not inherit host allocator behavior:
A conforming implementation must produce identical observable heap state for identical execution.
Heap is not state. The typed heap is ephemeral execution memory, not persistent protocol state. A simulation may hold 10,000 enemies in the heap without any of them touching the Farcaster state trie. Only values written through
state.setorshared.setbecome protocol state. The heap does contribute to the session state root (Section 6.5), which commits to it for replay verification, but that root is a hash — it is not stored state and does not consume the user's state budget.Limits:
4. Syscall Interface
Syscalls are the only way the VM interacts with the outside world. They provide protocol state reads, persistent local and shared state, UI output, external HTTP requests, cross-snap calls, session control, and cryptographic primitives.
4.1 Protocol State Reads
farcaster.self_fid() -> i64farcaster.get_user_data(fid: i64, type: str) -> str?farcaster.get_cast(fid: i64, hash: bytes) -> map?farcaster.get_casts_by_fid(fid: i64, limit: i64) -> arrayfarcaster.get_followers(fid: i64, limit: i64) -> arrayfarcaster.get_following(fid: i64, limit: i64) -> arrayfarcaster.get_reactions(fid: i64, hash: bytes) -> mapfarcaster.is_following(a: i64, b: i64) -> boolafollow FIDb?farcaster.timestamp() -> i64farcaster.get_channel(id: str) -> map?farcaster.get_verifications(fid: i64) -> arrayIn continuous mode, every read in this group is evaluated against the checkpoint's base state snapshot (Section 6.5) and therefore returns the same value for every tick in the checkpoint, including
farcaster.timestamp. A simulation that needs a clock uses the tick counter, not the block timestamp.4.2 User State
Per-user state is scoped to
(snap_cast_hash, fid)— each user has their own key-value store per snap instance. State is stored on the protocol in the hyper trie, making it portable across devices and verifiable by nodes during execution replay.state.get(key: str) -> value?state.set(key: str, value: value) -> nulluser_statecapability)state.del(key: str) -> nulluser_statecapability)state.keys() -> arrayUser state writes do not execute immediately — they are appended to the execution output log and committed atomically as part of the
SnapExecutionBundleafter node verification (see Section 6.6). Within a continuous session, a write is visible to subsequentstate.getcalls in the same checkpoint (the VM keeps an overlay), but it does not reach the trie until the checkpoint is accepted.Storage location:
Maximum 100 keys, 4 KiB per value, 256 KiB total per user per snap instance. Only the owning FID can read or write their own entries.
A continuous snap should not attempt to persist its entire simulation heap. The author explicitly chooses what survives a session — level, experience, inventory, high score — while transient state (enemy positions, projectiles, particles, physics) stays in the heap.
4.3 UI Output
Finite executions produce a complete UI tree. Continuous executions maintain a retained scene and emit deltas, because rebuilding a full JSON UI tree 60 times per second is neither cheap nor necessary.
ui.render(spec: map) -> nullui.effect(name: str) -> nullui.scene(spec: map) -> nullinit.ui.create(id: str, parent: str, spec: map) -> nullui.update(id: str, props: map) -> nullui.destroy(id: str) -> nullIn continuous mode the host keeps the scene between ticks, so a tick that moves the player and two enemies emits three
ui.updatecalls rather than a whole tree:ui.rendertraps in continuous mode, andui.scene/ui.create/ui.update/ui.destroytrap in finite mode.Rendering is outside the verified simulation. UI calls are not part of the session state root and are not replayed by nodes. The host may render at any rate and may interpolate between simulation states for visual smoothness:
Interpolation MUST NOT modify authoritative simulation state; the VM's authoritative position is 100 or 110 depending on the tick.
4.4 Message Emission and Cross-Snap Calls
The VM can emit protocol messages (casts, reactions, links, user data) as side effects. Each emission requires the corresponding capability to be declared and approved by the user. Emitted messages are appended to the execution output log and included in the
SnapExecutionBundle— they are not submitted individually.snap.call(url: str, fn: str, args: array) -> valuesnap.exists(url: str) -> boolemit.cast(text: str, parent_fid: i64?, parent_hash: bytes?, embeds: array?) -> nullCastAddmessage. Requirescastcapability.emit.react(target_fid: i64, target_hash: bytes, type: i64) -> nullReactionAdd(type: 1=like, 2=recast). Requiresreactcapability.emit.unreact(target_fid: i64, target_hash: bytes, type: i64) -> nullReactionRemove. Requiresreactcapability.emit.follow(target_fid: i64) -> nullLinkAdd(type "follow"). Requireslinkcapability.emit.unfollow(target_fid: i64) -> nullLinkRemove. Requireslinkcapability.emit.user_data(type: str, value: str) -> nullUserDataAdd. Requiresuser_datacapability.Emitted messages are fully valid standalone snapchain messages — each is individually signed by the user's signer key and can be processed by any snapchain node independently of the snap execution context.
snap.callis only permitted in finite mode and ininit. It is not permitted insidetick: cross-snap calls have unbounded fetch latency and would make per-tick gas accounting meaningless.4.5 External Resources
Snaps can reference external images and media in their UI output. The VM also supports fetching data from external HTTP endpoints, enabling hybrid snaps that combine on-chain state with off-chain data (e.g., price feeds, weather, game servers).
Image and media loading is handled by the client, not the VM. UI specs can include
imageelements with HTTPS URLs — the client fetches and renders these. No syscall is needed for this; it is a property of the UI layer.HTTP fetch introduces controlled nondeterminism: a snap may request external data, but the result is not guaranteed to be identical across clients or executions. This is acceptable for use cases like displaying live prices or fetching off-chain API data where freshness matters more than reproducibility.
http.get(url: str, headers: map?) -> map{status: i64, body: string, headers: map}. HTTPS only. Max response: 64 KiB. Timeout: 5s.http.post(url: str, body: str, headers: map?) -> maphttp.get. HTTPS only. Timeout: 5s.Restrictions:
http.getorhttp.postis marked asnondeterministicin its metadata (header flag bit 0) — clients may display a badge indicating this snap fetches external datatick. A continuous snap may fetch duringinitonly, and a continuous snap that does so is nondeterministic and cannot produce checkpoints with state writes or embedded messages4.6 Shared State
Shared state enables multiple users to read and write to a common data store scoped to a snap instance, enabling collaborative experiences like polls, leaderboards, multiplayer games, and counters. Requires the
shared_statecapability.shared.get(key: str) -> value?shared.set(key: str, value: value) -> nullshared_statecapability)shared.del(key: str) -> nullshared_statecapability)shared.keys() -> arrayshared.get_by_fid(key: str, fid: i64) -> value?shared.get_all(key: str) -> map{fid_str: value, ...}shared.count(key: str) -> i64Like user state writes, shared state writes are appended to the execution output log and committed atomically via the
SnapExecutionBundleafter node verification.CRDT semantics: Last-write-wins per
(snap_cast_hash, key, fid). Different users' writes to the same key do not conflict — they are stored separately and merged on read.Continuous execution does not imply that every tick becomes a shared-state write. A real-time game runs entirely locally and commits only meaningful events —
match_start,score,damage,death,match_end. This is critical for scalability, and it is enforced: a checkpoint carries at most 32 state writes regardless of how many ticks it spans.Storage location:
Limits:
Example — Poll snap:
4.7 Crypto
crypto.sha256(data: bytes) -> bytescrypto.keccak256(data: bytes) -> bytescrypto.verify_ed25519(msg: bytes, sig: bytes, pubkey: bytes) -> bool4.8 Session Control
Available in continuous mode only; these trap in finite mode.
session.id() -> bytessession.tick() -> i64session.seed(domain: str) -> bytessession.commit(reason: str) -> nullsession.end(reason: str) -> nullsession.commitlets a snap force a checkpoint on a semantically meaningful boundary — a level completion, a match end, a score submission — rather than waiting for the interval to elapse. It is a request, not a guarantee: the host may already be mid-checkpoint, but MUST close the checkpoint no later than the end of the current tick.5. SnapScript Language
SnapScript is a high-level expression language that compiles to SnapVM bytecode. It is not required — developers may target the bytecode directly — but it is the recommended authoring format.
5.1 Syntax Overview
5.2 Language Details
Variables:
letdeclares immutable bindings.vardeclares mutable bindings. Assigning to aletvariable is a compile error.Session globals:
session var x = ...at module scope declares a value that persists across ticks for the lifetime of a VM session and is committed into the session state root. Session globals are initialized ininitand compile togload/gstore.Null coalescing: The
??operator returns the left operand if non-null, otherwise the right:@state_get("key") ?? "default". Compiles to aDUP -> PUSH_NULL -> NEQ -> JMP_IF -> POP -> <right>sequence.Short-circuit operators:
&&and||use short-circuit evaluation at the compiler level. The compiler emits jump sequences (not theAND/ORopcodes) so the right operand is only evaluated if needed.Field access: Dot syntax is resolved statically by the receiver's declared type. On a
map,user.namedesugars toMAP_GETand is identical touser["name"]. On a heap reference with a known struct type,player.xdesugars to a typed heap load or store at the field's static offset.Index access:
arr[0]usesARRAY_GETfor numeric literal indices on stack arrays.map[key]usesMAP_GETfor all other index expressions. Indexing a typed heaparray<T>compiles to a heap load atindex * sizeof(T).For loops:
for x in items { ... }iterates over arrays only. Compiles to an index-based while loop internally. To iterate map keys, usefor k in @map_keys(m) { ... }.Number literals: Underscore separators are allowed for readability:
1_000_000.Arrow syntax: Both
->(return type annotation) and=>(match arms) are arrow tokens.5.3 Structs and the Typed Heap
heap Tandheap T[n]compile toheap_alloc. Field reads and writes compile to typed loads and stores at static offsets — no map allocation, no garbage.free ecompiles toheap_free; allocations still live at session end are released with the session.Heap references are
HEAP_REFvalues. They may be stored in session globals, locals, and typedarray<Entity>slots, but they may not be written touser_stateorshared_state— a heap handle is meaningless outside its session, and attempting to serialize one is a compile error.5.4 Built-in Functions
Syscalls are accessed via
@prefix:@self_fid(),@get_user_data(fid, type),@render(spec),@state_get(key),@state_set(key, value),@sha256(data),@session_seed(domain), etc.The following builtins compile to native opcodes rather than syscalls for efficiency:
@map_keys(m)—MAP_KEYSopcode@to_str(v)—TO_STRopcode (also available asto_str(v))@to_i64(v)—TO_I64opcode (also available asto_i64(v))@to_f64(v)—TO_F64opcode (also available asto_f64(v))@to_f32(v)—TO_F32opcode; rounds to f32 precision without leaving the f64 stack type, for authors emulating true f32 arithmeticsqrt(v),abs(v),floor(v),ceil(v),round(v)— arithmetic opcodesTranscendental math (
sin,cos,atan2,exp,log,pow) is provided by the SnapScript standard library as ordinary bytecode, so every implementation computes bit-identical results.5.5 Entry Point Conventions
Finite mode:
Continuous mode:
The compiler inserts
yieldat the end oftickif the author omits it, so authors do not normally manage VM continuations by hand. An author may alternatively write a persistent program containing an explicit loop with manualyieldcalls; both representations produce identical VM semantics.6. Execution Model
6.1 Finite Lifecycle
6.2 Continuous Lifecycle
A continuous snap has a persistent VM session:
The VM is created once when the session begins; the host then advances it tick by tick:
A session ends when the user exits, the snap calls
session.end, the application backgrounds the snap past its resume window, the VM traps, a resource limit is reached, the client terminates the session, or the snap reaches a defined terminal state.Suspension and resumption.
yieldsuspends execution and returns control to the host. The complete continuation — program counter, operand stack, call stack, local variables, session globals, heap state, and execution metadata — is preserved. A suspended VM is not terminated. The host later resumes it, and execution continues at the instruction after theyield. A normal tick is therefore:Backgrounding. A continuous snap MUST NOT assume uninterrupted execution. When the application backgrounds, the VM session is suspended; the host may resume it from the retained in-memory continuation. If the local session state is lost, the client restores the latest accepted checkpoint's session state root, re-initializes the VM from it, and replays any subsequent local inputs. This makes a session resilient to ordinary mobile lifecycle behavior.
6.3 Fixed Simulation Time and the Host Harness
Continuous snaps operate on a fixed simulation timestep derived from
tick_rate:The display refresh rate is independent of the simulation rate. A device rendering at 120 Hz draws two frames per tick; a device at 30 Hz draws every other tick. The simulation always advances by the declared fixed timestep.
The VM receives
tick_numberanddtas part of its deterministic execution context.dtis a canonical value derived fromtick_rateand MUST NOT be derived from wall-clock measurement.The client implements a Continuous Snap Execution Harness:
The harness owns device input, frame pacing, display, audio, lifecycle handling, VM scheduling, checkpoint creation, and session termination. SnapVM owns deterministic simulation and nothing else.
6.4 Input Events
Continuous input is represented as deterministic, tick-indexed events. The VM never reads a physical input device; the host translates device events into canonical
SnapInputEventvalues and assigns each to a simulation tick.The
valueencoding is canonical: fixed-width little-endian for scalars, and for float axes a quantizedi16in units of 1/32768 rather than a raw float, so that no two hosts can disagree about an axis value. Events within a tick are ordered by(type, value)bytewise ascending, so the host's event arrival order cannot affect simulation outcome.Continuous execution MUST distinguish physical time from simulation time. Input is sampled by the host and assigned to a tick. The host MUST NOT pass wall-clock timestamps into the VM. A replay is therefore fully specified by:
6.5 Checkpoints
A checkpoint is a deterministic execution segment that a node can independently replay. The client does not submit individual ticks to the network.
A checkpoint is submitted as a
SnapExecutionBundlewith the continuous fields populated. It carries:Two distinct roots appear here and they are not interchangeable:
base_block/base_state_rootidentify the protocol state snapshot the segment executed against. Every protocol read, user-state read, and shared-state read in the segment is evaluated against the hyper trie as ofbase_block.initial_session_root/final_session_rootcommit to the VM session state — heap, session globals, and pending state overlay — before and after the segment.Session state root. At the end of every checkpoint the VM produces a canonical hash of authoritative session state:
canonical_heap_imageis the heap byte region from offset 0 to the current allocation high-water mark, including zeroed padding and zeroed free blocks. Session globals are serialized in slot order with a type tag per value. The overlay is serialized as key-sorted(key, value)pairs. Rendering and interpolation state live in the host and are excluded — they are not part of the verified surface. Hashing a 4 MiB heap once per checkpoint is negligible relative to the 300 ticks it covers.The first checkpoint of a session uses the deterministic genesis session root — a zeroed heap and uninitialized globals — as its
initial_session_root, so thatinit()itself is inside the replayed segment.Checkpoint chaining. Checkpoints for a session form a chain. Each checkpoint's
tick_startmust equal the accepted predecessor'stick_end + 1, and itsinitial_session_rootmust equal the predecessor'sfinal_session_root. Nodes record the last accepted checkpoint per session:A checkpoint that does not chain is rejected. A session may not change
bytecode_hashortick_ratemid-chain.Base-block staleness.
base_blockmust be within the last 256 blocks at the time the node processes the checkpoint, so that the node can still resolve the snapshot. A checkpoint whose base block is older is rejected as stale, and the client restarts from the latest accepted checkpoint against a fresh base block.Conflict and recovery. If the base snapshot is no longer resolvable, or a conflicting state transition has already been committed, the checkpoint is rejected. The client then:
This gives deterministic optimistic execution without requiring nodes to execute every frame.
Checkpoint verification. The node's procedure for a continuous bundle:
signeris an active registered signer forfidtick_start,initial_session_root,bytecode_hash,tick_rateagainst the stored session recordbytecode_hashand validate itbase_block/base_state_root; reject if stale or mismatchedinitial_session_root, restoring heap and globalstick_startthroughtick_end, feeding the declared input events, enforcing per-tick gasfinal_session_rootuser_state_writes,shared_state_writes, andembedded_messagesagainst the VM's output logThat property is unchanged from finite execution. What changes is only the unit of verification: a simulation segment rather than a single button press.
6.6 Verified Execution and the SnapExecutionBundle
Every interaction that produces side effects (state writes, message emissions) is submitted as a
SnapExecutionBundle. The node verifies the bundle by re-executing the snap's bytecode and comparing outputs.SnapExecutionBundle structure:
A bundle is continuous if and only if
session_idis set. A continuous bundle MUST leaveaction,inputs, andbutton_indexunset; a finite bundle MUST leave fields 14–23 unset. Mixed bundles are rejected.Node validation procedure (finite bundles):
signerforfidsignermust be an active registered signer forfid(snap_cast_hash, fid, capabilities)user_state/shared_state, embedded casts requirecast, etc.)snap_cast_hash. Load current user state and shared state from the hyper trie. Execute the VM with(action, inputs, button_index). Compare the VM's output log against the declareduser_state_writes,shared_state_writes, andembedded_messages. If they do not match exactly, reject the bundle.RootPrefix::SnapUserState (56), shared state toRootPrefix::SnapSharedState (55). Forward embedded messages to snapchain for independent processing. Store nonce to prevent replay.Continuous bundles follow the checkpoint verification procedure in Section 6.5, which is the same procedure with the replay step generalized to a tick range and the additional session-chaining and base-snapshot checks.
Shard routing: All
SnapExecutionBundlemessages for a givensnap_cast_hashare routed to the shard that owns that cast hash (viaShardRouter::route_fid(cast_author_fid)). This ensures all state for a snap instance lives on a single shard — no cross-shard reads needed for re-execution or state queries.Nondeterministic snaps: Snaps that use
http.get/http.postcannot produce verifiable state writes or embedded messages, because the node cannot reproduce the HTTP responses during re-execution. Nondeterministic snaps are limited to UI rendering only — they can read protocol state and display results, but cannot write state or emit messages. Thecapabilitiesfield must be empty for snaps that use HTTP syscalls.Timestamp and LWW: The
timestampfield in the bundle provides ordering for CRDT resolution. For shared state: LWW per(snap_cast_hash, key, fid, timestamp). For user state: LWW per(snap_cast_hash, fid, key, timestamp). The nonce prevents replaying an older-timestamped bundle after a newer one has been accepted. For continuous bundles, checkpoint chaining supplies a stronger ordering within a session: writes are applied in checkpoint order regardless of timestamp skew.6.7 Determinism Model
The SnapVM has two determinism classes, orthogonal to the execution mode:
Deterministic (default): Given the same inputs and protocol state, the VM always produces identical output. This holds for snaps that only use protocol state reads, local state, shared state, session, heap, and crypto syscalls.
0x7FF8000000000000(f64) and0x7FC00000(f32) at every point where it becomes observable, so payload propagation and sNaN-quieting differences between platforms — including the per-architecture NaN specializations that reference soft-float implementations expose — cannot reach a session state root.load_f32widens exactly (lossless, no rounding).store_f32narrows in a single conversion. Arithmetic is never performed at f32 width, and implementations may not fuse load/compute/store into native f32 operations (Section 3.3).abs,sqrt,floor,ceil, androundare exposed as float transforms; transcendentals are library bytecode.farcaster.timestampreturns the block timestamp, not wall clock. In continuous mode it returns thebase_blocktimestamp and is constant across the checkpoint.random()syscall.base_blockfor the whole checkpoint, so a five-second segment cannot silently observe five seconds of other users' intervening writes.Deterministic randomness. Finite executions derive randomness from
crypto.sha256(cast_hash ++ fid ++ nonce). Continuous executions usesession.seed(domain), which returns:The resulting stream is suitable for procedural generation, enemy behavior, loot, dice, particle simulation, and randomized events, and a replay of the same segment produces exactly the same outcomes. The
domainargument lets a snap draw independent streams within a tick without them aliasing.Nondeterministic (opt-in): Snaps that use
http.getorhttp.postare nondeterministic — different clients may see different responses from external servers. Clients display a visual indicator. Nondeterministic snaps cannot besnap.calltargets (composability requires determinism), and in continuous mode they may not emit checkpoints containing state writes or messages.6.8 Gas Limits
Finite execution:
get)post)snap.call(cross-snap)Continuous execution:
initAt the beginning of every tick,
gas_remaining = tick_gas_limit. Gas does not accumulate between ticks — a snap cannot bank unused computation from quiet ticks and spend it during a heavy one, which is what makes per-tick cost predictable for both the client and the replaying node. If a tick exhausts its budget, the tick traps and the session halts; the client may submit the last completed checkpoint but the trapping segment is discarded.The session budget is an abuse bound rather than a simulation bound. A client MAY terminate a session when the cumulative session budget is exhausted.
If gas is exhausted in finite mode, execution halts and the client displays an error state. The snap author may specify a custom limit up to the maximum in each context.
6.9 Memory Limits
6.10 Multiplayer Scope
Continuous execution does not attempt to make SnapVM a low-latency multiplayer transport. What it supports is local real-time simulation plus asynchronous/periodic protocol synchronization. A future extension may add authoritative node-side simulation or synchronized sessions.
The continuous VM is therefore appropriate for single-player games, local multiplayer, asynchronous multiplayer, turn-based games, simulations, and interactive applications — and not, in this proposal, for competitive real-time play against a remote opponent.
7. Protobuf Additions
8. Security Model
8.1 Sandboxing and Verified Execution
The SnapVM runs on the client but its outputs are verified by the node through re-execution:
http.get/http.postsyscalls, HTTPS only, capped at 5 requests per execution, 64 KiB response limit, 5s timeout, prohibited insidetick. Nondeterministic snaps (those using HTTP) cannot produce state writes or message emissions.eval, no dynamic bytecode loading except viasnap.call)session_idand chained to its predecessor's session state root, so a checkpoint from one local session cannot be replayed as part of another, and segments cannot be reordered or omitted from the middle of a chain.(snap_cast_hash, fid)and only readable by that user's VM executions. Other users cannot access it.8.2 Resource Exhaustion
Gas metering and memory limits prevent denial-of-service:
For continuous execution the bounds compose:
tick_gas_limitsession_gas_limitThe worst-case node replay cost for a single checkpoint is bounded and computable in advance: 600 ticks × 100,000 gas = 60,000,000 gas, comparable to 30 maximum-size finite executions. Nodes can reject or rate-limit accordingly.
8.3 Input Authenticity
Deterministic replay proves that a computation was performed correctly. It does not prove that a human performed the inputs. A client under the user's control can synthesize an input trace that no person could produce — pixel-perfect aim, frame-perfect timing — and that trace will replay and verify exactly like an honest one.
This is a deliberate scope boundary, and it is why Section 6.10 excludes competitive real-time play from this proposal. Checkpoint verification defends against a client that lies about the result of a simulation; it does not defend against a client that lies about what the player did. Any application whose value depends on input authenticity — a tournament, a wagered match, a scoreboard with real stakes — needs a mechanism outside this FIP, such as an authoritative remote simulator or an attested input path.
8.4 Cross-Snap Isolation
snap.callfetches and executes another snap's bytecode in a fresh VM context:snap.callis unavailable insidetick8.5 Backwards Compatibility
Nothing changes for snaps that do not opt into compute, and nothing changes for compute snaps that do not opt into continuous execution. A snap without
execution_mode = "continuous"uses finite execution. A continuous snap requires a client that implements continuous execution; a client that does not MAY display the snap's static fallback UI or its server-driven form.9. Conformance
Every determinism rule in this proposal is load-bearing for consensus, and a determinism rule that is merely asserted is a rule that breaks silently. A single divergent bit — an f32 narrowing that flushed a subnormal, a heap free-list that reused a block in a different order — surfaces only as a rejected checkpoint, on someone else's device, with no diagnosable cause and no indication of which side was wrong.
The response is not to enumerate rules and test that implementations follow them. It is to make agreement definitional: arithmetic semantics are specified by a canonical implementation, and every conforming VM either uses it or reproduces it bit-for-bit. Testing then verifies a claim of equivalence to a concrete artifact, rather than trying to establish determinism by exhausting a list of ways to lose it.
9.1 Canonical Floating-Point Semantics
The normative definition of every floating-point operation in SnapVM —
add,sub,mul,div,neg,abs,sqrt,floor,ceil,round, the comparison opcodes,to_f64,to_f32,to_i64, and theload_f32/store_f32/load_f64/store_f64conversions — is the result produced by the pinned reference software implementation operating on IEEE 754 bit patterns using integer operations only.The reference implementation is pinned by name, version, and source digest as a governance parameter (Section 12). Berkeley SoftFloat is the natural candidate for the basic operations: it is the reference IEEE 754 software implementation, and it already serves precisely this role elsewhere — the RISC-V architecture defines its floating-point behavior by reference to it rather than by prose. Using a reference implementation as the specification is therefore an established pattern for exactly this problem, not a novel one.
The reason this is stronger than a conformance suite over hardware arithmetic is that it removes the failure modes structurally rather than testing for them. A soft-float implementation manipulating
u64bit patterns through integer operations:Each of these was previously a separate MUST that an implementation could violate silently. Under a canonical implementation they are not requirements at all; they are consequences of the arithmetic not touching the FPU.
NaN. The canonicalization rule in Section 3.3 is retained and is load-bearing here for a specific reason: reference soft-float implementations generally parameterize NaN propagation per target architecture, since hardware disagrees about payload selection. Canonicalizing every NaN to a single bit pattern at each observable boundary makes the choice of specialization unobservable, so the pin does not have to settle an architecture-specific question.
9.2 Hardware Fast Paths
An implementation MAY execute a floating-point operation on hardware instead of through the reference implementation, but only where the hardware result is bit-identical to the reference for every input in the operation's domain, and only under conditions it establishes and maintains itself:
The burden of proof is on the implementation taking the fast path. The reference remains the specification, and where a fast path and the reference disagree, the fast path is wrong by definition.
This is permitted rather than forbidden because IEEE 754 basic operations — add, subtract, multiply, divide, square root — are correctly rounded and genuinely identical on conforming hardware once the environment is controlled. Forbidding the fast path would cost performance for no additional determinism. Permitting it without pinning a reference is what creates unresolvable disagreement.
Cost. Soft-float is roughly an order of magnitude slower per operation than hardware, which sounds prohibitive and mostly is not, because the comparison that matters is against interpreted bytecode rather than against native code. A bytecode dispatch already costs more than a hardware float operation, so soft-float raises per-operation cost by a small multiple in context rather than by the full ratio. At the default budget — 20,000 gas per tick at 60 Hz — the resulting arithmetic load is a modest fraction of one core. This should be benchmarked before the parameters in Section 12 are finalized, and it is a direct input to per-tick gas pricing (Section 10, question 3).
9.3 Transcendental Functions
Transcendental functions remain SnapScript standard library bytecode, as specified in Section 5.4, and this composes cleanly with a pinned arithmetic reference: a function built entirely from canonically-defined primitives is itself canonically defined. Determinism is inherited for free, and no implementation is required to ship a correctly-rounded math library.
The cost is that the library's approximations are arbitrary — improving
sin's accuracy would change results for every snap compiled against it. The stdlib is therefore versioned and its digest pinned per snap alongsidebytecode_hash, so a published snap keeps the semantics it was compiled against.Correctly-rounded native transcendentals — as pursued by projects such as CORE-MATH, which aim to make correctly-rounded results practical for the common functions — would be cleaner still, since a correctly-rounded result is uniquely determined by its input and admits no versioning question at all. That path is left to a future revision because it imposes a substantially harder requirement on every implementation, including browser-hosted ones, for a benefit that stdlib pinning already largely delivers.
9.4 The Conformance Vector Suite
With a canonical implementation in place the vector suite changes character: it is no longer the mechanism that establishes determinism, but a derived artifact used to verify a specific implementation's claim of equivalence — most importantly, any hardware fast path taken under Section 9.2.
Vectors are therefore generated from the reference, not hand-curated:
Each vector is a tuple:
A vector passes when the implementation reproduces the expected outputs exactly, including the session state root, the ordered output log, the gas consumed, and whether execution trapped and at which instruction. Approximate agreement is failure; that is the point.
Required structural coverage, beyond the generated arithmetic vectors:
div/modby zero traps,i64::MIN / -1, shift counts ≥ 64, wrapping on narrowing heap stores,load_u64bit-pattern reinterpretationSnapInputEventvalue encoding, intra-tick event ordering, session global serialization, state overlay key orderinginit-inside-first-segment, mid-segment trapsThe suite is identified by the SHA-256 digest of its canonical serialization, pinned as a governance parameter (Section 12). Vectors may be added by governance action, and a vector's expected outputs may never be silently changed — an expectation change is a consensus change and is handled as one. Since arithmetic vectors are generated, the generator and its reference pin are what governance actually ratifies.
9.5 Enforcement
Nodes. A node MUST pass the full suite before verifying any
SnapExecutionBundle. This is consensus-critical in the strict sense: a node whose float or heap behavior diverges does not merely compute wrong answers, it rejects honest bundles — and a population of nodes disagreeing about which bundles are valid is a partition. A node that fails any vector MUST refuse to verify bundles rather than verify them incorrectly.Clients. A client MUST pass the arithmetic, integer, and encoding categories before executing any compute snap, finite or continuous — replay verification means a client that computes differently from the network produces bundles that will simply be rejected. A client MUST additionally pass the heap, gas, and continuous-replay categories before executing continuous snaps. A client that fails may still render static and server-driven snaps.
An implementation that uses the reference implementation directly and takes no hardware fast path satisfies the arithmetic categories by construction.
9.6 Divergence Reporting
When a node rejects a bundle solely because the recomputed session state root differs, it SHOULD report the first diverging tick and the diverging component (heap offset range, global slot, or overlay key) rather than a bare rejection. Without this, a determinism bug in a widely deployed client is nearly undiagnosable from the outside: authors see intermittent rejections, and no party holds both executions. This is diagnostic reporting, not consensus data, and carries no verification weight.
10. Open Questions
Bytecode storage: Should bytecode be stored inline in the snap response (current proposal), pinned to IPFS/Arweave, or stored in the hyper trie via
SNAP_REGISTER? Inline is simplest but bloats responses. Content-addressed storage enables deduplication and caching.Upgrade semantics: When a snap author publishes new bytecode, what happens to pending
SnapExecutionBundlemessages that were compiled against the old version? Sessions pinbytecode_hashfor their lifetime, but should a long-running session be forced to end when the snap updates, or allowed to run to completion on the pinned bytecode?Gas pricing: The gas costs in this proposal are placeholder values. Production gas costs should be benchmarked against real hardware to ensure consistent execution time across devices — both on client and on node (which must re-execute for verification). Per-tick gas needs particular care: a tick budget that is comfortable on a desktop and impossible on a low-end phone fragments the snap ecosystem by device class.
Node re-execution cost: Every bundle requires the node to re-execute the VM, and a checkpoint costs up to 600 ticks of it. Should nodes charge a protocol fee for verification, should checkpoint verification be sampled probabilistically with fraud proofs rather than executed for every bundle, or is the gas limit sufficient to bound computational cost?
State divergence window: Between client execution and node verification, shared state may change. Finite bundles are rejected on mismatch; continuous checkpoints pin a
base_blocksnapshot, which converts the problem into a staleness bound. Is 256 blocks the right window, and should clients retry automatically or should there be an explicit resynchronization protocol?Compiler trust: If SnapScript is the primary authoring format, users trust the compiler to produce correct bytecode. Should compiled bytecode include a proof of compilation, or is source-code publication sufficient for auditability?
Cross-snap versioning: When snap A calls snap B's exported function, and snap B updates its bytecode, should snap A get the new version or the version it was compiled against?
Privacy via ZK: User state is currently protocol-visible. A future version could use ZK proofs to verify execution without revealing user state values — and for continuous execution, a succinct proof of a tick range would replace replay entirely, which is a much larger win. What proof system would be appropriate, and what is the performance budget?
Capability revocation: Once a user approves capabilities for a snap, can they revoke? Should the capability approval have an expiry timestamp?
Embedded message ordering: When a bundle contains multiple embedded messages (e.g., a cast and a reaction), does their ordering within the bundle matter for snapchain processing? Should they be processed atomically or independently?
Session state root serialization: Hashing the full heap image is simple and obviously correct, but a 16 MiB heap hashed every 30 ticks is meaningful work on a phone. Should the root be a Merkle tree over heap pages so that unchanged regions are not rehashed?
Abandoned sessions: Sessions accumulate
SnapSessionRecordentries that are never explicitly ended. What is the pruning policy, and does an abandoned session's tail leave the state trie with orphan records?Multi-device sessions: A user opening the same continuous snap on two devices creates two session IDs with independent chains writing to the same user state. Is LWW acceptable, or should a snap be able to declare that it wants a single active session per user?
Heap in
snap.call: Exported functions currently run in a fresh VM without a heap. Should exports be able to declare a heap requirement, and if so, who pays for it?Which reference, and how is it pinned across languages? Section 9.1 pins a reference implementation by name, version, and source digest, and names Berkeley SoftFloat as the natural candidate. But the pin is a source artifact in one language, while implementations will exist in Rust, TypeScript, Swift, and Kotlin. Is the pin the C source (with every other language required to be a verified port), a language-agnostic specification of the algorithms, or a WebAssembly build of the reference that every host can execute directly? The last is attractive — one binary artifact, byte-identical everywhere, and WebAssembly's own semantics are deterministic — but it puts a Wasm runtime on the critical path of every client.
Should hardware fast paths be permitted at all? Section 9.2 allows them under conditions the implementation must establish itself, on the reasoning that IEEE basic operations are genuinely identical on conforming hardware once the environment is controlled. The counter-argument is that this reintroduces exactly the class of silent, environment-dependent divergence that pinning a reference eliminates, in exchange for a performance win that the soft-float cost analysis suggests may not be needed. Forbidding fast paths outright would make conformance structural rather than tested for the entire arithmetic surface.
Cost of soft-float at tick rates: The estimate in Section 9.2 — that soft-float is a modest multiple of interpreted-bytecode cost rather than the full hardware ratio — needs benchmarking on low-end mobile hardware before per-tick gas is finalized. If it proves worse than expected, the resolution is likely lower default tick gas rather than weakening the arithmetic definition, but that tradeoff should be made with measurements in hand.
11. New Proto Additions Summary
snap_compute.proto(new)SnapComputeEnvelopemessagesnap_compute.proto(new)SnapRegisterBodymessagesnap_compute.proto(new)SnapExecutionBundlemessagesnap_compute.proto(new)SnapInputEventmessagesnap_compute.proto(new)SnapCapabilityApprovalmessagesnap_compute.proto(new)SnapStateWritemessagesnap_compute.proto(new)SnapSessionRecordmessagehyper.protoHYPER_MESSAGE_TYPE_SNAP_REGISTER = 150hyper.protoHYPER_MESSAGE_TYPE_SNAP_UPDATE = 151hyper.protoHYPER_MESSAGE_TYPE_SNAP_EXECUTION = 152hyper.protoRootPrefix::SnapSharedState = 55hyper.protoRootPrefix::SnapUserState = 56hyper.protoRootPrefix::SnapSession = 5712. Parameter Summary
"1.1"The numerical values above are initial governance parameters rather than immutable protocol constants.
13. Appendix: Continuous Execution Examples
13.1 Real-Time Platformer
At 60 Hz the client runs this continuously. Every 300 ticks it produces one checkpoint carrying the input trace and state commitment for those five seconds. The node does not receive 300 execution messages; it receives one execution segment representing 300 deterministic ticks.
13.2 Asteroids
The same VM holds
Player,Asteroids[0..N],Bullets[0..M], andScorein typed heap memory. Each tick reads inputs, updates the player, updates bullets, updates asteroids, detects collisions, spawns and despawns entities, updates the score, and yields. The client renders the resulting state. No network communication is required for ordinary gameplay; onlymatch_start,score, andmatch_endreach shared state.13.3 Shorter Checkpoints for Higher Assurance
An application that wants tighter protocol confirmation shortens its checkpoint interval:
The player still gets 60 Hz local gameplay; the protocol gets a verifiable half-second execution segment. The node replays from the initial session state plus 30 ticks of input and verifies the final state root, state writes, and messages. Checkpoint interval is therefore a tunable assurance/overhead tradeoff — with the caveat in Section 8.3 that it buys assurance about computation, not about who produced the inputs.
All reactions