Expand our repeat-watch time

This commit is contained in:
Jack Kingsman
2026-01-19 17:18:03 -08:00
parent 4bc51f82da
commit ff6b25c0e5
7 changed files with 581 additions and 550 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -13,8 +13,8 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<script type="module" crossorigin src="/assets/index-BBzeYRV9.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Ty42XaBr.css">
<script type="module" crossorigin src="/assets/index-Pr4PQ17F.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bg6UtK9Z.css">
</head>
<body>
<div id="root"></div>
+35 -4
View File
@@ -108,7 +108,7 @@ const PARTICLE_COLOR_MAP: Record<PacketLabel, string> = {
};
const PARTICLE_SPEED = 0.008;
const OBSERVATION_WINDOW_MS = 2000;
const DEFAULT_OBSERVATION_WINDOW_SEC = 15;
const FORTY_EIGHT_HOURS_MS = 48 * 60 * 60 * 1000;
const LEGEND_ITEMS = [
@@ -254,6 +254,7 @@ interface UseVisualizerDataOptions {
chargeStrength: number;
letEmDrift: boolean;
particleSpeedMultiplier: number;
observationWindowSec: number;
dimensions: { width: number; height: number };
}
@@ -276,6 +277,7 @@ function useVisualizerData({
chargeStrength,
letEmDrift,
particleSpeedMultiplier,
observationWindowSec,
dimensions,
}: UseVisualizerDataOptions): VisualizerData {
const nodesRef = useRef<Map<string, GraphNode>>(new Map());
@@ -286,13 +288,18 @@ function useVisualizerData({
const pendingRef = useRef<Map<string, PendingPacket>>(new Map());
const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
const speedMultiplierRef = useRef(particleSpeedMultiplier);
const observationWindowRef = useRef(observationWindowSec * 1000);
const [stats, setStats] = useState({ processed: 0, animated: 0, nodes: 0, links: 0 });
// Keep speed multiplier ref in sync with prop
// Keep refs in sync with props
useEffect(() => {
speedMultiplierRef.current = particleSpeedMultiplier;
}, [particleSpeedMultiplier]);
useEffect(() => {
observationWindowRef.current = observationWindowSec * 1000;
}, [observationWindowSec]);
// Initialize simulation
useEffect(() => {
const sim = forceSimulation<GraphNode, GraphLink>([])
@@ -657,16 +664,17 @@ function useVisualizerData({
if (timersRef.current.has(packetKey)) {
clearTimeout(timersRef.current.get(packetKey));
}
const windowMs = observationWindowRef.current;
pendingRef.current.set(packetKey, {
key: packetKey,
label: getPacketLabel(parsed.payloadType),
paths: [{ nodes: path, snr: packet.snr ?? null, timestamp: now }],
firstSeen: now,
expiresAt: now + OBSERVATION_WINDOW_MS,
expiresAt: now + windowMs,
});
timersRef.current.set(
packetKey,
setTimeout(() => publishPacket(packetKey), OBSERVATION_WINDOW_MS)
setTimeout(() => publishPacket(packetKey), windowMs)
);
}
@@ -951,6 +959,7 @@ export function PacketVisualizer({
const [showAmbiguousNodes, setShowAmbiguousNodes] = useState(false);
const [chargeStrength, setChargeStrength] = useState(-200);
const [filterOldRepeaters, setFilterOldRepeaters] = useState(false);
const [observationWindowSec, setObservationWindowSec] = useState(DEFAULT_OBSERVATION_WINDOW_SEC);
const [letEmDrift, setLetEmDrift] = useState(true);
const [particleSpeedMultiplier, setParticleSpeedMultiplier] = useState(3);
const [hideUI, setHideUI] = useState(false);
@@ -973,6 +982,7 @@ export function PacketVisualizer({
chargeStrength,
letEmDrift,
particleSpeedMultiplier,
observationWindowSec,
dimensions,
});
@@ -1210,6 +1220,27 @@ export function PacketVisualizer({
Hide repeaters &gt;48hrs heard
</span>
</label>
<div className="flex items-center gap-2">
<label
className="text-muted-foreground"
title="How long to wait for duplicate packets via different paths before animating"
>
Observation window:
</label>
<input
type="number"
min="1"
max="60"
value={observationWindowSec}
onChange={(e) =>
setObservationWindowSec(
Math.max(1, Math.min(60, parseInt(e.target.value) || 1))
)
}
className="w-12 px-1 py-0.5 bg-background border border-border rounded text-xs text-center"
/>
<span className="text-muted-foreground">sec</span>
</div>
<div className="border-t border-border pt-2 mt-1 flex flex-col gap-2">
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox