Project

General

Profile

User Story #38 » 0005-feat-netcode-carry-peer-orientation-through-the-fuse.patch

knight8241, 08/07/2026 18:31

View differences:

common/src/commonMain/kotlin/com/aether/mofe/engine/netcode/NetcodeAdapter.kt
package com.aether.mofe.engine.netcode
import com.aether.mofe.model.DeviceId
import com.aether.mofe.model.Quaternion
import com.aether.mofe.model.Timestamp
import com.aether.mofe.model.messaging.FusedStateSample
import com.aether.mofe.model.messaging.MeshPayload
......
/**
* Record every sample in a fused-state batch into its device's history. The wire
* timestamp is treated as mesh time (the sender stamps it — companion edit #2).
* The fused wire sample carries no orientation, so remote poses use identity; the
* geometric predicates are positional, so this is sufficient for them.
* The fused wire sample now carries the sender's orientation, so remote poses render
* with real facing (Gap 1); pre-orientation senders decode to identity via the field
* default, leaving the positional predicates unaffected.
*/
fun onFusedSample(sample: FusedStateSample) {
for (w in sample.samples) {
session.recordSample(
DeviceId(w.targetId),
Timestamp(w.timestampMicros),
TemporalPose(w.position, w.velocity, Quaternion.IDENTITY),
TemporalPose(w.position, w.velocity, w.orientation),
)
}
}
common/src/commonMain/kotlin/com/aether/mofe/model/CoreTypes.kt
* Quaternion for 3D rotation representation.
* Avoids gimbal lock and provides smooth interpolation.
*/
@Serializable
data class Quaternion(
val w: Double,
val x: Double,
common/src/commonMain/kotlin/com/aether/mofe/model/messaging/WireFragments.kt
import com.aether.mofe.model.AnchorObservation
import com.aether.mofe.model.DeviceId
import com.aether.mofe.model.FusedState
import com.aether.mofe.model.Quaternion
import com.aether.mofe.model.Timestamp
import com.aether.mofe.model.Vector3D
import kotlinx.serialization.Serializable
......
val velocity: Vector3D,
/** 1-σ per axis — lets receivers render confidence without the full EKF state. */
val positionSigma: Vector3D,
/** Body-frame orientation (unit quaternion) so peers render with real facing
* instead of identity. Defaulted for schema evolution: a pre-orientation sender
* omits it and decodes to identity, and the mesh codec ignores unknown keys, so
* a newer sender stays compatible with an older receiver too. */
val orientation: Quaternion = Quaternion.IDENTITY,
)
/**
......
position = position,
velocity = velocity,
positionSigma = positionUncertainty,
orientation = orientation,
)
common/src/commonTest/kotlin/com/aether/mofe/engine/netcode/NetcodeAdapterTest.kt
package com.aether.mofe.engine.netcode
import com.aether.mofe.model.DeviceId
import com.aether.mofe.model.Quaternion
import com.aether.mofe.model.Timestamp
import com.aether.mofe.model.Vector3D
import com.aether.mofe.model.messaging.FusedStateSample
......
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
/**
......
*/
class NetcodeAdapterTest {
private fun wire(id: String, tMicros: Long, x: Double) = WireFusedSample(
private fun wire(
id: String,
tMicros: Long,
x: Double,
orientation: Quaternion = Quaternion.IDENTITY,
) = WireFusedSample(
targetId = id,
timestampMicros = tMicros,
position = Vector3D(x, 0.0, 0.0),
velocity = Vector3D.ZERO,
positionSigma = Vector3D.ZERO,
orientation = orientation,
)
@Test
......
assertTrue(session.trackedDevices().containsAll(listOf(DeviceId("A"), DeviceId("B"))))
}
@Test
fun onFusedSampleCarriesPeerOrientationNotIdentity() {
val session = NetcodeSession()
val adapter = NetcodeAdapter(session)
// A real facing (180° about Z) — must survive the wire instead of collapsing to identity (Gap 1).
val facing = Quaternion(0.0, 0.0, 0.0, 1.0)
adapter.onFusedSample(FusedStateSample(listOf(wire("A", 500_000, 1.0, facing))))
val pose = session.poseAt(DeviceId("A"), Timestamp(500_000))!!
assertEquals(facing, pose.orientation)
assertNotEquals(Quaternion.IDENTITY, pose.orientation)
}
@Test
fun onTimeSyncResponseFeedsTheClock() {
val session = NetcodeSession()
common/src/commonTest/kotlin/com/aether/mofe/messaging/MeshCodecTest.kt
package com.aether.mofe.messaging
import com.aether.mofe.model.Quaternion
import com.aether.mofe.model.Vector3D
import com.aether.mofe.model.messaging.DeviceCredential
import com.aether.mofe.model.messaging.FusedStateSample
import com.aether.mofe.model.messaging.MeshChannel
import com.aether.mofe.model.messaging.MeshPayload
import com.aether.mofe.model.messaging.MessageEnvelope
......
import com.aether.mofe.model.messaging.SecHelloDone
import com.aether.mofe.model.messaging.SecHelloInit
import com.aether.mofe.model.messaging.StateDelta
import com.aether.mofe.model.messaging.WireFusedSample
import com.aether.mofe.model.messaging.WireObservation
import kotlin.test.Test
import kotlin.test.assertEquals
......
assertEquals(batch, MeshCodec.decodeOrNull(MeshCodec.encode(envelope(batch)))?.payload)
}
@Test
fun fusedSampleRoundTripsPreservingOrientation() {
// A peer's real facing must survive the wire (Gap 1) — this exercises the
// @Serializable on Quaternion end-to-end through the CBOR codec.
val facing = Quaternion(0.0, 0.0, 0.0, 1.0) // 180° about Z, non-identity
val sample = FusedStateSample(listOf(WireFusedSample(
targetId = "t", timestampMicros = 42,
position = Vector3D(1.0, 2.0, 3.0), velocity = Vector3D.ZERO,
positionSigma = Vector3D.ZERO, orientation = facing,
)))
val decoded = MeshCodec.decodeOrNull(MeshCodec.encode(envelope(sample)))?.payload as? FusedStateSample
assertEquals(facing, decoded?.samples?.single()?.orientation,
"orientation must round-trip through CBOR, not reset to identity")
}
@Test
fun garbageDecodesToNullWithoutThrowing() {
assertNull(MeshCodec.decodeOrNull(byteArrayOf(1, 2, 3, 4, 5)))
    (1-1/1)