User Story #59 » 0004-feat-ui-surface-placement-geometry-quality-in-MESH-D.patch
| androidApp/src/main/java/com/aether/mofe/platform/MofeEngineHost.kt | ||
|---|---|---|
|
* (an anchor dropped, healing). Advertises frame-stability degradation and its recovery. */
|
||
|
val constellationFrame: StateFlow<ConstellationFrameState> = _constellationFrame.asStateFlow()
|
||
|
private val _geometryReport = MutableStateFlow<com.aether.mofe.engine.ConstellationGeometryReport?>(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<com.aether.mofe.engine.ConstellationGeometryReport?> = _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 {
|
||
| ... | ... | |
|
// 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()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
| androidApp/src/main/java/com/aether/mofe/ui/AppShell.kt | ||
|---|---|---|
|
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) }
|
||
| ... | ... | |
|
anchorLinks = anchorLinks,
|
||
|
antennaDelays = antennaDelays,
|
||
|
frameState = constellationFrame,
|
||
|
geometryReport = geometryReport,
|
||
|
)
|
||
|
}
|
||
|
}
|
||
| androidApp/src/main/java/com/aether/mofe/ui/MeshHealthPane.kt | ||
|---|---|---|
|
// 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 }
|
||
| ... | ... | |
|
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.
|
||
| ... | ... | |
|
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)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
- « Previous
- 1
- 2
- Next »