fix(rotor): Stop no longer blinks through a rotation
The direction test that kept the wind out was applied to staying in as well, and there it was wrong. Its unit is a four-degree step, which is four seconds of travel at a degree a second — far longer than the 1600 ms settle window — so between two accepted steps nothing said the mast was still turning, the timer expired, and Stop went dark and lit again the whole way round. Entry stays strict: two consecutive steps of four degrees the same way, which is what weather cannot produce. Staying in is now sensitive instead — any continued progress in the direction already established re-arms the timer, down to a single degree, because one degree the same way is not a gust when the rotor is demonstrably under power. That demonstration is exactly what the strict entry provides, so the loose exit costs nothing: wind still cannot start it, and once stopped the sensitive path is disarmed with the direction. The settle window goes to 3000 ms so it outlasts one degree of progress on a slow mast rather than four degrees on a fast one. Measuring progress needs the PREVIOUS reading, which movementReferenceRef is not: it deliberately holds still through sub-threshold steps so they can accumulate into one.
This commit is contained in:
+4
-2
@@ -14,7 +14,8 @@
|
||||
"The grid-square map filters by one mode, by band and by satellite. The FTx button is gone: it grouped FT8, FT4 and FT2 together, while the question the map answers is where a single mode has been heard. The named modes are read from the log itself, so each one is offered only if there is something behind it — and FT2 appears for whoever is already using it, with nothing to change here the day it becomes a registered mode. The band list is the station's own plus anything worked outside it, and the satellite list only appears once a square has been worked through a bird.",
|
||||
"Saving the satellite settings now refreshes the Satellites tab. It read the followed list once when it was opened, so un-following a satellite left it in the pass table and in the dropdown until the tab was reopened — which looks exactly like a setting that failed to save. The lowest pass, the window and the locator are picked up the same way, and a satellite that is no longer followed hands the selection to the first one that is.",
|
||||
"The FT map takes one colour for every decode, next to the basemap buttons. The band palette is calibrated against a plain map — 60m navy and 70cm olive all but vanish over the satellite imagery — and it is only useful to somebody watching several bands at once, which most operators are not. Leave it empty for the colour per band, as before. The legend then keeps the band names and drops the swatches, since which bands are up is still worth knowing.",
|
||||
"\"Later\" on an update notice now asks for how long: 1, 4, 12 or 24 hours. It only hid the card before, and the check behind it runs every five minutes, so the same notice came back four times an hour all evening for a version already declined. The delay is recorded against that version, so a newer release is different news and appears at once — a deferral can never bury an update. The cross hides it for an hour, and opening About still answers whatever was deferred, because asking is asking."
|
||||
"\"Later\" on an update notice now asks for how long: 1, 4, 12 or 24 hours. It only hid the card before, and the check behind it runs every five minutes, so the same notice came back four times an hour all evening for a version already declined. The delay is recorded against that version, so a newer release is different news and appears at once — a deferral can never bury an update. The cross hides it for an hour, and opening About still answers whatever was deferred, because asking is asking.",
|
||||
"Stop stays lit for the whole rotation instead of blinking. Keeping the wind out took a four-degree step, but four degrees is four seconds of travel on a real rotor, so between two of them nothing confirmed the mast was still turning and the button went dark and came back the whole way round. Getting in stays strict — two steps the same way, which weather never gives — while staying in now needs only continued progress in that direction: one degree the same way is not a gust when the rotor is already under power."
|
||||
],
|
||||
"fr": [
|
||||
"SteppIR : un bouton Calibrer, à côté de Rétracter. Il amène chaque élément en butée pour que le contrôleur retrouve son zéro — le remède à une antenne qui s’accorde à la mauvaise longueur après une coupure en pleine course, après avoir poussé les éléments à la main, ou après un moteur qui a glissé. Cela prend plusieurs minutes et l’antenne est inutilisable jusqu’à la fin : une confirmation est donc demandée. Les contrôleurs Ultrabeam n’ont pas cette commande et le disent, au lieu de faire semblant. Rétracter les éléments existait déjà et s’explique maintenant : c’est la position de rangement, et le prochain accord les fait ressortir tout seuls.",
|
||||
@@ -28,7 +29,8 @@
|
||||
"La carte des carrés locator se filtre par mode précis, par bande et par satellite. Le bouton FTx disparaît : il regroupait FT8, FT4 et FT2, alors que la question à laquelle répond cette carte est de savoir où un seul mode a été entendu. Les modes proposés sont lus dans le journal, donc chacun n’apparaît que s’il y a quelque chose derrière — et FT2 est proposé à qui l’utilise déjà, sans rien à changer ici le jour où il deviendra un mode officiel. La liste des bandes est celle de la station, complétée par ce qui a été travaillé en dehors, et la liste des satellites n’apparaît qu’une fois un carré travaillé par satellite.",
|
||||
"Enregistrer les réglages satellite rafraîchit désormais l’onglet Satellites. Il lisait la liste des suivis une seule fois à son ouverture : retirer un satellite le laissait dans le tableau des passes et dans la liste déroulante jusqu’à la réouverture de l’onglet — ce qui ressemble exactement à un réglage qui ne s’est pas enregistré. L’élévation minimale, la fenêtre de prévision et le locator sont pris en compte de la même façon, et un satellite qui n’est plus suivi passe la sélection au premier qui l’est.",
|
||||
"La carte FT accepte une couleur unique pour tous les décodages, à côté des boutons de fond de carte. La palette par bande est calibrée sur une carte claire — le bleu marine du 60 m et l’olive du 70 cm disparaissent presque sur l’imagerie satellite — et elle ne sert qu’à qui surveille plusieurs bandes à la fois, ce qui n’est pas le cas de la plupart. Laissez-la vide pour retrouver la couleur par bande. La légende conserve alors les noms de bandes et abandonne les pastilles, car savoir quelles bandes sont ouvertes reste utile.",
|
||||
"« Plus tard » sur une notification de mise à jour demande désormais combien de temps : 1, 4, 12 ou 24 heures. Le bouton ne faisait que masquer la fenêtre, et la vérification derrière tourne toutes les cinq minutes : la même notification revenait quatre fois par heure toute la soirée pour une version déjà refusée. Le report est enregistré pour cette version précise, donc une version plus récente est une autre nouvelle et s’affiche immédiatement — un report ne peut jamais enterrer une mise à jour. La croix masque pendant une heure, et ouvrir « À propos » répond toujours, même sur une version reportée : demander, c’est demander."
|
||||
"« Plus tard » sur une notification de mise à jour demande désormais combien de temps : 1, 4, 12 ou 24 heures. Le bouton ne faisait que masquer la fenêtre, et la vérification derrière tourne toutes les cinq minutes : la même notification revenait quatre fois par heure toute la soirée pour une version déjà refusée. Le report est enregistré pour cette version précise, donc une version plus récente est une autre nouvelle et s’affiche immédiatement — un report ne peut jamais enterrer une mise à jour. La croix masque pendant une heure, et ouvrir « À propos » répond toujours, même sur une version reportée : demander, c’est demander.",
|
||||
"Le bouton Stop reste allumé pendant toute la rotation au lieu de clignoter. Écarter le vent demandait un écart de quatre degrés, mais quatre degrés représentent quatre secondes de course sur un vrai rotor : entre deux d’entre eux, plus rien ne confirmait que le pylône tournait encore et le bouton s’éteignait puis se rallumait tout du long. L’entrée reste stricte — deux écarts dans le même sens, ce que la météo ne produit jamais — tandis que le maintien ne demande plus qu’une progression continue dans ce sens : un degré du même côté n’est pas une rafale quand le rotor est déjà sous tension."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -44,7 +44,15 @@ type RotorPreset = { label: string; azimuth: number };
|
||||
// A rotor is slow and its readout is coarse, so "is it moving" is inferred
|
||||
// rather than reported: a heading that changes by more than a degree means it
|
||||
// is, and it is considered stopped once the readout has been still for a while.
|
||||
const MOVEMENT_SETTLE_MS = 1600;
|
||||
// Long enough to outlast the gap between two readings of a turning rotor.
|
||||
//
|
||||
// It was 1600 ms, shorter than the time a rotor takes to move far enough to be
|
||||
// noticed at all: at a degree a second, four-degree steps are four seconds
|
||||
// apart, so Stop went dark between every one of them and lit again on the
|
||||
// next — a rotation crossing a pass blinked the whole way round. The window
|
||||
// now only has to outlast ONE degree of progress, which even a slow mast
|
||||
// delivers about every two seconds while the poller is running fast.
|
||||
const MOVEMENT_SETTLE_MS = 3000;
|
||||
// Four degrees: the band inside which a reading is not a step at all.
|
||||
//
|
||||
// A rotor at rest does not report a constant heading — the potentiometer and
|
||||
@@ -523,6 +531,10 @@ export function RotorCompass({
|
||||
// in a row have gone that way. Two make it a rotation; one is weather.
|
||||
const movementDirRef = useRef(0);
|
||||
const movementRunRef = useRef(0);
|
||||
// The previous reading, whatever it was. movementReferenceRef deliberately
|
||||
// holds still through sub-threshold steps so they can accumulate, which
|
||||
// makes it useless for measuring progress poll to poll.
|
||||
const lastRawRef = useRef<number | null>(null);
|
||||
|
||||
const rememberTarget = (value: number | null) => {
|
||||
if (value == null) rememberedTargets.delete(rotorKey);
|
||||
@@ -555,6 +567,7 @@ export function RotorCompass({
|
||||
movementSeenRef.current = false;
|
||||
movementDirRef.current = 0;
|
||||
movementRunRef.current = 0;
|
||||
lastRawRef.current = nextAzimuth;
|
||||
window.clearTimeout(movementTimerRef.current);
|
||||
window.clearTimeout(commandTimerRef.current);
|
||||
window.clearTimeout(targetArrivalTimerRef.current);
|
||||
@@ -570,6 +583,25 @@ export function RotorCompass({
|
||||
window.clearTimeout(targetFadeTimerRef.current);
|
||||
}, []);
|
||||
|
||||
// Declare the antenna moving and start the clock on it stopping. Called from
|
||||
// both halves of the test below, and re-arming the timer is the whole point:
|
||||
// the antenna counts as stopped only once nothing has said otherwise for
|
||||
// MOVEMENT_SETTLE_MS.
|
||||
function armMovement() {
|
||||
movementSeenRef.current = true;
|
||||
setIsMoving(true);
|
||||
window.clearTimeout(commandTimerRef.current);
|
||||
window.clearTimeout(movementTimerRef.current);
|
||||
movementTimerRef.current = window.setTimeout(() => {
|
||||
setIsMoving(false);
|
||||
movementSeenRef.current = false;
|
||||
// Forget the direction too: the next real move starts its own run rather
|
||||
// than inheriting one from a rotation that finished minutes ago.
|
||||
movementDirRef.current = 0;
|
||||
movementRunRef.current = 0;
|
||||
}, MOVEMENT_SETTLE_MS);
|
||||
}
|
||||
|
||||
// Movement is inferred from the readout itself, so a rotor turned by its own
|
||||
// controller — or by another program — reads as moving here too.
|
||||
//
|
||||
@@ -586,12 +618,27 @@ export function RotorCompass({
|
||||
// PREVIOUS step went the same way. Wind gives +5, −5, +5 and never two in a
|
||||
// row; a rotor gives +4, +4, +4 and is announced on the second — one poll,
|
||||
// about a second, on a mast that takes half a minute to cross a pass.
|
||||
//
|
||||
// Getting IN is strict; staying in is not, and must not be. The same four
|
||||
// degrees that keep the wind out are four seconds of travel on a real rotor,
|
||||
// so waiting for the next four-degree step before believing it is still
|
||||
// turning left Stop dark for most of the rotation. Once movement is
|
||||
// established, ANY continued progress the way it was going keeps it alive —
|
||||
// one degree the same way is not weather when the mast is already under
|
||||
// power, and the strict test is what guarantees that it is.
|
||||
useEffect(() => {
|
||||
if (rawAzimuth == null) return;
|
||||
// Signed and the short way round: crossing north is a small step, not 350°.
|
||||
const short = (from: number, to: number) => ((to - from + 540) % 360) - 180;
|
||||
const previous = lastRawRef.current;
|
||||
lastRawRef.current = rawAzimuth;
|
||||
if (movementSeenRef.current && previous != null && movementDirRef.current !== 0) {
|
||||
const step = short(previous, rawAzimuth);
|
||||
if (step !== 0 && Math.sign(step) === movementDirRef.current) armMovement();
|
||||
}
|
||||
const reference = movementReferenceRef.current;
|
||||
if (reference == null) { movementReferenceRef.current = rawAzimuth; return; }
|
||||
// Signed, and the short way round: crossing north is a small step, not 350°.
|
||||
const delta = ((rawAzimuth - reference + 540) % 360) - 180;
|
||||
const delta = short(reference, rawAzimuth);
|
||||
if (Math.abs(delta) < MOVEMENT_TRIGGER_DEG) return; // inside the noise band
|
||||
const sign = delta > 0 ? 1 : -1;
|
||||
if (movementDirRef.current === sign) {
|
||||
@@ -603,18 +650,7 @@ export function RotorCompass({
|
||||
movementReferenceRef.current = rawAzimuth;
|
||||
if (movementRunRef.current < 2) return; // one step either way is weather
|
||||
|
||||
movementSeenRef.current = true;
|
||||
setIsMoving(true);
|
||||
window.clearTimeout(commandTimerRef.current);
|
||||
window.clearTimeout(movementTimerRef.current);
|
||||
movementTimerRef.current = window.setTimeout(() => {
|
||||
setIsMoving(false);
|
||||
movementSeenRef.current = false;
|
||||
// Forget the direction too: the next real move starts its own run rather
|
||||
// than inheriting one from a rotation that finished minutes ago.
|
||||
movementDirRef.current = 0;
|
||||
movementRunRef.current = 0;
|
||||
}, MOVEMENT_SETTLE_MS);
|
||||
armMovement();
|
||||
}, [rawAzimuth, rotorKey]);
|
||||
|
||||
// Arrival: confirmed over time, then faded. Every check re-reads the
|
||||
|
||||
Reference in New Issue
Block a user