From b3d66489e53e2745d24447a4be82c3fbc56568aa Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 11:38:13 +0000 Subject: [PATCH 04/10] feat(ui): surface placement-geometry quality in MESH DIAGNOSTICS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renders the engine's ConstellationGeometryReport (patch 0003) so a bad LAYOUT is visible to the operator instead of read as a mysterious never-latching / twisted frame. MofeEngineHost.geometryReport StateFlow (refreshed on the maintenance tick, mirroring anchorLinks/antennaDelays), collected in AppShell and passed to MeshHealthPane, which shows a "PLACEMENT QUALITY" card — a GOOD/MARGINAL/DEGENERATE pill, the links-vs-rigidity + raised-anchor summary, and the actionable issue list (each with its one-line fix). Shown only when actionable (not GOOD), placed right under the frame-state banner so a placement problem reads before the link detail. androidApp is not compilable in the headless env — delivered as a reviewed wiring patch; the engine half it renders is jvmTest-verified. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01WppuiKZt4CuQxX4N7k6SVR --- .../aether/mofe/platform/MofeEngineHost.kt | 8 +++ .../main/java/com/aether/mofe/ui/AppShell.kt | 2 + .../java/com/aether/mofe/ui/MeshHealthPane.kt | 53 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/androidApp/src/main/java/com/aether/mofe/platform/MofeEngineHost.kt b/androidApp/src/main/java/com/aether/mofe/platform/MofeEngineHost.kt index e59875a..87043fc 100644 --- a/androidApp/src/main/java/com/aether/mofe/platform/MofeEngineHost.kt +++ b/androidApp/src/main/java/com/aether/mofe/platform/MofeEngineHost.kt @@ -129,6 +129,12 @@ class MofeEngineHost( * (an anchor dropped, healing). Advertises frame-stability degradation and its recovery. */ val constellationFrame: StateFlow = _constellationFrame.asStateFlow() + private val _geometryReport = MutableStateFlow(null) + /** Placement/GEOMETRY quality of the anchor layout (GOOD/MARGINAL/DEGENERATE + actionable issues) + * for the mesh-health UI — the "is this layout solvable and orientable" signal, distinct from + * ranging health ([anchorLinks]). Refreshed on the maintenance tick; null before a topology. */ + val geometryReport: StateFlow = _geometryReport.asStateFlow() + /** Instrumentation + state capture — the engine's multi-observer fusion output, * surfaced to logcat AND to the flows above for the HUD/health pane to consume. */ private val logListener = object : MofeListener { @@ -240,6 +246,8 @@ class MofeEngineHost( // Frame-lock lifecycle (LOCKED / RESOLVING_MOTION / …) for the mesh-health UI. _constellationFrame.value = runCatching { eng.constellationFrameState() } .getOrDefault(ConstellationFrameState.CONVERGING) + // Placement/geometry quality (solvable/orientable) for the mesh-health UI. + _geometryReport.value = runCatching { eng.constellationGeometryReport() }.getOrNull() } } } diff --git a/androidApp/src/main/java/com/aether/mofe/ui/AppShell.kt b/androidApp/src/main/java/com/aether/mofe/ui/AppShell.kt index 3f2d68b..97ef13f 100644 --- a/androidApp/src/main/java/com/aether/mofe/ui/AppShell.kt +++ b/androidApp/src/main/java/com/aether/mofe/ui/AppShell.kt @@ -113,6 +113,7 @@ fun AppShell(app: AetherApp, hud: HudViewModel) { val anchorLinks by app.mofeEngineHost.anchorLinks.collectAsState() val antennaDelays by app.mofeEngineHost.antennaDelays.collectAsState() val constellationFrame by app.mofeEngineHost.constellationFrame.collectAsState() + val geometryReport by app.mofeEngineHost.geometryReport.collectAsState() val realizerScope = rememberCoroutineScope() val realizerVm = remember { PredicateRealizerViewModel(app.meshRepository, realizerScope) } @@ -262,6 +263,7 @@ fun AppShell(app: AetherApp, hud: HudViewModel) { anchorLinks = anchorLinks, antennaDelays = antennaDelays, frameState = constellationFrame, + geometryReport = geometryReport, ) } } diff --git a/androidApp/src/main/java/com/aether/mofe/ui/MeshHealthPane.kt b/androidApp/src/main/java/com/aether/mofe/ui/MeshHealthPane.kt index cc646f3..ad9a1cd 100644 --- a/androidApp/src/main/java/com/aether/mofe/ui/MeshHealthPane.kt +++ b/androidApp/src/main/java/com/aether/mofe/ui/MeshHealthPane.kt @@ -67,6 +67,10 @@ fun MeshHealthPane( // Constellation frame lifecycle (CONVERGING / LOCKED / RESOLVING* on a bump) — drives the // re-solve banner and re-keys the constellation layout so it refreshes when the frame re-locks. frameState: ConstellationFrameState = ConstellationFrameState.CONVERGING, + // Placement/GEOMETRY quality of the anchor layout — flags a LAYOUT problem (too few links, no + // raised anchor) distinct from ranging health, so a bad placement is caught here instead of read + // as a mysterious never-latching / twisted frame. Null before a topology exists. + geometryReport: com.aether.mofe.engine.ConstellationGeometryReport? = null, ) { val now = System.currentTimeMillis() val mpById = snapshot.meshPoints.associateBy { it.id } @@ -109,6 +113,15 @@ fun MeshHealthPane( item { FrameStateBanner(frameState) } } + // PLACEMENT / GEOMETRY QUALITY — a LAYOUT problem (too few inter-anchor links to be rigid, or + // no clearly-raised anchor so the frame can't level cleanly) reads DEGENERATE/MARGINAL here + // even when every link is green. Shown only when actionable, with the fix for each issue, so + // the operator corrects the placement instead of chasing a frame that can never settle. + geometryReport?.takeIf { it.quality != com.aether.mofe.engine.GeometryQuality.GOOD && it.issues.isNotEmpty() }?.let { rep -> + item { SectionHeader("PLACEMENT QUALITY", rep.anchorCount) } + item { PlacementQualityCard(rep) } + } + // INTER-ANCHOR LINK HEALTH — the constellation (colored links) + a list it filters. Default // list shows only weak/missing; tapping a node shows all its links (worst first); tapping // empty space resets. Antenna-delay calibration rides underneath. @@ -440,4 +453,44 @@ private fun SectionHeader(text: String, count: Int) { modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)) } } +} + +/** Placement/geometry quality card: a GOOD/MARGINAL/DEGENERATE pill + the actionable issue list (each + * with its one-line fix). This is the "your LAYOUT is the problem, not the ranging" signal. */ +@Composable +private fun PlacementQualityCard(report: com.aether.mofe.engine.ConstellationGeometryReport) { + val (color, label) = when (report.quality) { + com.aether.mofe.engine.GeometryQuality.GOOD -> AetherColors.Healthy to "GOOD" + com.aether.mofe.engine.GeometryQuality.MARGINAL -> AetherColors.Warning to "MARGINAL" + com.aether.mofe.engine.GeometryQuality.DEGENERATE -> AetherColors.Critical to "DEGENERATE" + } + Surface(color = AetherColors.Surface, shape = RoundedCornerShape(14.dp), modifier = Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Straighten, null, tint = color, modifier = Modifier.size(16.dp)) + Spacer(Modifier.width(8.dp)) + Text("ANCHOR LAYOUT", color = AetherColors.TextDim, fontFamily = FontFamily.Monospace, + fontSize = 9.sp, letterSpacing = 1.sp) + Spacer(Modifier.weight(1f)) + Surface(color = color.copy(alpha = 0.18f), shape = RoundedCornerShape(50)) { + Text(label, color = color, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold, + fontSize = 10.sp, letterSpacing = 0.5.sp, + modifier = Modifier.padding(horizontal = 9.dp, vertical = 3.dp)) + } + } + Text("${report.edgeCount}/${report.edgesForRigidity} links · " + + (if (report.hasRaisedAnchor) "raised anchor ✓" else "no raised anchor"), + color = AetherColors.TextDim, fontFamily = FontFamily.Monospace, fontSize = 9.sp, + modifier = Modifier.padding(top = 4.dp)) + Spacer(Modifier.height(8.dp)) + report.issues.forEach { issue -> + Row(Modifier.padding(vertical = 3.dp)) { + Text("•", color = color, fontFamily = FontFamily.Monospace, fontSize = 11.sp) + Spacer(Modifier.width(8.dp)) + Text(issue, color = AetherColors.TextSecondary, fontFamily = FontFamily.Monospace, + fontSize = 10.sp, lineHeight = 14.sp) + } + } + } + } } \ No newline at end of file -- 2.43.0