From d4f97b80bfd11eb811a8422d5646383cb4572bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5=D1=80=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE?= Date: Wed, 23 Sep 2026 14:40:05 +0300 Subject: [PATCH] build157 --- app.py | 2 +- ui_builder/static/app.js | 45 +++++++++++++++++----------------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/app.py b/app.py index 93ce659..1cd1012 100644 --- a/app.py +++ b/app.py @@ -30,7 +30,7 @@ from ui_builder import install_ui_builder from khl_site.khl_data_center import APP as khl_site_app BASE_DIR = Path(__file__).resolve().parent -BUILD_VERSION = "2026.09.23.3" +BUILD_VERSION = "2026.09.23.4" # compatibility: BUILD_VERSION = "2026.08.26.1" # compatibility: BUILD_VERSION = "2026.08.25.1" # compatibility: BUILD_VERSION = "2026.08.24.15" diff --git a/ui_builder/static/app.js b/ui_builder/static/app.js index fa919e6..d6eaf60 100644 --- a/ui_builder/static/app.js +++ b/ui_builder/static/app.js @@ -4260,20 +4260,25 @@ function startCustomTooltips() { } function vmixGameCountdownControlCommands(input, selectedName, millisecondsProvider, action = "start") { - // BUILD108: the native vMix game clock is seeded only on an explicit time change. - // Space must never re-seed the countdown: Start/Resume only starts it and Pause/Stop - // only pauses it. This keeps the already-running native vMix countdown continuous - // and removes jumps caused by writing the browser value during a pause transition. + // BUILD157: Runtime is authoritative at every operator Start/Resume transition. + // We seed the CURRENT native vMix countdown position once at that transition and + // then let vMix count locally. There is still absolutely no per-second vMix write. + // This deliberately removes BUILD108/156's fragile browser-only "seeded" assumption: + // vMix may have been restarted/reloaded while the browser still remembered the target. const target = { Input: String(input || "").trim(), SelectedName: String(selectedName || "").trim() }; if (!target.Input || !target.SelectedName) return []; if (action === "pause" || action === "stop") { return [{ Function: "PauseCountdown", ...target }]; } + const milliseconds = typeof millisecondsProvider === "function" ? millisecondsProvider() : millisecondsProvider; + const value = vmixCountdownValue(milliseconds); if (action === "set") { - const milliseconds = typeof millisecondsProvider === "function" ? millisecondsProvider() : millisecondsProvider; - return [{ Function: "ChangeCountdown", ...target, Value: vmixCountdownValue(milliseconds) }]; + return [{ Function: "ChangeCountdown", ...target, Value: value }]; } - return [{ Function: "StartCountdown", ...target }]; + return [ + { Function: "ChangeCountdown", ...target, Value: value }, + { Function: "StartCountdown", ...target }, + ]; } function buildShortcutRuntimeContext(sequence = null) { @@ -5528,16 +5533,9 @@ function startCustomTooltips() { step.game_vmix_input, step.game_vmix_selected_name, () => gameTimerState.currentMs, "pause" )); } else { - // BUILD156: the first Start for this exact Input + SelectedName must also - // transfer the current Runtime value. This fixes a fresh vMix title where - // StartCountdown arrived but the configured web time never did. After the - // target is seeded, normal Resume is StartCountdown-only (BUILD108 rule). - if (!isVmixGameCountdownSeeded(step.game_vmix_input, step.game_vmix_selected_name)) { - commands.push(...vmixGameCountdownControlCommands( - step.game_vmix_input, step.game_vmix_selected_name, () => gameTimerState.currentMs, "set" - )); - markVmixGameCountdownSeeded(step.game_vmix_input, step.game_vmix_selected_name); - } + // BUILD157: every Start/Resume re-seeds from the current Runtime value once, + // then starts the native countdown. vMix performs the second-by-second count + // locally; Runtime never mirrors timer_tick values to vMix. commands.push(...vmixGameCountdownControlCommands( step.game_vmix_input, step.game_vmix_selected_name, () => gameTimerState.currentMs, "start" )); @@ -5574,9 +5572,9 @@ function startCustomTooltips() { eventKey, input: target.input, selectedName: target.selected_name, overlay: target.overlay, sourceSide, targetSide: side, mode: "countdown", running: eventRunning, }); - // BUILD155: native penalty Countdown behaves exactly like the game clock. - // Seed only when this vMix slot is assigned to a new penalty. Space pause/resume - // is PauseCountdown/StartCountdown only; there is no time writeback on every Space. + // BUILD157: penalty countdowns use the same robust transition model as + // the game clock. Every Start/Resume sends current remaining time once and + // StartCountdown, then vMix counts locally. Pause remains a native pause. if (pausing || !eventRunning) { commands.push(...vmixGameCountdownControlCommands( target.input, target.selected_name, () => event.remainingMs, "pause" @@ -5587,11 +5585,6 @@ function startCustomTooltips() { )); } } else { - if (assignmentChanged) { - commands.push(...vmixGameCountdownControlCommands( - target.input, target.selected_name, () => event.remainingMs, "set" - )); - } commands.push(...vmixGameCountdownControlCommands( target.input, target.selected_name, () => event.remainingMs, "start" )); @@ -14746,7 +14739,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
${timerFinishActionRows(step)}
${escapeHtml(shortcutInventoryLabel())}Для каждого countdown теперь обязательно выбирается конкретный Text / SelectedName. Это исключает отправку времени в первый текстовый элемент по умолчанию.
-

Режим Countdown vMix: при первом Start для выбранного Input + SelectedName текущее веб-время один раз передаётся через ChangeCountdown, затем отправляется StartCountdown. После этого Space не пересылает значение: Resume отправляет только StartCountdown, Pause/Stop — только PauseCountdown. Явная установка/сброс времени также обновляет vMix через ChangeCountdown. Это исключает скачки времени при паузе. StopCountdown в обычном управлении не используется. Команды идут в Agent без ACK. Text mirror оставлен только для совместимости со старыми титрами. Для верхнего счёта используется одна penalty-плашка: при реальном большинстве она показывает ближайшее изменение численного состава; при чистом равном обоюдном удалении сама по себе не появляется.

+

Режим Countdown vMix: каждый Start/Resume один раз передаёт текущее Runtime-время через ChangeCountdown, затем отправляет StartCountdown. После этого vMix считает локально — посекундной передачи времени нет. Pause/Stop отправляет только PauseCountdown. Явная установка/сброс времени также обновляет vMix через ChangeCountdown. Это не зависит от browser-only флага «таймер уже инициализирован» и устойчиво к перезапуску/перезагрузке vMix-титра. StopCountdown в обычном управлении не используется. Команды идут в Agent без ACK. Для верхнего счёта используется одна penalty-плашка: при реальном большинстве она показывает ближайшее изменение численного состава; при чистом равном обоюдном удалении сама по себе не появляется.

`; } else if (step.type === "delay") { body.innerHTML = `
`;