docs: network topology + mDNS hostnames

Add a "Network topology" section to top-level CLAUDE.md doc
the 3-host layout (GrosMac source, Supra-M1 sink via mDNS,
iPhone via Personal Hotspot DHCP).

mDNS is canonical now : AVBODY_HOST and MULTIHMR_REMOTE_HOST
accept hostname.local instead of IPs, so the cluster survives
DHCP rotations on iPhone hotspot (172.20.10.x).
This commit is contained in:
L'électron rare
2026-05-14 12:47:51 +02:00
parent d122c93d19
commit a769bcd7ae
@@ -10,64 +10,113 @@ struct ContentView: View {
@State private var sendEnvMesh: Bool = false
var body: some View {
ZStack(alignment: .topLeading) {
ARViewContainer(session: session)
.ignoresSafeArea()
VStack(alignment: .leading, spacing: 8) {
Text("AR Body → AV-Live")
.font(.headline)
.foregroundColor(.white)
HStack {
Text("Host").foregroundColor(.white)
TextField("GrosMac IP", text: $host)
.keyboardType(.numbersAndPunctuation)
.textFieldStyle(.roundedBorder)
}
HStack {
Text("Py").foregroundColor(.white)
TextField("57128", text: $pythonPort)
.keyboardType(.numberPad)
.textFieldStyle(.roundedBorder)
.frame(width: 70)
Text("Swift").foregroundColor(.white)
TextField("57129", text: $swiftPort)
.keyboardType(.numberPad)
.textFieldStyle(.roundedBorder)
.frame(width: 70)
}
Toggle(isOn: $sendEnvMesh) {
Text("Env mesh (LiDAR)").foregroundColor(.white)
}
HStack {
Button(session.running ? "Stop" : "Start") {
if session.running {
session.stop()
} else {
session.configure(
host: host,
pythonPort: UInt16(pythonPort) ?? 57128,
swiftPort: UInt16(swiftPort) ?? 57129,
sendEnvMesh: sendEnvMesh)
session.start()
}
GeometryReader { geo in
ZStack(alignment: .topLeading) {
ARViewContainer(session: session)
.ignoresSafeArea()
SkeletonOverlay(snapshot: session.skeleton2D,
parents: session.bodyParentIndices)
.ignoresSafeArea()
.allowsHitTesting(false)
controlPanel
}
.onAppear { session.viewportSize = geo.size }
.onChange(of: geo.size) { _, newSize in
session.viewportSize = newSize
}
}
}
private var controlPanel: some View {
VStack(alignment: .leading, spacing: 8) {
Text("AR Body → AV-Live")
.font(.headline)
.foregroundColor(.white)
HStack {
Text("Host").foregroundColor(.white)
TextField("GrosMac IP", text: $host)
.keyboardType(.numbersAndPunctuation)
.textFieldStyle(.roundedBorder)
}
HStack {
Text("Py").foregroundColor(.white)
TextField("57128", text: $pythonPort)
.keyboardType(.numberPad)
.textFieldStyle(.roundedBorder)
.frame(width: 70)
Text("Swift").foregroundColor(.white)
TextField("57129", text: $swiftPort)
.keyboardType(.numberPad)
.textFieldStyle(.roundedBorder)
.frame(width: 70)
}
Toggle(isOn: $sendEnvMesh) {
Text("Env mesh (LiDAR)").foregroundColor(.white)
}
HStack {
Button(session.running ? "Stop" : "Start") {
if session.running {
session.stop()
} else {
session.configure(
host: host,
pythonPort: UInt16(pythonPort) ?? 57128,
swiftPort: UInt16(swiftPort) ?? 57129,
sendEnvMesh: sendEnvMesh)
session.start()
}
.buttonStyle(.borderedProminent)
Spacer()
Text(session.status)
.font(.caption)
.foregroundColor(.white)
.padding(6)
.background(.black.opacity(0.5))
.cornerRadius(6)
}
Text("frames: \(session.framesSent) joints/s: \(Int(session.jointsPerSec))")
.font(.caption2)
.buttonStyle(.borderedProminent)
Spacer()
Text(session.status)
.font(.caption)
.foregroundColor(.white)
.padding(6)
.background(.black.opacity(0.5))
.cornerRadius(6)
}
Text("bodies: \(session.bodyCount) frames: \(session.framesSent) joints/s: \(Int(session.jointsPerSec))")
.font(.caption2)
.foregroundColor(.white)
}
.padding(12)
.background(.black.opacity(0.5))
.cornerRadius(10)
.padding()
}
}
/// Draws ARKit body joints + bones over the camera view. Bones are
/// derived from `ARSkeletonDefinition.defaultBody3D` parent indices.
struct SkeletonOverlay: View {
let snapshot: SkeletonSnapshot?
let parents: [Int]
var body: some View {
Canvas { ctx, _ in
guard let snap = snapshot else { return }
for (i, parent) in parents.enumerated() where parent >= 0 {
guard i < snap.points.count, parent < snap.points.count,
let a = snap.points[i], let b = snap.points[parent]
else { continue }
var path = Path()
path.move(to: a)
path.addLine(to: b)
let solid = snap.tracked[i] && snap.tracked[parent]
ctx.stroke(
path,
with: .color(solid ? .green : .yellow.opacity(0.5)),
lineWidth: solid ? 2 : 1.2)
}
for (i, pt) in snap.points.enumerated() {
guard let pt else { continue }
let r: CGFloat = snap.tracked[i] ? 4 : 2.5
let rect = CGRect(x: pt.x - r, y: pt.y - r,
width: r * 2, height: r * 2)
ctx.fill(Path(ellipseIn: rect),
with: .color(snap.tracked[i]
? .cyan : .yellow.opacity(0.8)))
}
.padding(12)
.background(.black.opacity(0.5))
.cornerRadius(10)
.padding()
}
}
}