Add native source id hashing

This commit is contained in:
Meta Vens 2026-05-19 12:35:26 +01:00
commit 22f643574e
7 changed files with 60 additions and 3 deletions

View file

@ -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,

View file

@ -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.

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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."}

View file

@ -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.