fix(autocall): rest in overs, no phantom miss, and a readable auto readout
The rest between two series was two minutes — four overs on FT8, by which time the DX has worked four other callers and half the time has gone. It is now counted in the station's OWN overs and is one by default: seven calls, one over listened through, and it goes again if the station is still there. The deadline lands mid-cycle on purpose, so "sit out one over" means one over rather than depending on a millisecond of clock skew. A period the target was DECODED in is never counted as a miss. A period is judged more than once — decodes arrive in a burst and stragglers follow — and a later judgement holds a partial view of it, not evidence of absence: D2ACE answered in the very period the counter then read as a miss. The readout moved out of the Auto button and beside it. "Auto D2ACE 1/7 ·1/3" read as one number, and the control changed width every period. The callsign is now the biggest thing on the row, the calls and the missed periods each carry a label, the miss count appears only once there is one, and a station being waited for gets its own amber chip.
This commit is contained in:
@@ -1009,22 +1009,48 @@ export function DecodesPanel({ decodes, txMsgs, txState, txStates, spotStatus, r
|
||||
>
|
||||
<Bot className="size-3.5" />
|
||||
{t('dec.autoCall')}
|
||||
{autoCallOn && autoCall?.target && (
|
||||
<span className="font-mono text-xs">
|
||||
{autoCall.target} {autoCall.calls}/{autoCall.max}
|
||||
{autoCall.misses > 0 ? ` ·${autoCall.misses}/${autoCall.max_miss}` : ''}
|
||||
</span>
|
||||
)}
|
||||
{/* Wanted, decoded, and in a QSO with somebody else. Holding fire
|
||||
looks exactly like having nothing to do, and the operator had no
|
||||
way to tell them apart. */}
|
||||
{autoCallOn && !autoCall?.target && autoCall?.waiting && (
|
||||
<span className="font-mono text-xs animate-pulse" title={t('dec.autoWaitTip', { call: autoCall.waiting })}>
|
||||
{autoCall.waiting} ⏳
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{/* WHAT IT IS DOING, BESIDE THE SWITCH RATHER THAN ON IT.
|
||||
A thing that keys the transmitter must never be a switch with no
|
||||
readout — but the readout was crammed inside the button as
|
||||
"Auto D2ACE 1/7 ·1/3", where the two counters read as one number and
|
||||
the whole control changed width every period. Out here each figure
|
||||
gets a label, and the station being called is the biggest thing on
|
||||
the row. */}
|
||||
{autoCallOn && autoCall?.target && (
|
||||
<span className="h-8 inline-flex items-center gap-2 rounded-lg border border-success/50 bg-success/10 px-2"
|
||||
title={t('dec.autoTargetTip', { call: autoCall.target })}>
|
||||
<span className="font-mono text-sm font-bold text-success">{autoCall.target}</span>
|
||||
<span className="inline-flex items-center gap-1 text-[11px] text-muted-foreground">
|
||||
<span className="uppercase tracking-wide">{t('dec.autoCalls')}</span>
|
||||
<span className="font-mono tabular-nums text-foreground">{autoCall.calls}/{autoCall.max}</span>
|
||||
</span>
|
||||
{/* Only once there is one to report: a zero that is always there is
|
||||
a figure nobody reads, and it was the half of the pair that got
|
||||
mistaken for the call count. */}
|
||||
{autoCall.misses > 0 && (
|
||||
<span className="inline-flex items-center gap-1 text-[11px] text-muted-foreground">
|
||||
<span className="uppercase tracking-wide">{t('dec.autoMisses')}</span>
|
||||
<span className={cn('font-mono tabular-nums',
|
||||
autoCall.misses + 1 >= autoCall.max_miss ? 'text-warning' : 'text-foreground')}>
|
||||
{autoCall.misses}/{autoCall.max_miss}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{/* Wanted, decoded, and in a QSO with somebody else. Holding fire looks
|
||||
exactly like having nothing to do, and the operator had no way to
|
||||
tell them apart. */}
|
||||
{autoCallOn && !autoCall?.target && autoCall?.waiting && (
|
||||
<span className="h-8 inline-flex items-center gap-1.5 rounded-lg border border-warning/50 bg-warning/10 px-2 animate-pulse"
|
||||
title={t('dec.autoWaitTip', { call: autoCall.waiting })}>
|
||||
<span className="text-sm">⏳</span>
|
||||
<span className="font-mono text-sm font-bold text-warning">{autoCall.waiting}</span>
|
||||
<span className="text-[11px] uppercase tracking-wide text-muted-foreground">{t('dec.autoWaiting')}</span>
|
||||
</span>
|
||||
)}
|
||||
{/* The chase list. Raw text while typing, committed on blur or Enter:
|
||||
the stored value is upper-cased and trimmed, and binding the box to
|
||||
that makes the space bar look dead — in a field whose whole purpose
|
||||
|
||||
@@ -2106,7 +2106,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
const [chaseSotaOn, setChaseSotaOn] = useState(() => localStorage.getItem('opslog.chaseSota') !== '0');
|
||||
const [chaseNew, setChaseNew] = useState(false);
|
||||
const [pskTgt, setPskTgt] = useState<any>({ enabled: false, scope: 'target' });
|
||||
const [ac, setAc] = useState<any>({ enabled: false, only: '', attempts: 7, watched_attempts: 15, misses: 3, max_rounds: 3, rest_min: 2, on_screen_only: true });
|
||||
const [ac, setAc] = useState<any>({ enabled: false, only: '', attempts: 7, watched_attempts: 15, misses: 3, max_rounds: 3, rest_periods: 1, on_screen_only: true });
|
||||
// The named contest callsigns. Raw text in state, written on blur: it is a
|
||||
// multi-line list, and normalising it on every keystroke would fight the
|
||||
// Return key — the one key this box is built around.
|
||||
@@ -5566,7 +5566,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
['watched_attempts', t('ac.watchedAttempts'), 1, 60],
|
||||
['misses', t('ac.misses'), 1, 10],
|
||||
['max_rounds', t('ac.rounds'), 1, 10],
|
||||
['rest_min', t('ac.rest'), 1, 60],
|
||||
['rest_periods', t('ac.rest'), 1, 20],
|
||||
] as [string, string, number, number][]).map(([k, label, min, max]) => (
|
||||
<span key={k} className="inline-flex items-center gap-1.5">
|
||||
<span className="text-xs text-muted-foreground">{label}</span>
|
||||
|
||||
@@ -193,8 +193,8 @@ const en: Dict = {
|
||||
'ac.ladder': 'Order of priority: every callsign on the watch list first (among themselves: new entity, band, mode, slot), then new entity, new band, new mode, new slot. A real need comes before one that exists only because a QSL never came. A station calling YOU is answered whatever the log makes of it. A station in the middle of a QSO with somebody else is never called — it cannot answer.',
|
||||
'ac.only': 'Chase only', 'ac.onlyPh': 'VP6D 3Y0J (space or comma)', 'ac.onlyHint': 'one or more callsigns — nothing else is called; empty means call whatever the log needs',
|
||||
'ac.attempts': 'Calls before giving up', 'ac.watchedAttempts': 'if watched', 'ac.misses': 'Missed periods',
|
||||
'ac.rounds': 'Series per station', 'ac.rest': 'Rest (min)', 'ac.trace': 'Log every decision', 'ac.traceHint': '— one line per period in the diagnostic log: what was on the air, why each station was refused, and what was decided. For working out why nothing is being called. A line every fifteen seconds, so leave it off otherwise.', 'ac.onScreen': 'Call only what the decodes list is showing', 'ac.onScreenHint': '— the filters above the list (CQ only, LoTW only, the new-category chips, continents, report, search) steer the transmitter too: a station filtered off the screen is not called. With the list closed nothing is filtered.',
|
||||
'dec.chasePh': 'chase…', 'dec.chaseTip': 'Call ONLY these stations — one or more callsigns, spaces or commas. Empty means call whatever the log needs. Same field as Preferences → DXHunter.', 'dec.autoWaitTip': '{call} is wanted and is working somebody else — it is called the moment it is free.', 'dec.autoStoppedTip': 'Auto-call has given up on a station from the chase list — click to switch it off, or Halt to clear it.',
|
||||
'ac.rounds': 'Series per station', 'ac.rest': 'Rest (overs)', 'ac.trace': 'Log every decision', 'ac.traceHint': '— one line per period in the diagnostic log: what was on the air, why each station was refused, and what was decided. For working out why nothing is being called. A line every fifteen seconds, so leave it off otherwise.', 'ac.onScreen': 'Call only what the decodes list is showing', 'ac.onScreenHint': '— the filters above the list (CQ only, LoTW only, the new-category chips, continents, report, search) steer the transmitter too: a station filtered off the screen is not called. With the list closed nothing is filtered.',
|
||||
'dec.chasePh': 'chase…', 'dec.chaseTip': 'Call ONLY these stations — one or more callsigns, spaces or commas. Empty means call whatever the log needs. Same field as Preferences → DXHunter.', 'dec.autoCalls': 'calls', 'dec.autoMisses': 'missed', 'dec.autoWaiting': 'waiting', 'dec.autoTargetTip': 'Auto-call is calling {call} — calls made of the allowance, and periods of its own it was not decoded in.', 'dec.autoWaitTip': '{call} is wanted and is working somebody else — it is called the moment it is free.', 'dec.autoStoppedTip': 'Auto-call has given up on a station from the chase list — click to switch it off, or Halt to clear it.',
|
||||
// Contest watchlist (Settings → DXHunter): how a special-event fleet joins the list on its own
|
||||
'wlc.title': 'Contest', 'wlc.pattern': 'Auto-add on', 'wlc.patternPh': 'WWA', 'wlc.patternHint': 'any spotted callsign CONTAINING this joins the watchlist as a contest entry (TM29WWA, HB9WWA, F4WWA/P)',
|
||||
'wlc.calls': 'And these callsigns, one per line', 'wlc.callsPh': 'R7W DL0ABC', 'wlc.callsHint': 'For the entries a pattern cannot catch: a station taking part under a callsign that says nothing about the event. Named here, it joins the contest watchlist the moment it is spotted. Commas and spaces work too.',
|
||||
@@ -750,8 +750,8 @@ const fr: Dict = {
|
||||
'ac.ladder': "Ordre de priorité : d'abord tout indicatif de la liste de surveillance (entre eux : nouvelle entité, bande, mode, slot), puis nouvelle entité, nouvelle bande, nouveau mode, nouveau slot. Un vrai besoin passe devant un besoin qui n'existe que faute de QSL. Une station qui VOUS appelle est toujours prise, quoi qu'en dise le log. Une station en plein QSO avec quelqu'un d'autre n'est jamais appelée — elle ne peut pas répondre.",
|
||||
'ac.only': 'Chasser uniquement', 'ac.onlyPh': 'VP6D 3Y0J (espace ou virgule)', 'ac.onlyHint': 'un ou plusieurs indicatifs — rien d’autre n’est appelé ; vide = appeler ce dont le log a besoin',
|
||||
'ac.attempts': 'Appels avant abandon', 'ac.watchedAttempts': 'si surveillé', 'ac.misses': 'Périodes manquées',
|
||||
'ac.rounds': 'Séries par station', 'ac.rest': 'Repos (min)', 'ac.trace': 'Journaliser chaque décision', 'ac.traceHint': '— une ligne par période dans le journal de diagnostic : ce qu’il y avait sur l’air, pourquoi chaque station a été écartée, et ce qui a été décidé. Pour comprendre pourquoi rien n’est appelé. Une ligne toutes les quinze secondes : à laisser éteint le reste du temps.', 'ac.onScreen': 'N’appeler que ce qu’affiche la liste des décodages', 'ac.onScreenHint': '— les filtres au-dessus de la liste (CQ seuls, LoTW, les pastilles de catégorie, continents, report, recherche) pilotent aussi l’émetteur : une station filtrée hors écran n’est pas appelée. La liste fermée, rien n’est filtré.',
|
||||
'dec.chasePh': 'chasser…', 'dec.chaseTip': "N'appeler QUE ces stations — un ou plusieurs indicatifs, espaces ou virgules. Vide = appeler ce dont le log a besoin. Même champ que Réglages → DXHunter.", 'dec.autoWaitTip': '{call} est voulue et travaille quelqu’un d’autre — elle sera appelée dès qu’elle sera libre.', 'dec.autoStoppedTip': "L'appel automatique a renoncé à une station de la liste de chasse — cliquez pour l'éteindre, ou Stop pour effacer.",
|
||||
'ac.rounds': 'Séries par station', 'ac.rest': 'Repos (tours)', 'ac.trace': 'Journaliser chaque décision', 'ac.traceHint': '— une ligne par période dans le journal de diagnostic : ce qu’il y avait sur l’air, pourquoi chaque station a été écartée, et ce qui a été décidé. Pour comprendre pourquoi rien n’est appelé. Une ligne toutes les quinze secondes : à laisser éteint le reste du temps.', 'ac.onScreen': 'N’appeler que ce qu’affiche la liste des décodages', 'ac.onScreenHint': '— les filtres au-dessus de la liste (CQ seuls, LoTW, les pastilles de catégorie, continents, report, recherche) pilotent aussi l’émetteur : une station filtrée hors écran n’est pas appelée. La liste fermée, rien n’est filtré.',
|
||||
'dec.chasePh': 'chasser…', 'dec.chaseTip': "N'appeler QUE ces stations — un ou plusieurs indicatifs, espaces ou virgules. Vide = appeler ce dont le log a besoin. Même champ que Réglages → DXHunter.", 'dec.autoCalls': 'appels', 'dec.autoMisses': 'ratés', 'dec.autoWaiting': 'en attente', 'dec.autoTargetTip': 'L’auto-call appelle {call} — appels effectués sur l’allocation, et périodes à lui où il n’a pas été décodé.', 'dec.autoWaitTip': '{call} est voulue et travaille quelqu’un d’autre — elle sera appelée dès qu’elle sera libre.', 'dec.autoStoppedTip': "L'appel automatique a renoncé à une station de la liste de chasse — cliquez pour l'éteindre, ou Stop pour effacer.",
|
||||
// Watchlist contest (Réglages → DXHunter) : comment une flotte d’événement rejoint la liste toute seule
|
||||
'wlc.title': 'Contest', 'wlc.pattern': 'Ajout auto sur', 'wlc.patternPh': 'WWA', 'wlc.patternHint': 'tout indicatif spotté CONTENANT ceci rejoint la watchlist comme entrée contest (TM29WWA, HB9WWA, F4WWA/P)',
|
||||
'wlc.calls': 'Et ces indicatifs, un par ligne', 'wlc.callsPh': 'R7W DL0ABC', 'wlc.callsHint': 'Pour les participants qu’aucun motif ne peut attraper : une station engagée sous un indicatif qui ne dit rien de l’événement. Nommée ici, elle rejoint la watchlist contest dès qu’elle est spottée. Les virgules et les espaces marchent aussi.',
|
||||
|
||||
Reference in New Issue
Block a user