Project

General

Profile

User Story #58 » 0005-feat-mesh-NetcodeBringUp-kill-switch-default-OFF-to-.patch

knight8241, 08/07/2026 18:32

View differences:

common/src/commonMain/kotlin/com/aether/mofe/messaging/MeshCoordinator.kt
* [EventNotifier]. Non-arbitrable events map to no claims.
*/
fun emitClaims(event: MeshEvent) {
if (!config.netcodeBringUp) return // netcode disabled → no claims on the control plane
for (claim in MofeNetcodeBridge.claimsFor(event, selfId)) {
claimTracker.track(claim)
val wire = claim.toWire()
......
scope.launch {
registry.deltas.collect { delta -> applyLocalSideEffects(delta) }
}
// Fold every peer's fused sample (stamped in mesh
// time by the sender) into its history buffer. Runs for both roles;
// the leader also arbitrates against these buffers.
scope.launch {
messaging.fusedSamples.collect { netcodeAdapter.onFusedSample(it.second) }
// Netcode bring-up (kill-switch, default OFF — see MessagingConfig.netcodeBringUp).
// Gated as one unit so a bring-up defect stays fully isolated from the mesh:
// when OFF, none of the netcode collectors/transport run in the mesh scope, so
// ranging, the solve, and Raft proceed exactly as they did before go-live.
if (config.netcodeBringUp) {
// Fold every peer's fused sample (stamped in mesh
// time by the sender) into its history buffer. Runs for both roles;
// the leader also arbitrates against these buffers.
scope.launch {
messaging.fusedSamples.collect { netcodeAdapter.onFusedSample(it.second) }
}
// G4b: the leader arbitrates each inbound claim against its rewound history and
// broadcasts the verdict; every device applies inbound verdicts to its predictions.
netcodeTransport.start(
scope = scope,
eventClaims = messaging.eventClaims,
eventVerdicts = messaging.eventVerdicts,
isLeader = { isLeader },
nowMesh = { netcode.toMeshTime(clock.now()) },
broadcastControl = { messaging.broadcastControl(it) },
)
}
// G4b: the leader arbitrates each inbound claim against its rewound history and
// broadcasts the verdict; every device applies inbound verdicts to its predictions.
netcodeTransport.start(
scope = scope,
eventClaims = messaging.eventClaims,
eventVerdicts = messaging.eventVerdicts,
isLeader = { isLeader },
nowMesh = { netcode.toMeshTime(clock.now()) },
broadcastControl = { messaging.broadcastControl(it) },
)
}
/** Whether the netcode seam is live on this node (see [MessagingConfig.netcodeBringUp]).
* Exposed so the host/diagnostics can surface the bring-up mode; when false the
* [netcode] session is constructed but inert. */
val netcodeBringUpEnabled: Boolean get() = config.netcodeBringUp
// ═══════════════════════════ VOTER (anchor) ═══════════════════════════
/**
common/src/commonMain/kotlin/com/aether/mofe/messaging/MeshMessagingService.kt
val rangingPublishHz: Int = 10,
val fusedSamplePublishHz: Int = 10,
val timeSyncIntervalMillis: Long = 5_000,
/**
* Netcode bring-up kill-switch. Default **OFF**: the coordinator still
* *constructs* the [com.aether.mofe.engine.netcode.NetcodeSession] (so the
* render / aim-tracking APIs the UI reads stay non-null and simply return
* empty), but it does NOT fold the live fused-state plane into it, start the
* claim/verdict transport, or emit claims onto the control plane. This keeps
* the netcode seam fully inert so a bring-up defect cannot regress ranging,
* the solve, or Raft. Flip ON (via the [MeshNode] descriptor's config) to
* exercise go-live on-device; flip back OFF to revert without a rebuild.
*/
val netcodeBringUp: Boolean = false,
)
/** A peer this node fans data-plane traffic out to. Maintained from the registry. */
common/src/commonTest/kotlin/com/aether/mofe/messaging/NetcodeArbitrationTest.kt
@Test
fun memberClaimConfirmedByLeaderRewindArbitration() = runTest {
val h = ClusterHarness(this)
val h = ClusterHarness(this, netcodeBringUp = true) // exercising the netcode go-live path
val leader = h.bootstrapLeader("root", 9921); runCurrent()
val member = h.joinAsMember("client", 9922, leaderHost = "root")
h.pump(40) // membership
......
@Test
fun leaderArbitratesItsOwnDetectedClaim() = runTest {
val h = ClusterHarness(this)
val h = ClusterHarness(this, netcodeBringUp = true) // exercising the netcode go-live path
val leader = h.bootstrapLeader("root", 9931)
h.pump(10)
leader.registerPredicateMeshWide(zone())
common/src/commonTest/kotlin/com/aether/mofe/messaging/NetcodeAssemblyTest.kt
@Test
fun coordinatorBuffersPeerFusedSamplesIntoTheNetcodeSession() = runTest {
val h = ClusterHarness(this)
val h = ClusterHarness(this, netcodeBringUp = true) // exercising the netcode go-live path
h.bootstrapLeader("root", 9901)
h.pump(5)
val svc = h.services["root"]!!
common/src/commonTest/kotlin/com/aether/mofe/messaging/NetcodeBringUpGateTest.kt
package com.aether.mofe.messaging
import com.aether.mofe.messaging.support.ClusterHarness
import com.aether.mofe.messaging.support.TestFactories
import com.aether.mofe.model.DeviceId
import com.aether.mofe.model.Timestamp
import com.aether.mofe.model.Vector3D
import com.aether.mofe.model.messaging.FusedStateSample
import com.aether.mofe.model.messaging.MeshChannel
import com.aether.mofe.model.messaging.MessageEnvelope
import com.aether.mofe.model.messaging.WireFusedSample
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
/**
* The NetcodeBringUp kill-switch ([MessagingConfig.netcodeBringUp], default OFF).
*
* Safeguard for netcode go-live: with the switch OFF the coordinator still owns a
* [com.aether.mofe.engine.netcode.NetcodeSession], but the live seam is inert — no
* peer fused samples are folded in, the claim/verdict transport never starts, and
* [MeshCoordinator.emitClaims] puts nothing on the control plane. So a netcode
* bring-up defect cannot regress ranging, the solve, or Raft. Flipping it ON
* restores full go-live behaviour (covered end to end by NetcodeAssemblyTest /
* NetcodeClaimEmissionTest / NetcodeArbitrationTest).
*
* Both cases deliver the SAME peer sample under opposite flag states and assert
* opposite outcomes, so the gate — not some incidental wiring — is what makes
* netcode live.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class NetcodeBringUpGateTest {
private val meshT = 50_000_000L
/** A peer's mesh-time-stamped fused sample arriving on [leaderHost]'s data plane. */
private suspend fun ClusterHarness.deliverPeerSample(leaderHost: String, leaderPort: Int) {
val svc = services[leaderHost]!!
val env = MessageEnvelope(
messageId = "m1", sourceNodeId = "peerX", channel = MeshChannel.RANGING,
sequence = 1L, sentAtMicros = meshT, meshEpoch = svc.currentEpoch,
payload = FusedStateSample(listOf(WireFusedSample(
targetId = "peerX", timestampMicros = meshT,
position = Vector3D(3.0, 4.0, 0.0), velocity = Vector3D(1.0, 0.0, 0.0),
positionSigma = Vector3D(0.1, 0.1, 0.1),
))),
)
ether.endpoint("peerX", 5555).send(NodeAddress(leaderHost, leaderPort), MeshCodec.encode(env))
}
@Test
fun default_off_keeps_the_netcode_seam_inert() = runTest {
val h = ClusterHarness(this) // default: netcodeBringUp = false
val leader = h.bootstrapLeader("root", 9941)
h.pump(5)
h.deliverPeerSample("root", 9941)
h.pump(5)
// The fused-sample collector never launched, so nothing reached the session.
assertNull(
h.coords["root"]!!.netcode.poseAt(DeviceId("peerX"), Timestamp(meshT)),
"with bring-up OFF the peer's sample is not folded into the netcode session",
)
assertEquals(false, leader.netcodeBringUpEnabled)
// And emitClaims puts nothing on the control plane / tracks nothing.
leader.emitClaims(TestFactories.regionEntry("zone-1", "target-7", meshT))
h.pump(5)
assertEquals(0, leader.claimTracker.pendingCount(), "with bring-up OFF no claim is emitted or tracked")
}
@Test
fun bring_up_on_makes_the_same_sample_live() = runTest {
val h = ClusterHarness(this, netcodeBringUp = true)
val leader = h.bootstrapLeader("root", 9942)
h.pump(5)
h.deliverPeerSample("root", 9942)
h.pump(5)
val pose = assertNotNull(
h.coords["root"]!!.netcode.poseAt(DeviceId("peerX"), Timestamp(meshT)),
"with bring-up ON the same peer sample is folded into the session",
)
assertEquals(3.0, pose.position.x, 1e-9)
assertEquals(4.0, pose.position.y, 1e-9)
assertEquals(true, leader.netcodeBringUpEnabled)
}
}
common/src/commonTest/kotlin/com/aether/mofe/messaging/NetcodeClaimEmissionTest.kt
@Test
fun memberEmitsClaimToLeaderAndTracksItPending() = runTest {
val h = ClusterHarness(this)
val h = ClusterHarness(this, netcodeBringUp = true) // exercising the netcode go-live path
h.bootstrapLeader("root", 9911); runCurrent()
val member = h.joinAsMember("client", 9912, leaderHost = "root")
h.pump(40) // membership handshake
common/src/commonTest/kotlin/com/aether/mofe/messaging/support/ClusterHarness.kt
import com.aether.mofe.messaging.MeshCoordinator
import com.aether.mofe.messaging.MeshMessagingService
import com.aether.mofe.messaging.MeshStateRegistry
import com.aether.mofe.messaging.MessagingConfig
import com.aether.mofe.messaging.NodeAddress
import com.aether.mofe.messaging.raft.InMemoryRaftPersistence
import com.aether.mofe.messaging.security.GroupKeyManager
......
class ClusterHarness(
private val test: TestScope,
startMicros: Long = 1_000L,
/** Enable the netcode seam on every node built by this harness (default OFF,
* matching production). Go-live tests opt in; mesh/Raft scenarios leave it off. */
private val netcodeBringUp: Boolean = false,
) {
val clock = VirtualClock(startMicros)
val bus = VirtualControlBus()
......
val coord = MeshCoordinator(
selfId = DeviceId(id), engine = engine(), messaging = messaging, registry = registry,
clock = clock, scope = nodeScope, persistence = InMemoryRaftPersistence(), groupKeys = groupKeys,
config = MessagingConfig(netcodeBringUp = netcodeBringUp),
)
coords[id] = coord; registries[id] = registry; services[id] = messaging
scopes[id] = nodeScope; ports[id] = port
    (1-1/1)