diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index fa9fbd5..5658201 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -25,6 +25,8 @@ shape removes independent per-kind storage from the live foundation. Aquarium/Faust C ABI: opaque reservoir create/destroy, sample-handle push, edge/window queries, ring counts, and latest sample lookup by sensor hash. The ABI now exposes total rolling-buffer length and typed-view length. + - `localcast_hash_source_id` owns stable FNV-1a source hashing for native + producers; empty/null source ids produce zero and are rejected. - `LocalcastRuntime` wraps the current reservoir with typed native producer calls and total/typed read functions. It is the intended live spine for Aquarium/Faust bindings. @@ -134,9 +136,10 @@ shape removes independent per-kind storage from the live foundation. packet handles through `ILocalCastNativeRuntime` with an injected payload decoder. It also has the matching safe-code audio block and render packet descriptor bindings, plus a render descriptor decoder that reads timing and - target metadata from native payload handles while leaving point-buffer - decoding injectable. The next Aquarium cut is wiring native runtime creation - and real point-buffer decode into the app path. + target metadata from native payload handles and decodes the first native + point-buffer ABI. Aquarium also exposes the native source-id hash wrapper. + The next Aquarium cut is wiring native runtime creation into the app path + once a producer exists to feed it. 3. Move camera/mic/loopback ingest into native capture workers that use `LocalcastProducer` to append typed sample handles. 4. Move feature extraction, flow, cross-view matching, LOD reconciliation, diff --git a/native/README.md b/native/README.md index 0eb91d3..df6fc11 100644 --- a/native/README.md +++ b/native/README.md @@ -19,6 +19,9 @@ tests, and offline analysis. It does not own dense live fusion or DSP. The reservoir ABI is intentionally small: - `LocalcastReservoir` is opaque. +- `localcast_hash_source_id` is the stable FNV-1a source-id hash used before + creating producers. Empty or null source ids hash to zero and are rejected by + producer creation. - `LocalcastSampleHandle` is sample metadata plus a `payload_handle` and provenance flags. Live samples must have `flags == 0`; diagnostic or unknown flagged samples are rejected by the live reservoir. diff --git a/native/reservoir/include/localcast_reservoir.h b/native/reservoir/include/localcast_reservoir.h index 451ffaf..5d81ad1 100644 --- a/native/reservoir/include/localcast_reservoir.h +++ b/native/reservoir/include/localcast_reservoir.h @@ -94,6 +94,8 @@ typedef struct LocalcastRuntimeStatus { size_t render_packet_count; } LocalcastRuntimeStatus; +uint64_t localcast_hash_source_id(const uint8_t *bytes, size_t byte_len); + LocalcastReservoir *localcast_reservoir_create(uint64_t duration_ns); void localcast_reservoir_destroy(LocalcastReservoir *reservoir); diff --git a/native/reservoir/src/lib.rs b/native/reservoir/src/lib.rs index bc3322d..9804ad4 100644 --- a/native/reservoir/src/lib.rs +++ b/native/reservoir/src/lib.rs @@ -3,6 +3,8 @@ use std::collections::VecDeque; pub const DEFAULT_RESERVOIR_NS: u64 = 5_000_000_000; pub const LOCALCAST_SAMPLE_FLAG_DIAGNOSTIC: u32 = 1 << 0; pub const LOCALCAST_AUDIO_SAMPLE_FORMAT_F32_INTERLEAVED: u32 = 1; +const FNV1A_OFFSET_64: u64 = 14_695_981_039_346_656_037; +const FNV1A_PRIME_64: u64 = 1_099_511_628_211; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SampleKind { @@ -357,6 +359,29 @@ fn is_live_sample(sample: LocalcastSampleHandle) -> bool { sample.flags == 0 } +fn hash_source_id(bytes: &[u8]) -> u64 { + if bytes.is_empty() { + return 0; + } + let mut hash = FNV1A_OFFSET_64; + for byte in bytes { + hash ^= u64::from(*byte); + hash = hash.wrapping_mul(FNV1A_PRIME_64); + } + if hash == 0 { 1 } else { hash } +} + +#[unsafe(no_mangle)] +pub extern "C" fn localcast_hash_source_id(bytes: *const u8, byte_len: usize) -> u64 { + if byte_len == 0 { + return 0; + } + if bytes.is_null() { + return 0; + } + hash_source_id(unsafe { std::slice::from_raw_parts(bytes, byte_len) }) +} + #[unsafe(no_mangle)] pub extern "C" fn localcast_reservoir_create(duration_ns: u64) -> *mut LocalcastReservoir { Box::into_raw(Box::new(LocalcastReservoir { @@ -923,6 +948,19 @@ mod tests { } } + #[test] + fn source_id_hash_is_stable_and_rejects_empty_sources() { + let source = b"kiyo-primary"; + + assert_eq!(0, hash_source_id(b"")); + assert_eq!(0, localcast_hash_source_id(std::ptr::null(), source.len())); + assert_eq!( + hash_source_id(source), + localcast_hash_source_id(source.as_ptr(), source.len()) + ); + assert_ne!(hash_source_id(source), hash_source_id(b"kiyo-secondary")); + } + #[test] fn rolling_reservoir_expires_every_kind_from_one_edge() { let mut reservoir = RollingReservoir::new(DEFAULT_RESERVOIR_NS); diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index aca358c..3de2359 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -33,6 +33,8 @@ Get-Content .\state\evidence.jsonl -Tail 8 - `LocalcastRuntime` now exposes producer pushes and consumer reads. `LocalcastProducer` owns source identity and sequence assignment for native capture workers. +- `localcast_hash_source_id` owns stable FNV-1a source-id hashing for native + producer creation. - `LocalcastAudioBlockDescriptor` is the first typed audio payload descriptor: caller-owned float32 interleaved audio blocks cross the ABI by descriptor pointer, while the reservoir still owns only timing/retention metadata. @@ -58,6 +60,10 @@ Get-Content .\state\evidence.jsonl -Tail 8 - Aquarium-Engine commit `cfb48d7` adds `LocalCastNativeRenderDescriptorDecoder`, which reads render descriptor timing/target metadata from native payload handles while keeping point-buffer decoding injectable. +- Aquarium-Engine commit `f4c5988` adds `LocalCastNativeRenderPoint` and native + point-buffer decoding for the render descriptor decoder. +- Aquarium-Engine commit `924aa2f` exposes `LocalCastNativeSourceId.HashUtf8` + as the safe wrapper over native source-id hashing. - Edge JSON is schema/diagnostic only. Runtime network/process data should remain typed CultNet documents. - Python live producers and diagnostic CultCache/JSON file adapters are diagnostics, not production foundations. The OpenGL Spout sink has been diff --git a/state/evidence.jsonl b/state/evidence.jsonl index bc562e8..0e56a09 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -83,3 +83,6 @@ {"ts": "2026-05-19T11:21:55+00:00", "type": "implementation", "status": "ok", "note": "Aquarium-Engine commit cfb48d7 adds LocalCastNativeRenderDescriptorDecoder: Aquarium now reads LocalcastRenderPacketDescriptor timing/target metadata from native payload handles while keeping point-buffer decoding as an injected boundary."} {"ts": "2026-05-19T11:25:11+00:00", "type": "decision", "status": "ok", "note": "Mimir is now the public brand for the LocalCastBridge/localcast implementation surfaces: local sensors become one coherent realtime volumetric field, while OBS, Aquarium, Faust, and FFmpeg retain their existing ownership boundaries."} {"ts": "2026-05-19T11:25:59+00:00", "type": "implementation", "status": "ok", "note": "Native reservoir ABI now includes LocalcastRenderPoint, the fixed first point-buffer element for Aquarium render decoding: stable-key hash, source timestamp, position, radius, color/alpha, and confidence."} +{"ts": "2026-05-19T11:29:24+00:00", "type": "implementation", "status": "ok", "note": "Aquarium-Engine commit f4c5988 adds LocalCastNativeRenderPoint binding plus native point-buffer decoding for LocalCastNativeRenderDescriptorDecoder; LocalCast tests pass 20 tests no-build."} +{"ts": "2026-05-19T11:30:58+00:00", "type": "implementation", "status": "ok", "note": "Native reservoir ABI now owns source-id hashing through localcast_hash_source_id, a stable FNV-1a hash for producer source identity; null/empty sources hash to zero and producer creation still rejects zero."} +{"ts": "2026-05-19T11:34:44+00:00", "type": "implementation", "status": "ok", "note": "Aquarium-Engine commit 924aa2f exposes LocalCastNativeSourceId.HashUtf8 as a safe wrapper over native localcast_hash_source_id, avoiding managed reimplementation of source identity hashing."} diff --git a/state/map.yaml b/state/map.yaml index c62cd89..227b7c5 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -56,6 +56,8 @@ accepted_design: - `LocalcastRuntime` exposes typed producer pushes plus total/typed window reads. `LocalcastProducer` owns source identity and sequence assignment before hardware adapters append live sample handles. + - `localcast_hash_source_id` owns stable native source-id hashing so capture + workers do not each invent incompatible sensor hashes. - `LocalcastAudioBlockDescriptor` is the first typed payload descriptor for caller-owned float32 interleaved audio blocks; the reservoir stores only the descriptor pointer as a payload handle and does not own DSP memory.