Limits and defaults#
Some of these are hard protocol constants. Others are recommended defaults a hub may tune and then advertise. The Notes column carries the registry's own rationale where it records one.
Core protocol constants#
| Name | Value | Notes |
|---|---|---|
header_bytes |
8 |
|
min_transport_payload |
242 |
ESP-NOW 250 − 8 header; STATE frames must fit this (§9.1) |
catalog_chunk_payload |
192 |
|
blob_chunks_in_flight |
4 |
RFC-050: advertised SENDER pacing budget — the max unacknowledged-by-application-progress BLOB_CHUNKs a sender may have outstanding for one transfer before it MUST hold emission (§8.4's backpressure table). Concrete answer to the panel's "what IS the signal" finding: a hub MAY advertise a smaller value, MUST NOT advertise more. |
bundle_max_samples |
32 |
|
bundle_max_span_ms |
20 |
RFC-087: the span cap of samples-kind and every h2c bundle; a c2h segments-kind bundle spans up to its grant's schedule horizon instead. |
segment_t_off_unit_us |
100 |
RFC-087: t_off unit on a c2h segments-kind bundle (a u16 spans 6.5535 s, covering the 1000 ms horizon ceiling at 0.1 ms resolution). samples-kind and h2c bundles keep 1 us units. |
seq_width_bits |
16 |
|
seq_newer_window |
32768 |
serial arithmetic half-window |
frag_reassembly_timeout_ms |
5000 |
|
frag_max_concurrent_per_session |
2 |
|
idempotency_ring_depth |
32 |
|
intent_ingress_default_per_s |
50 |
|
stream_ingress_overage_nack_per_s |
5 |
§10.5: per-session cap on RATE_LIMITED NACKs emitted for STREAM-ingress overage (throttle — the NACK is back-pressure feedback, not a per-sample echo; unthrottled it would mirror the very flood it reports) |
event_queue_depth_per_subscriber |
16 |
|
never_shed_stall_eviction_ms |
2000 |
§10.4 step 4: never-shed queue stall past this PARKS the session (RFC-051) — transport closed + detached, identical end state to RFC-042's transport-loss trigger. No longer a teardown/eviction; SESSION_EVICTED does not fire from this clock any more. |
catalog_chunk_gap_timeout_ms |
500 |
recommended (SHOULD) |
busy_retry_after_default_ms |
2000 |
|
ping_interval_holding_control_ms |
200 |
|
ping_interval_idle_ms |
1000 |
|
deadman_default_ms |
600 |
|
deadman_min_ms |
250 |
|
deadman_max_ms |
5000 |
|
stream_quiet_release_ms |
500 |
RFC-098 (§11.4): a stream source is released when its last admitted bundle has played out and no bundle arrived for this long, or for the grant's schedule horizon when that is longer. |
pairing_window_default_s |
120 |
|
pairing_pin_digits |
4 |
|
pairing_gesture_boot_count |
3 |
RFC-049g: N in §12.3(c)'s power-cycle gesture — this many CONSECUTIVE short boots arm the push-to-pair window on the next boot. Was prose-only ("N (default 3)"); the panel's own complaint pattern (registry doctrine says numbers are never left as hedges) applies to this one too. |
pairing_gesture_max_uptime_ms |
10000 |
RFC-049g: the per-boot uptime ceiling that counts as "short" for the gesture above. Was prose-only ("~10 s") — the tilde was hedge language in a normative section; pinned here matching the value SPEC §12.3(c) already carried in prose. Reference-firmware conformance to this exact value is unverified by this pass — implementation is Phase D. |
token_bytes |
16 |
|
instance_id_bytes |
8 |
|
etag_bytes |
8 |
|
conformance_min_clients |
4 |
|
default_max_clients_ws |
8 |
|
default_max_clients_espnow |
4 |
|
default_max_clients_ble |
1 |
|
default_max_clients_serial |
1 |
|
estop_repeat_interval_ms |
50 |
|
estop_repeat_max |
20 |
|
clock_resync_interval_s |
10 |
|
probe_default_bytes |
8192 |
|
probe_max_duration_ms |
1500 |
|
catalog_max_entries |
256 |
|
catalog_max_entry_bytes |
4096 |
feasibility pass: a 50-field FULLY annotated entry (defaults + options + groups + descs) encodes to ~8–10 KB, which violates RFC-028's no-unbounded-allocation rule for a per-entry decode buffer. Oversize is a catalog-AUTHORING error caught by conformance tooling, not a runtime surprise: the entry splits across channels or trims its descs. |
max_subscriptions_per_session |
64 |
|
max_subscriptions_per_frame |
16 |
RFC-033.3: wishes one SUBSCRIBE/HELLO frame may carry (= the reference decoder's kSubscribeMaxWishes, which was previously discoverable only by binary-searching a live hub). Advertised in WELCOME limits key 4; a hub MAY advertise less, never more than it decodes. Overflow answers SUBSCRIBE_REJECTED, never silence. |
ws_subprotocol |
valence.v1 |
Per-binding max_frame defaults#
| Name | Value | Notes |
|---|---|---|
max_frame_ws |
512 |
= the reference hub's FrameBuffer capacity. Nothing NEEDS to be bigger: data-plane frames fit the 242 B floor by rule, blob chunks are catalog_chunk_payload + header, and oversized control frames fragment (§5.6). A fixed capacity keeps every queue slot heap-free on an ESP32. |
max_frame_espnow |
250 |
the hard ESP-NOW MTU. Payload 250−8 = 242 = min_transport_payload; that subtraction is where the 242 B STATE floor came from in the first place. |
max_frame_ble |
244 |
ATT_MTU 247 − 3 B notification header. Payload 236. A client stuck at the legacy 23 B MTU cannot carry a full STATE frame at all, which is why §13 SHOULD-mandates MTU exchange + data length extension before catalog transfer. |
max_frame_serial |
512 |
byte-stream binding: no MTU of its own, so it matches the WS/buffer figure rather than inventing a third number. |
Session lifecycle (RFC-015, RFC-024)#
| Name | Value | Notes |
|---|---|---|
catalog_ready_timeout_ms |
15000 |
RFC-015: a session that PINGs happily but never sends CATALOG_READY is GOODBYE'd READY_TIMEOUT. Liveness reaping alone NEVER fires on a pinging client, so without this a half-adopted session holds a slot forever with both planes gated shut. (READY itself is re-sent by the client at catalog_chunk_gap_timeout_ms until retained STATE arrives — idempotent, no handshake state machine.) |
idle_reap_multiplier |
3 |
RFC-024: reap a non-owning session after this multiple of ping_interval_idle_ms of silence. §6.5 said "MAY"; it was never implemented, so a viewer that went dark held a slot forever. Two liveness regimes, deliberately different: source OWNERS get the deadman window + §11.3 loss policy; everyone else gets idle reaping with no motion consequence. |
Streaming (RFC-013, RFC-014, RFC-049c)#
| Name | Value | Notes |
|---|---|---|
schedule_horizon_max_ms |
1000 |
RFC-087: ceiling of the per-grant schedule horizon (cbor key 50). The pinned steps are 250 (default), 500 and 1000. |
max_future_schedule_ms |
250 |
RFC-014, widened by RFC-084, refined by RFC-087: the lead cap for samples-kind c2h STREAM and the DEFAULT schedule horizon of a segments-kind grant (a grant MAY advertise 500 or 1000 via schedule_horizon_ms); a sample stamped further ahead is clamped, never rejected. Segments: t_base + t_off[i] IS the execution start; samples: it is the arrival time (§5.4). It was already the shipped fw 2.1.45 behavior but registered NOWHERE — the MFP plugin carried a private SegLookaheadMs=120 against it. Interop by folklore, now by number. Recommended client lookahead <= half of this. |
max_burst_multiple |
4 |
RFC-013: cap on burst relative to granted rate. An unbounded client-declared burst would reintroduce the exact flood the token bucket exists to stop. |
segment_end_vel_unspecified |
-32768 |
RFC-058: the §5.4 unspecified sentinel of a motion-input i16 end-velocity field (a signed field's type minimum; zero is a real slope). Without a scheduled successor a hub MUST resolve it to rest (§9.6). Also the absent value for any motion-input field a sender has no value for (RFC-071). Replaces the hand copies (reference catalog comment, MFP SegmentEndVelSentinel). |
segment_dwell_span |
0.02 |
RFC-058: §9.6 dwell rule, normalized units: a segment whose target is within this of the previous accepted target on the same source is a hold; a nonzero end velocity on it SHOULD be bounded to zero and surfaced as its own anomaly kind. Tested against the target, never position. |
segment_handoff_k |
1.5 |
RFC-049c: the H11 machine-side handoff-sanity bound (§9.6) — a hub SHOULD reject/bound an accepted end-velocity exceeding k * min(|chord_in|, |chord_out|). Was reference-implementation-only (the MFP plugin's own Fritsch-Carlson limiter and the firmware's boundHandoffVelocity both hardcoded 1.5 independently) — the panel quoted this registry's OWN doctrine ("a competing implementation has no authoritative source for the clamping constant") back at us. Pinned here so a second implementation matches shape without reverse-engineering the reference. Hub-side per-source scheduling-depth backstop (widening H11's lookahead-bounded coverage) is Phase D implementation, not a registry number. |
String caps (RFC-009, RFC-022.5, RFC-028.2)#
| Name | Value | Notes |
|---|---|---|
desc_max_bytes |
128 |
RFC-009: per-field user-facing description. Flash-resident on the hub, travels once, etag-cached forever. |
nack_detail_max_bytes |
48 |
RFC-022.5 |
option_label_max_bytes |
24 |
RFC-009.1: one label in a single-select options array. Matches the 24 B field-name cap so a label is never the thing that overflows an entry. |
Stores & trust (RFC-021, RFC-027, RFC-029)#
| Name | Value | Notes |
|---|---|---|
preset_capacity_min |
32 |
RFC-021.6: conformance FLOOR for a store's declared capacity, not a cap. 32 fray-d presets ~ 1.5 KB NVS; the mechanism does not blink at 256. Small hubs declare less, the catalog says so, clients render accordingly. |
preset_item_max_bytes |
4096 |
RFC-021.6 default per_item_max. The payload is opaque: the protocol never decodes it, so this is purely a transfer/storage budget. |
paired_devices_max |
8 |
trust-ledger capacity. 8, matching the library's kMaxPaired TODAY (the RFC text said 16; the feasibility pass corrected it against the source). Also equals default_max_clients_ws, which is a coincidence worth not reading meaning into. |
trust_ledger_max_bytes |
1900 |
feasibility pass: the whole encoded ledger is ONE blob in the existing valence NVS namespace and must stay inside a single ~2 KB NVS page. Written only on change, and gated on ota_active exactly like savePairing() — flash-cache writes during an OTA reset the chip. |
pairing_pending_max |
4 |
RFC-027.2a: bounded knock list. Bounded because it is an unauthenticated queue — the one surface a stranger can fill. |
client_ver_max_bytes |
24 |
HELLO trust.client_ver. Registered as a NUMBER (the trust_keys note only stated it in prose) because RFC-028.2 makes registry string caps a PARSE-TIME obligation: a receiver rejects an over-cap string in a structural payload rather than truncating and continuing, and it cannot enforce a cap that exists only in English. Same value bounds the trust-ledger version field, so the tripwire never compares a truncated value against a full one. |
trust_ledger_name_max_bytes |
16 |
trust-ledger name. 16 to match the 0x0002 roster's str16 rather than HELLO's 32: a roster label is a label, and the authoritative full name rides HELLO/0x0007 while the session lives. |
trust_ledger_kind_max_bytes |
16 |
trust-ledger kind. Equals the HELLO client_kind cap, so a kind is recorded whole and never appears to change between sessions. |
hub_sig_timeout_ms |
3000 |
RFC-029 item 1: how long a client that REQUESTED a hub signature AND holds a pinned key waits before calling the absence a failure. Generous on purpose — one software ECDSA is ~30-80 ms, but it is queued behind whatever the hub's low-priority worker is already doing, and a hub is allowed to be busy. A client with NO pinned key never applies this timeout: it has nothing to verify against, and silence from a hub that simply has no keypair is conformant, not suspicious. |
auth_attempts_max |
3 |
RFC-029 item 6: failed AUTH (0x1C) proofs a session may present before the hub stops answering and GOODBYEs it. Mirrors §12.2's "three failures close the PIN window" verbatim rather than inventing a second number. The proof is 16 bytes, so this is not what makes guessing infeasible — it is what stops an unauthenticated peer spending the hub's HMAC budget in a loop. |
Logging (RFC-017)#
| Name | Value | Notes |
|---|---|---|
provision_join_timeout_ms |
20000 |
RFC-069 (§13.9): longest a hub defers its answer to a provisioning wifi_join before NACK NETWORK_JOIN_FAILED. Covers association plus DHCP on a slow access point. |
log_replay_depth_default |
32 |
entries the hub MAY replay from its ring tail when a session is granted the log channel. THE named exception to §9.4's no-replay rule ("except where a channel's catalog entry declares a replay depth"); the actual depth is declared per-entry, this is the default. 32 lines is roughly "what went wrong just before I connected" without making every grant a burst. |
ESP-NOW accessory spoke (RFC-075, §13.3.1)#
| Name | Value | Notes |
|---|---|---|
spoke_beacon_interval_ms |
1000 |
an accessory host's BEACON cadence while the spoke is up (500 ms while a pairing window is open) |
spoke_deadman_ms |
5000 |
accessory deadman default: five missed beacons at the idle cadence. A declaration MAY carry a shorter window, never below 2 x spoke_beacon_interval_ms. |
accessory_slice_ids |
32 |
RFC-076 (§8.10): ids per accessory slice in the user space 0x8000-0xBFFF (512 slices). r 0 session-scoped, r 1 accessory-status, r 2-31 the accessory's own (30 at most). Narrowed from the draft's 0x80 by ruling. |
relationships_max |
16 |
RFC-078 (§8.11): most relationships a host stores; the relationships-roster masks are 2 x bitfield8. |
accessory_declaration_max_bytes |
4096 |
RFC-076 (§8.10): largest encoded accessory declaration a host accepts. A worst-case 30-channel declaration is about 3.5 KB (19 chunks of catalog_chunk_payload). |
spoke_scan_dwell_ms |
150 |
an accessory's per-channel listen after its DISCOVER_PROBE; also the host's floor between out-of-cadence probe-answer beacons. 13 channels x 150 ms = about 2 s cold join. |