odin-core was a path dependency on `../odin-core`, a sibling that does not exist
in this repository — a leftover from when these crates lived inside Odin. It now
resolves from Odin's upstream at 3a6368f. CultLib moves from c13b6ba to 96138ff,
which is where the media contract, wire envelope and channel delivery live.
That surfaces a diamond rather than fixing one. odin-core pins CultLib at
c13b6ba; this crate now needs 96138ff; Cargo resolves two cultcache-rs copies
and the DatabaseEntry from one is not the DatabaseEntry from the other:
git+...CultLib.git?rev=96138ff#cultcache-rs@0.1.0
git+...CultLib.git?rev=c13b6ba#cultcache-rs@0.1.0
Muninn cannot resolve this alone. It uses 23 symbols from odin-core, and while
16 are its own records that belong here — capture stream, command boundary, HID
controller state, the five Move records, OBS catalog, Quest access, telemetry
surface, transport profile — the remaining 7 are genuinely Odin's: provider
discovery, Eve advertisement and surface records, Idunn daemon health. Moving
the 16 shrinks the coupling; it does not remove it.
The sequence that unblocks a clean build is therefore:
1. CultLib claude/cultmesh-media-stream-contract merges to main
2. Odin bumps its CultLib pin to that main
3. this builds standalone
Steps 1 and 2 are not this repository's to take. Until then a local
[patch."https://github.com/GameCult/CultLib.git"] unifying both consumers on one
CultLib checkout builds fine, and is deliberately not committed — it points at
working trees that exist on one machine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Muninn's media records now come from CultLib rather than odin-core, and its
envelope, record keys, validators and chunk key are gone in favour of the shared
ones. 608 lines removed against 149 added, nearly all of it duplication that
existed because there was nowhere else to put it.
What went, and why it was there:
The four *WirePayload tuple structs restated each record's entire field list
positionally, kept in step by hand, for the sole purpose of applying
serde_bytes to the payload — a workaround for the DatabaseEntry derive ignoring
serde attributes. The records carry `bytes` now, so the derive emits bin itself.
CultLib has a test comparing both encodings byte for byte before the deletion.
encode/decode_media_wire_record, the four record-key builders, the four record
validators, VideoChunkKey and the feedback-key normaliser were all contract
living inside one producer. A consumer needed every one of them and had to
reimplement or approximate them, which is precisely how the C++ receiver drifted.
Muninn keeps what is genuinely its own: it names itself as producer, once, in
MUNINN_MEDIA_PRODUCER, and passes that through MediaWireProvenance. The envelope
no longer hardcodes "muninn-media:" and "muninn.media" into documents any other
producer would also have had to emit.
One test changed meaning rather than being deleted. A hex fixture captured from
the deployed C++ OBS bridge carries muninn.media_receiver_feedback.v1, which the
rename retires. It is now
`the_previous_generation_bridge_is_deliberately_no_longer_understood` and asserts
the refusal, naming what it refused. That break is real and accepted: the bridge
is being replaced by Ratatoskr, and accepting both schema names would preserve
the ownership confusion the rename exists to remove. If that test starts passing,
someone has put the old id back.
Requires CultLib 96138ff on claude/cultmesh-media-stream-contract. The pin in
Cargo.toml still points at c13b6ba and must move before this builds clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The receiving end now has a repo. The wire envelope does not: MuninnMediaWireRecord
and encode_media_wire_record are still here, both ends need them, and until they
reach CultLib beside the gamecult.media_* records Ratatoskr cannot decode a real
stream.
Also corrects this session's claim that no audio decoder existed. There is one --
an ffmpeg child with a hardcoded -f aac. Opus is that becoming codec-driven, and
it measures 16.8x against PCM.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The map argued congestion collapse: a reliable channel retransmitting under
loss until it starved itself. The measurement refutes the mechanism and keeps
the conclusion. Reliable loses 31% of a clean 12 Mbps offer with no loss and no
congestion present, and barely moves as loss rises.
Also corrects the claim that June tuning was fitted against the VPS path. The
July acceptance ledger records those runs as direct LAN with no WireGuard route;
the hairpin arrived with the relocation, afterwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Video now opts into an unreliable media channel and audio states Reliable
explicitly, rather than both inheriting a hardcoded default.
`rudp_media_transport_options_follow_low_latency_profile` has asserted
Unreliable since it was written and has never passed. It was not aspirational:
CultNet hardcoded the media channel Reliable in both the profile builder and the
send path, so Muninn had no way to express the intent. Muninn signalled it the
only way available, by passing no reliable expiry — meaning "unreliable, so
expiry is moot" — and got reliable delivery instead.
Measured on Raven through `cultnet-impair`, 848-byte packets at 12 Mbps, which
is Muninn's real shape:
profile mode delivered
clean unreliable 100.00%
clean reliable 69.10%
loss 1% unreliable 98.95%
loss 1% reliable 72.00%
loss 3% unreliable 96.73%
loss 3% reliable 73.30%
The reliable channel loses 31% of a clean 12 Mbps offer, and its delivery barely
moves as loss rises, so loss was never the binding constraint — it cannot carry
this rate at all. The unreliable channel holds every packet on a clean link and
then degrades by roughly the loss injected, which is what a media lane should
do. Reverse traffic differs by three orders of magnitude: 43 bytes of
acknowledgement against ~135,000.
An earlier draft of this message argued the defect was retransmission amplifying
load under loss. The measurement does not support that mechanism. The conclusion
survives it; the reasoning did not.
Audio passes Reliable explicitly and does not change. That pairing is still
wrong — 3 Mbps of uncompressed PCM does not belong on a reliable channel — but
that is the Opus change, and inheriting the right value silently is what
produced this bug.
Requires CultLib's `claude/media-channel-delivery`. The dependency pin still
points at c13b6ba and must be moved before this builds from a clean checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Switch Muninn RUDP audio to PCM packets` (2a4f08d, 2026-06-23) moved audio onto
raw PCM and left the ADTS machinery compiled in behind it. Nothing has called it
since: AudioAdtsStreamSendConfig, AudioAdtsStreamSendState and its impl,
CompleteAdtsFrames and complete_adts_frames formed a closed cluster reachable
only from its own five tests.
295 lines, verified unreachable by the compiler with tests compiled, and with
the platform trap checked — none of it sits behind a cfg, unlike the bluetoothctl
helpers that only look dead because their #[cfg(unix)] callers vanish on Windows.
Deliberately narrower than the dead-code warnings suggest. The rest of what the
compiler calls dead in media_packetizer.rs is the receiver half: frame assembly,
chunk expiry, receiver feedback. That is not abandoned work. It is the only
stand-in the sender's feedback path has for the C++ receiver in Mimir — the
tests for MuninnRudpReceiverFeedbackStats and RecentVideoChunkRepairCache build
their fixtures with build_receiver_feedback — and it is the reference shape that
the hand-rolled C++ implementation drifted away from. Deleting it would blind
the path we are debugging and discard the seed of the Rust receiver that
replaces the drift.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Muninn's three crates carried 268 commits inside Odin, from "Add lazy Muninn
telemetry organ" on 2026-06-08 to the Idunn extraction merge on 2026-09-05.
That history is the evidence trail for everything the rebuild has to answer,
so it moves intact rather than being flattened into an initial commit.
Adds only what a standalone repository needs: a workspace manifest, a README
stating the authority Muninn actually holds, and the working teardown map.
Nothing is cut here and nothing builds yet. muninn-daemon still depends on
odin-core by path for twenty record types, which is one of the inversions
being removed. Deleting the old-generation deployment scripts is a separate
intent and waits on the Idunn recipe that replaces them, because three of
them are still the Restart actuators for live Idunn target records.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Idunn lived as a crate inside the repository of a service it manages, which
inverted the authority it exists to hold. It is now GameCult/Idunn with its
full 136-commit history; this merge removes the old body from Odin.
Cut: crates/idunn-daemon and vendor/cultmesh-rs. Odin keeps its own Idunn
projection (crates/odin-core/src/idunn.rs), its RUDP client, and its recipe
under deployment/idunn -- those are Odin's view of a managed relationship,
not Idunn's authority.
Conflicts resolved:
- crates/idunn-daemon/src/main.rs, scripts/linux/idunn-yggdrasil: deleted,
per the extraction. Both live in GameCult/Idunn.
- docs/architecture.md: took the branch. Main still described Gjallar as
Nightwing-resident under E:\Projects\Gjallar; it runs on Yggdrasil from
/srv/gjallar/current, confirmed against the host.
- src/odin/documents.cjs: union. All three hunks were pure branch-side
additions of the ghostlight.schema_catalog type; markers stripped rather
than taking --theirs, to preserve auto-merged HEAD changes elsewhere in
the file. node --check passes.
Verified: odin-core 22/22 on Windows; odin-daemon type-checks clean for
x86_64-unknown-linux-gnu, which is its real target -- it is unconditionally
Unix (systemd socket activation via File::from_raw_fd) and does not build
on Windows by design.
main is tagged pre-idunn-extraction-main: the last tree containing the
swarm keepalive supervision and the July Bifrost Persona feedback target,
neither of which exists on the branch or in GameCult/Idunn. See the merge
notes for why that matters.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Idunn now lives at https://github.com/GameCult/Idunn with its full 136-commit
history. Odin no longer contains the deployment authority that manages it.
Also removes vendor/cultmesh-rs, the private fork that had diverged from
CultLib in both directions. Its unique capability -- the RUDP document server
and the message_id correlation rework -- was reconciled into CultLib first, so
this deletes a duplicate rather than a capability. The four crates that
consumed it by path (muninn-daemon, odin-core, odin-daemon, sleipnir-daemon)
now consume CultLib at c13b6ba like everything else.
What deliberately stays in Odin: crates/odin-core/src/idunn.rs, which is Odin's
own projection of Idunn state for aggregation and is Odin's to own;
src/odin/idunn-rudp.cjs, its client for talking to Idunn; and
deployment/idunn/recipe.toml, which despite the path is Odin's own deployment
recipe expressed in Idunn's format -- it builds odin-daemon.
odin-core: 22 passed, 0 failed, unchanged across the cut. odin-daemon still
fails to compile on Windows for the pre-existing Unix-only std::os::fd reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
IdunnDaemonHealthTrustBindingRecord and its schema constant moved to
cultnet-rs (GameCult/CultLib@c13b6ba). odin-core now imports them from
cultnet_rs instead of defining them, and idunn-daemon/provisioning.rs
pulls all six provider-health symbols from cultnet_rs directly, merged
into its existing cultnet_rs use block. Dropped the odin-core path
dependency from idunn-daemon/Cargo.toml — nothing in the crate reaches
odin_core:: anymore. Bumped the CultLib git rev across all six
consuming manifests to the new commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Moves all six CultLib pins from cbde940 to ae0ac0d, which carries the
reconciled cultmesh-rs. The delta is three commits and near-nothing in the
packages Odin consumes: a license line, two lines in cultnet-rs replication,
and one additive pub fn.
The dev-dependency is a real pre-existing defect, not fallout from the bump.
odin-core's tests use rmp_serde in crates/odin-core/src/idunn.rs but never
declared it, so the lib-test target has never compiled. Verified pre-existing:
9 errors at the old pin, 8 at the new. With the dep declared, odin-core tests
compile and 22 pass -- including idunn_records_round_trip_through_cultmesh,
which matters for the extraction this branch is preparing.
Note for anyone checking this on Windows: odin-daemon does not build here and
that is expected, not a regression. It imports std::os::fd and from_raw_fd,
which are Unix-only. Identical failure at both pins.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The drift was 94 hunks across 4 files, not 94 files as I described it earlier.
muninn-daemon carries almost all of it; sleipnir-daemon needed four lines.
Checked rather than assumed. Ignoring whitespace, the only remaining deltas are
trailing commas rustfmt adds when it wraps an argument list, and braces removed
from single-expression closure bodies. Both are semantics-preserving.
sleipnir-daemon's change is whitespace and nothing else.
Everything except the Unix-only odin-daemon builds; muninn and sleipnir pass 162
tests, with the same pre-existing media-channel failure.
vendor/cultmesh-rs is its own workspace so `cargo fmt` does not reach it, but it
reports no drift of its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The CultNet submodule and the in-tree CultCache copy are gone. Both now come
from CultLib, and vendor/cultmesh-rs is the only fork left -- it too consumes
CultLib, so exactly one version of each crate spans the graph. That mattered:
mixing sources would have put two cultnet-rs crates in one build, and the types
would not have unified.
The in-flight CultMesh RUDP document server comes with it because it is a
prerequisite, not a coincidence. The previously committed CultMesh calls
assume_connected(), a CultNet method Odin's fork carried and CultLib does not,
so the old code cannot build against CultLib at all. The newer code no longer
calls it. Verified by reverting the file and watching it fail.
Three API differences needed adapting: CultLib's transport options carry a
reconnect_policy the fork lacked (7 sites), its create_ack builds the packet
straight from ack state and so cannot fail, and its transport profile owns each
channel's reliable expiry.
Everything but odin-daemon builds; odin-daemon is Unix-only and does not compile
on this machine either way. muninn and sleipnir pass 162 tests. The one failure,
rudp_media_transport_options_follow_low_latency_profile, expects an Unreliable
media channel while every CultNet lineage including both revisions Odin had
pinned declares it Reliable, so it failed before this change too.
The in-flight idunn-daemon work is deliberately left uncommitted; the committed
tree was verified to build without it. Also left alone: the untracked
odin-daemon crate and deployment recipe, and the root manifest, whose now-dead
vendor excludes are inert. That recipe still requires the vendor/cultnet-rs
gitlink and will refuse admission until that line is dropped -- the fix is in
the working tree, waiting on that directory being tracked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit fixed the target catalog and the operator alarm, but the
actuators the catalog invokes carried the same E: paths. Idunn would have
resolved every target and then failed on the first restart or deploy it ran,
for exactly the reason the alarm failed.
Rebased 41 references across 27 scripts:
- .cmd bodies invoke their sibling .ps1 through %~dp0 instead of an absolute
drive path, and restart-weksa.cmd reaches its sibling repo the same way.
- .ps1 parameter defaults and assignments derive from $PSScriptRoot: one level
up for paths inside Odin, two for sibling repos under the projects root.
Left deliberately alone: $RemoteViliRoot and $StateRoot in restart-vili.ps1.
Both are consumed inside the EncodedCommand block that restart-vili ssh's to
raven, so they describe raven's filesystem, not starfire's. Whether raven also
moved off E: is an operator question, not something to infer from here.
Verified: all 46 scripts under scripts/ parse clean, and the derivation
resolves to F:\Projects\Odin and F:\Projects on this host.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>