This commit is contained in:
legop3
2026-05-01 23:40:47 -04:00
parent 03cd8c00dd
commit 0f97382f42
4 changed files with 63 additions and 47 deletions
+2 -4
View File
@@ -3,7 +3,6 @@ You are The Overseer, an unserious collaborative rover co-host in chat.
Output contract: Output contract:
- Return exactly one line. - Return exactly one line.
- Output must be either SKIP or one chat message. - Output must be either SKIP or one chat message.
- Default length is 140 chars or less.
- If directly addressed with a request that clearly needs more detail, you may use up to 280 chars. - If directly addressed with a request that clearly needs more detail, you may use up to 280 chars.
- No emojis, no markdown, no extra lines, no assistant framing. - No emojis, no markdown, no extra lines, no assistant framing.
- Don't talk to the same person with a generic message more than once. - Don't talk to the same person with a generic message more than once.
@@ -29,7 +28,6 @@ Environment brief (stable facts):
- On the carpet side, three docks are mounted on a white wooden beam in front of the TV stand. - On the carpet side, three docks are mounted on a white wooden beam in front of the TV stand.
- A phone button to "call Carpet" is mounted on that same beam. - A phone button to "call Carpet" is mounted on that same beam.
- Near the carpet-side shelves: a small TV/laptop plays live TV. - Near the carpet-side shelves: a small TV/laptop plays live TV.
- To the right is a Roomba-accessible controller station where users can play Peggle.
- On the concrete side, a workbench has an additional dock. - On the concrete side, a workbench has an additional dock.
- Common room objects users reference: - Common room objects users reference:
- large green cardboard "minecraft slime" box - large green cardboard "minecraft slime" box
@@ -38,7 +36,7 @@ Environment brief (stable facts):
- two blue balls (one very large, one smaller) - two blue balls (one very large, one smaller)
- long snake plushie - long snake plushie
- laptop that can be run over - laptop that can be run over
- monitor usually showing a Chromecast screensaver - monitor with a broken screen
Rover context hints: Rover context hints:
- CHAT `rover_now` and SNAPSHOT FINAL include qualitative tags: - CHAT `rover_now` and SNAPSHOT FINAL include qualitative tags:
@@ -60,6 +58,7 @@ When to skip:
- If your line is generic and reusable across many ticks, output SKIP. - If your line is generic and reusable across many ticks, output SKIP.
- If you would repeat the same topic with no new angle, output SKIP. - If you would repeat the same topic with no new angle, output SKIP.
- Quiet periods with no active chat should mostly be SKIP. - Quiet periods with no active chat should mostly be SKIP.
- If you are talking about the same rover + person combo, output SKIP.
Freshness and anti-repeat: Freshness and anti-repeat:
- Check prior assistant messages in the timeline before speaking. - Check prior assistant messages in the timeline before speaking.
@@ -71,7 +70,6 @@ Freshness and anti-repeat:
Grounding: Grounding:
- Use timeline for flow. - Use timeline for flow.
- Use SNAPSHOT FINAL as current truth. - Use SNAPSHOT FINAL as current truth.
- Do not invent facts.
- Do not assume user intent or next actions. - Do not assume user intent or next actions.
Anti-announcer rule: Anti-announcer rule:
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title> <title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-DW9KJfni.js"></script> <script type="module" crossorigin src="/assets/index-7t2kb-aG.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CGvUXLso.css"> <link rel="stylesheet" crossorigin href="/assets/index-CGvUXLso.css">
</head> </head>
<body> <body>
+51 -33
View File
@@ -38,21 +38,49 @@ function StatusTile({ label, value, tone = 'muted', valueClass = '', hideLabel =
); );
} }
function buildLidarDots(points = []) { function buildLidarRenderPoints(points = []) {
const center = 110; const center = 110;
const radius = 92; const plotRadius = 102;
const maxDistanceMm = 4000; const validPoints = points.filter(
return points (point) => point && point.valid && Number.isFinite(point.angleDeg) && Number.isFinite(point.distanceMm),
.filter((point) => point && point.valid && Number.isFinite(point.angleDeg) && Number.isFinite(point.distanceMm)) );
const sortedPoints = [...validPoints].sort((a, b) => Number(a.angleDeg) - Number(b.angleDeg));
const filteredPoints = sortedPoints.filter((point, index, list) => {
if (list.length < 3) return true;
const distanceMm = Math.max(0, Number(point.distanceMm));
const prev = list[(index - 1 + list.length) % list.length];
const next = list[(index + 1) % list.length];
const prevDistanceMm = Math.max(0, Number(prev.distanceMm));
const nextDistanceMm = Math.max(0, Number(next.distanceMm));
const neighborDistanceMm = Math.max(prevDistanceMm, nextDistanceMm);
// Drop single-point radial spikes that sit far beyond both adjacent angle samples.
if (neighborDistanceMm <= 0) return true;
const muchFartherThanNeighbors = distanceMm > neighborDistanceMm * 1.85;
const largeAbsoluteGap = distanceMm - neighborDistanceMm > 1400;
return !(muchFartherThanNeighbors && largeAbsoluteGap);
});
const scaleDistances = filteredPoints.map((point) => Math.max(0, Number(point.distanceMm))).sort((a, b) => a - b);
const percentileIndex = Math.max(0, Math.floor((scaleDistances.length - 1) * 0.95));
const maxDistanceMm = Math.max(1000, scaleDistances[percentileIndex] || 1000);
return filteredPoints
.map((point) => { .map((point) => {
const angleRad = ((Number(point.angleDeg) - 90) * Math.PI) / 180; const angleRad = ((Number(point.angleDeg) - 90) * Math.PI) / 180;
const normalized = Math.max(0, Math.min(1, Number(point.distanceMm) / maxDistanceMm)); const distanceMm = Math.max(0, Number(point.distanceMm));
const scaledRadius = normalized * radius; const normalizedDistance = Math.max(0, Math.min(1, distanceMm / maxDistanceMm));
const scaledRadius = normalizedDistance * plotRadius;
const x = center + Math.cos(angleRad) * scaledRadius; const x = center + Math.cos(angleRad) * scaledRadius;
const y = center + Math.sin(angleRad) * scaledRadius; const y = center + Math.sin(angleRad) * scaledRadius;
return `${x},${y}`; const intensity = Math.max(0, Math.min(255, Number(point.intensity) || 0));
}) const colorLightness = 38 + (intensity / 255) * 38;
.join(' '); return {
key: `${point.angleDeg}-${point.distanceMm}-${point.intensity}`,
x,
y,
fill: `hsl(198 100% ${colorLightness}%)`,
};
});
} }
export default function VipNeatoCard({ export default function VipNeatoCard({
@@ -84,7 +112,7 @@ export default function VipNeatoCard({
const robotError = normalizeState(neato?.telemetry?.robotError) || '--'; const robotError = normalizeState(neato?.telemetry?.robotError) || '--';
const robotAlert = normalizeState(neato?.telemetry?.robotAlert) || '--'; const robotAlert = normalizeState(neato?.telemetry?.robotAlert) || '--';
const lidarPoints = Array.isArray(lidar?.points) ? lidar.points : []; const lidarPoints = Array.isArray(lidar?.points) ? lidar.points : [];
const lidarDots = buildLidarDots(lidarPoints); const lidarRenderPoints = buildLidarRenderPoints(lidarPoints);
const lidarStatus = normalizeState(lidar?.status) || '--'; const lidarStatus = normalizeState(lidar?.status) || '--';
const lidarDebug = lidar?.debug && typeof lidar.debug === 'object' ? lidar.debug : null; const lidarDebug = lidar?.debug && typeof lidar.debug === 'object' ? lidar.debug : null;
const lidarReason = normalizeState(lidarDebug?.reason) || '--'; const lidarReason = normalizeState(lidarDebug?.reason) || '--';
@@ -249,30 +277,20 @@ export default function VipNeatoCard({
Lidar Lidar
</button> </button>
<div className="mt-0.25 text-center text-[0.68rem] text-slate-400">Front of robot</div> <div className="mt-0.25 text-center text-[0.68rem] text-slate-400">Front of robot</div>
<div className="mt-0.25 aspect-square rounded-md bg-slate-900 p-0.25"> <div className="mt-0.25 aspect-square rounded-md bg-slate-800 p-0">
{lidarDots ? ( {lidarRenderPoints.length ? (
<svg viewBox="0 0 220 220" className="h-full w-full"> <svg viewBox="0 0 220 220" className="h-full w-full">
<rect x="0" y="0" width="220" height="220" fill="#0f172a" /> <rect x="0" y="0" width="220" height="220" fill="#1e293b" />
<circle cx="110" cy="110" r="23" fill="none" stroke="#1e293b" strokeWidth="1" /> <rect x="7" y="7" width="206" height="206" fill="none" stroke="#64748b" strokeWidth="1" />
<circle cx="110" cy="110" r="46" fill="none" stroke="#334155" strokeWidth="1" /> <rect x="33" y="33" width="154" height="154" fill="none" stroke="#475569" strokeWidth="1" />
<circle cx="110" cy="110" r="69" fill="none" stroke="#334155" strokeWidth="1" /> <rect x="59" y="59" width="102" height="102" fill="none" stroke="#475569" strokeWidth="1" />
<circle cx="110" cy="110" r="92" fill="none" stroke="#475569" strokeWidth="1" /> <rect x="85" y="85" width="50" height="50" fill="none" stroke="#334155" strokeWidth="1" />
<line x1="110" y1="18" x2="110" y2="202" stroke="#334155" strokeWidth="1" /> <line x1="110" y1="7" x2="110" y2="213" stroke="#475569" strokeWidth="1" />
<line x1="18" y1="110" x2="202" y2="110" stroke="#334155" strokeWidth="1" /> <line x1="7" y1="110" x2="213" y2="110" stroke="#475569" strokeWidth="1" />
<circle cx="110" cy="110" r="4" fill="#e2e8f0" /> <circle cx="110" cy="110" r="4" fill="#e2e8f0" />
{lidarPoints {lidarRenderPoints.map((point) => (
.filter( <circle key={point.key} cx={point.x} cy={point.y} r="1.8" fill={point.fill} />
(point) => ))}
point && point.valid && Number.isFinite(point.angleDeg) && Number.isFinite(point.distanceMm),
)
.map((point) => {
const angleRad = ((Number(point.angleDeg) - 90) * Math.PI) / 180;
const normalized = Math.max(0, Math.min(1, Number(point.distanceMm) / 4000));
const scaledRadius = normalized * 92;
const x = 110 + Math.cos(angleRad) * scaledRadius;
const y = 110 + Math.sin(angleRad) * scaledRadius;
return <circle key={`${point.angleDeg}-${point.distanceMm}-${point.intensity}`} cx={x} cy={y} r="1.6" fill="#38bdf8" />;
})}
</svg> </svg>
) : ( ) : (
<div className="flex h-full items-center justify-center text-center text-xs text-slate-400"> <div className="flex h-full items-center justify-center text-center text-xs text-slate-400">