ёбанные удаления версия хуй знает какая
This commit is contained in:
3
app.py
3
app.py
@@ -29,7 +29,8 @@ from ui_builder import install_ui_builder
|
|||||||
from khl_site.khl_data_center import APP as khl_site_app
|
from khl_site.khl_data_center import APP as khl_site_app
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
BUILD_VERSION = "2026.08.20.15"
|
BUILD_VERSION = "2026.08.20.16"
|
||||||
|
# compatibility: BUILD_VERSION = "2026.08.20.15"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.20.14"
|
# compatibility: BUILD_VERSION = "2026.08.20.14"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.20.13"
|
# compatibility: BUILD_VERSION = "2026.08.20.13"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.19.28"
|
# compatibility: BUILD_VERSION = "2026.08.19.28"
|
||||||
|
|||||||
56
tests/test_build95_countdown_explicit_start_stop_sync.py
Normal file
56
tests/test_build95_countdown_explicit_start_stop_sync.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
APP_JS = (ROOT / "ui_builder/static/app.js").read_text(encoding="utf-8")
|
||||||
|
APP = (ROOT / "app.py").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _block(start: str, end: str) -> str:
|
||||||
|
return APP_JS.split(start, 1)[1].split(end, 1)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepared_penalty_does_not_force_vmix_rebalance():
|
||||||
|
block = _block("function emitHockeyEventUpdated", "function createHockeyPenaltyDraft")
|
||||||
|
assert "rebalanceVmixPenaltyTargets" not in block
|
||||||
|
draft = _block("function createHockeyPenaltyDraft", "function findHockeyPenalty")
|
||||||
|
assert "startedOnce: false" in draft
|
||||||
|
control = _block("function controlHockeyPenalty", "function formatHockeyPenaltyTime")
|
||||||
|
start_branch = control.split('if (command === "start")', 1)[1].split('} else if (command === "pause")', 1)[0]
|
||||||
|
assert "event.startedOnce = true" in start_branch
|
||||||
|
|
||||||
|
|
||||||
|
def test_penalty_vmix_display_uses_only_started_or_paused_active_penalties():
|
||||||
|
assert "function activeHockeyPenaltyEntries()" in APP_JS
|
||||||
|
sorted_block = _block("function sortedPenaltyEntries", "function penaltyLocalAdvantageSide")
|
||||||
|
assert "activeHockeyPenaltyEntries()" in sorted_block
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_countdown_start_pause_stop_all_hard_sync_web_value():
|
||||||
|
block = _block("async function syncActiveVmixGameCountdown", "function penaltyMirrorKey")
|
||||||
|
start = block.split('if (["timer_start", "timer_restart", "timer_resume"].includes(eventName))', 1)[1].split('} else if (eventName === "timer_pause")', 1)[0]
|
||||||
|
assert start.index('Function: "SetCountdown"') < start.index('Function: "StartCountdown"')
|
||||||
|
pause = block.split('eventName === "timer_pause"', 1)[1].split('} else if (["timer_stop", "timer_finished"]', 1)[0]
|
||||||
|
assert pause.index('Function: "PauseCountdown"') < pause.index('Function: "SetCountdown"')
|
||||||
|
stop = block.split('["timer_stop", "timer_finished"].includes(eventName)', 1)[1].split('} else if (["timer_reset"', 1)[0]
|
||||||
|
assert stop.index('Function: "StopCountdown"') < stop.index('Function: "SetCountdown"')
|
||||||
|
|
||||||
|
|
||||||
|
def test_penalty_pause_freezes_before_reseed_and_start_reseeds_before_run():
|
||||||
|
block = _block("async function rebalanceVmixPenaltyTargets", "function finishActionMatchesSource")
|
||||||
|
assert 'stopCommands.push({ Function: "PauseCountdown"' in block
|
||||||
|
assert 'setCommands.push({ Function: "SetCountdown"' in block
|
||||||
|
assert 'runCommands.push({ Function: "StartCountdown"' in block
|
||||||
|
assert "Never StartCountdown for a prepared item" in block
|
||||||
|
|
||||||
|
|
||||||
|
def test_combined_shortcut_changes_web_first_then_synchronizes_vmix():
|
||||||
|
block = APP_JS.split('case "hockey_vmix_timers_start":', 1)[1].split('case "delay":', 1)[0]
|
||||||
|
web_game = block.index("if (step.start_web_game)")
|
||||||
|
vmix_game = block.index("if (step.sync_vmix_game && step.game_vmix_input)")
|
||||||
|
assert web_game < vmix_game
|
||||||
|
assert block.index('Function: "PauseCountdown"') < block.index('Function: "SetCountdown"', block.index('Function: "PauseCountdown"'))
|
||||||
|
assert 'Function: "StartCountdown"' in block
|
||||||
|
|
||||||
|
|
||||||
|
def test_build95_runtime_version():
|
||||||
|
assert 'BUILD_VERSION = "2026.08.20.16"' in APP
|
||||||
@@ -4110,6 +4110,22 @@ function startCustomTooltips() {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BUILD95: a prepared penalty is not an active vMix countdown yet.
|
||||||
|
// It becomes active only after the operator explicitly presses Start.
|
||||||
|
// Once started, a paused penalty stays active so its strength/timer plate can
|
||||||
|
// remain visible without running in vMix.
|
||||||
|
function hockeyPenaltyHasStarted(event) {
|
||||||
|
if (!event || event.finished) return false;
|
||||||
|
if (event.startedOnce === true || event.running) return true;
|
||||||
|
const duration = Math.max(0, Number(event.durationMs || 0));
|
||||||
|
const remaining = Math.max(0, Number(event.remainingMs ?? duration));
|
||||||
|
return duration > 0 && remaining > 0 && remaining < duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activeHockeyPenaltyEntries() {
|
||||||
|
return currentHockeyPenaltyEntries().filter(({ event }) => hockeyPenaltyHasStarted(event));
|
||||||
|
}
|
||||||
|
|
||||||
function vmixCountdownValue(milliseconds) {
|
function vmixCountdownValue(milliseconds) {
|
||||||
const totalSeconds = Math.max(0, Math.ceil(Number(milliseconds || 0) / 1000));
|
const totalSeconds = Math.max(0, Math.ceil(Number(milliseconds || 0) / 1000));
|
||||||
const hours = Math.floor(totalSeconds / 3600);
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
@@ -4607,17 +4623,24 @@ function startCustomTooltips() {
|
|||||||
if (!input || !selectedName) continue;
|
if (!input || !selectedName) continue;
|
||||||
const target = { Input: input, SelectedName: selectedName };
|
const target = { Input: input, SelectedName: selectedName };
|
||||||
if (["timer_start", "timer_restart", "timer_resume"].includes(eventName)) {
|
if (["timer_start", "timer_restart", "timer_resume"].includes(eventName)) {
|
||||||
// BUILD93: every launch/resume re-seeds vMix from the current Runtime time
|
// BUILD95: every launch/resume is a hard Runtime -> vMix sync.
|
||||||
// before StartCountdown. This prevents drift after pauses or delayed operator actions.
|
|
||||||
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
|
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
|
||||||
commands.push({ Function: "StartCountdown", ...target });
|
commands.push({ Function: "StartCountdown", ...target });
|
||||||
} else if (eventName === "timer_pause") {
|
} else if (eventName === "timer_pause") {
|
||||||
|
// Freeze vMix first, then overwrite it with the exact web value.
|
||||||
commands.push({ Function: "PauseCountdown", ...target });
|
commands.push({ Function: "PauseCountdown", ...target });
|
||||||
|
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
|
||||||
} else if (["timer_stop", "timer_finished"].includes(eventName)) {
|
} else if (["timer_stop", "timer_finished"].includes(eventName)) {
|
||||||
commands.push({ Function: "StopCountdown", ...target });
|
commands.push({ Function: "StopCountdown", ...target });
|
||||||
} else if (["timer_reset", "timer_set_time", "timer_add_time", "timer_subtract_time"].includes(eventName)) {
|
|
||||||
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
|
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
|
||||||
commands.push({ Function: timerState.running ? "StartCountdown" : "PauseCountdown", ...target });
|
} else if (["timer_reset", "timer_set_time", "timer_add_time", "timer_subtract_time"].includes(eventName)) {
|
||||||
|
if (timerState.running) {
|
||||||
|
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
|
||||||
|
commands.push({ Function: "StartCountdown", ...target });
|
||||||
|
} else {
|
||||||
|
commands.push({ Function: "PauseCountdown", ...target });
|
||||||
|
commands.push({ Function: "SetCountdown", ...target, Value: vmixCountdownValue(timerState.currentMs) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!commands.length) return false;
|
if (!commands.length) return false;
|
||||||
@@ -4688,7 +4711,7 @@ function startCustomTooltips() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sortedPenaltyEntries(side = "") {
|
function sortedPenaltyEntries(side = "") {
|
||||||
return currentHockeyPenaltyEntries()
|
return activeHockeyPenaltyEntries()
|
||||||
.filter((item) => !side || item.side === side)
|
.filter((item) => !side || item.side === side)
|
||||||
.sort((a, b) => Number(a.event.remainingMs || 0) - Number(b.event.remainingMs || 0)
|
.sort((a, b) => Number(a.event.remainingMs || 0) - Number(b.event.remainingMs || 0)
|
||||||
|| Number(a.event.createdAt || 0) - Number(b.event.createdAt || 0));
|
|| Number(a.event.createdAt || 0) - Number(b.event.createdAt || 0));
|
||||||
@@ -4897,15 +4920,18 @@ function startCustomTooltips() {
|
|||||||
&& (force || assignmentChanged || previous?.running !== true);
|
&& (force || assignmentChanged || previous?.running !== true);
|
||||||
// BUILD93 invariant: whenever StartCountdown is emitted, SetCountdown with
|
// BUILD93 invariant: whenever StartCountdown is emitted, SetCountdown with
|
||||||
// the current web value is emitted immediately before it.
|
// the current web value is emitted immediately before it.
|
||||||
if (force || assignmentChanged || startingCountdown) {
|
|
||||||
setCommands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(entry.event.remainingMs) });
|
|
||||||
}
|
|
||||||
if (entry.event.running) {
|
if (entry.event.running) {
|
||||||
|
if (force || assignmentChanged || startingCountdown) {
|
||||||
|
setCommands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(entry.event.remainingMs) });
|
||||||
|
}
|
||||||
if (startingCountdown) {
|
if (startingCountdown) {
|
||||||
runCommands.push({ Function: "StartCountdown", Input: target.input, SelectedName: target.selected_name });
|
runCommands.push({ Function: "StartCountdown", Input: target.input, SelectedName: target.selected_name });
|
||||||
}
|
}
|
||||||
} else if (force || assignmentChanged || previous?.running !== false) {
|
} else if (force || assignmentChanged || previous?.running !== false) {
|
||||||
runCommands.push({ Function: "PauseCountdown", Input: target.input, SelectedName: target.selected_name });
|
// BUILD95: on every pause/stop, freeze first and then seed the
|
||||||
|
// exact Runtime value. Never StartCountdown for a prepared item.
|
||||||
|
stopCommands.push({ Function: "PauseCountdown", Input: target.input, SelectedName: target.selected_name });
|
||||||
|
setCommands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(entry.event.remainingMs) });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assignedMirrorKeys.add(eventKey);
|
assignedMirrorKeys.add(eventKey);
|
||||||
@@ -5065,7 +5091,6 @@ function startCustomTooltips() {
|
|||||||
case "hockey_vmix_timers_start": {
|
case "hockey_vmix_timers_start": {
|
||||||
const gameTimer = componentByActionId(step.game_timer_action_id || "hockey_game_timer");
|
const gameTimer = componentByActionId(step.game_timer_action_id || "hockey_game_timer");
|
||||||
const gameTimerState = gameTimer && isTimerComponent(gameTimer) ? ensureTimerState(gameTimer) : null;
|
const gameTimerState = gameTimer && isTimerComponent(gameTimer) ? ensureTimerState(gameTimer) : null;
|
||||||
const penalties = currentHockeyPenaltyEntries();
|
|
||||||
const homeTargets = sequencePenaltyTargets(step, "home");
|
const homeTargets = sequencePenaltyTargets(step, "home");
|
||||||
const awayTargets = sequencePenaltyTargets(step, "away");
|
const awayTargets = sequencePenaltyTargets(step, "away");
|
||||||
const configuredCommand = ["toggle", "start", "pause", "resume"].includes(step.hockey_timer_command) ? step.hockey_timer_command : "toggle";
|
const configuredCommand = ["toggle", "start", "pause", "resume"].includes(step.hockey_timer_command) ? step.hockey_timer_command : "toggle";
|
||||||
@@ -5074,6 +5099,19 @@ function startCustomTooltips() {
|
|||||||
const commands = [];
|
const commands = [];
|
||||||
state.activeHockeyVmixTimerSteps.add(step.id);
|
state.activeHockeyVmixTimerSteps.add(step.id);
|
||||||
|
|
||||||
|
// BUILD95: update the web clocks FIRST. The vMix SetCountdown commands below
|
||||||
|
// are then always seeded from the exact post-action Runtime value.
|
||||||
|
if (step.start_web_game) {
|
||||||
|
if (!controlTimer(step.game_timer_action_id || "hockey_game_timer", pausing ? "pause" : (action === "resume" ? "resume" : "start"), "", { syncVmix: false })) {
|
||||||
|
throw new Error(`Основной таймер «${step.game_timer_action_id || "hockey_game_timer"}» не найден`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const penaltiesToControl = currentHockeyPenaltyEntries();
|
||||||
|
if (step.start_web_penalties) {
|
||||||
|
penaltiesToControl.forEach(({ component, event }) => controlHockeyPenalty(component, event.id, pausing ? "pause" : "start", "", { syncVmix: false }));
|
||||||
|
}
|
||||||
|
const penalties = currentHockeyPenaltyEntries();
|
||||||
|
|
||||||
if (step.sync_vmix_game && step.game_vmix_input) {
|
if (step.sync_vmix_game && step.game_vmix_input) {
|
||||||
if (!gameTimerState) throw new Error(`Основной таймер «${step.game_timer_action_id}» не найден`);
|
if (!gameTimerState) throw new Error(`Основной таймер «${step.game_timer_action_id}» не найден`);
|
||||||
if (!step.game_vmix_selected_name) throw new Error("Выберите Text / SelectedName основного таймера в vMix");
|
if (!step.game_vmix_selected_name) throw new Error("Выберите Text / SelectedName основного таймера в vMix");
|
||||||
@@ -5082,8 +5120,9 @@ function startCustomTooltips() {
|
|||||||
commands.push({ Function: "SetText", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: formatTimerValue(gameTimer, gameTimerState) });
|
commands.push({ Function: "SetText", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: formatTimerValue(gameTimer, gameTimerState) });
|
||||||
} else if (pausing) {
|
} else if (pausing) {
|
||||||
commands.push({ Function: "PauseCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name });
|
commands.push({ Function: "PauseCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name });
|
||||||
|
commands.push({ Function: "SetCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: vmixCountdownValue(gameTimerState.currentMs) });
|
||||||
} else {
|
} else {
|
||||||
// BUILD93: start and resume are both a hard Runtime -> vMix sync.
|
// BUILD95: every start/resume is SetCountdown -> StartCountdown.
|
||||||
commands.push({ Function: "SetCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: vmixCountdownValue(gameTimerState.currentMs) });
|
commands.push({ Function: "SetCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name, Value: vmixCountdownValue(gameTimerState.currentMs) });
|
||||||
commands.push({ Function: "StartCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name });
|
commands.push({ Function: "StartCountdown", Input: step.game_vmix_input, SelectedName: step.game_vmix_selected_name });
|
||||||
}
|
}
|
||||||
@@ -5114,8 +5153,9 @@ function startCustomTooltips() {
|
|||||||
});
|
});
|
||||||
if (pausing) {
|
if (pausing) {
|
||||||
commands.push({ Function: "PauseCountdown", Input: target.input, SelectedName: target.selected_name });
|
commands.push({ Function: "PauseCountdown", Input: target.input, SelectedName: target.selected_name });
|
||||||
|
commands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(event.remainingMs) });
|
||||||
} else {
|
} else {
|
||||||
// BUILD93: every penalty launch/resume is re-seeded from the web timer first.
|
// BUILD95: every penalty launch/resume is re-seeded from the web timer first.
|
||||||
commands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(event.remainingMs) });
|
commands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: vmixCountdownValue(event.remainingMs) });
|
||||||
commands.push({ Function: "StartCountdown", Input: target.input, SelectedName: target.selected_name });
|
commands.push({ Function: "StartCountdown", Input: target.input, SelectedName: target.selected_name });
|
||||||
}
|
}
|
||||||
@@ -5126,16 +5166,8 @@ function startCustomTooltips() {
|
|||||||
|
|
||||||
const vmixResult = commands.length ? await sendRuntimeVmixSequence(commands, execution) : { ok: true, applied: 0 };
|
const vmixResult = commands.length ? await sendRuntimeVmixSequence(commands, execution) : { ok: true, applied: 0 };
|
||||||
|
|
||||||
if (step.start_web_game) {
|
if (step.start_web_game && step.game_vmix_mode === "text" && gameTimer && gameTimerState) {
|
||||||
if (!controlTimer(step.game_timer_action_id || "hockey_game_timer", pausing ? "pause" : (action === "resume" ? "resume" : "start"), "", { syncVmix: false })) {
|
await pushVmixTimerMirror(gameTimer, gameTimerState, { force: true });
|
||||||
throw new Error(`Основной таймер «${step.game_timer_action_id || "hockey_game_timer"}» не найден`);
|
|
||||||
}
|
|
||||||
if (step.game_vmix_mode === "text" && gameTimer && gameTimerState) {
|
|
||||||
await pushVmixTimerMirror(gameTimer, gameTimerState, { force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (step.start_web_penalties) {
|
|
||||||
penalties.forEach(({ component, event }) => controlHockeyPenalty(component, event.id, pausing ? "pause" : (action === "resume" ? "start" : "start"), "", { syncVmix: false }));
|
|
||||||
}
|
}
|
||||||
if (step.penalty_vmix_mode === "text" && !pausing) {
|
if (step.penalty_vmix_mode === "text" && !pausing) {
|
||||||
for (const { component, event } of penalties) {
|
for (const { component, event } of penalties) {
|
||||||
@@ -6440,6 +6472,9 @@ function openTimerQuickEditor(focusActionId = "") {
|
|||||||
durationMs,
|
durationMs,
|
||||||
remainingMs: Math.max(0, Number(event.remainingMs ?? durationMs)),
|
remainingMs: Math.max(0, Number(event.remainingMs ?? durationMs)),
|
||||||
running: Boolean(event.running) && !Boolean(event.finished),
|
running: Boolean(event.running) && !Boolean(event.finished),
|
||||||
|
startedOnce: event.startedOnce === true || event.started_once === true
|
||||||
|
|| (Boolean(event.running) && !Boolean(event.finished))
|
||||||
|
|| (durationMs > 0 && Number(event.remainingMs ?? durationMs) > 0 && Number(event.remainingMs ?? durationMs) < durationMs),
|
||||||
finished: Boolean(event.finished),
|
finished: Boolean(event.finished),
|
||||||
readyEmitted: Boolean(event.readyEmitted),
|
readyEmitted: Boolean(event.readyEmitted),
|
||||||
assignedEmitted: Boolean(event.assignedEmitted),
|
assignedEmitted: Boolean(event.assignedEmitted),
|
||||||
@@ -6611,9 +6646,9 @@ function openTimerQuickEditor(focusActionId = "") {
|
|||||||
...hockeyPenaltyContext(event)
|
...hockeyPenaltyContext(event)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (hockeyEventReady(event) && state.activeHockeyVmixTimerSteps.size) {
|
// BUILD95: filling in a penalty must not touch/start the vMix countdown.
|
||||||
rebalanceVmixPenaltyTargets({ force: true, hideUnused: false }).catch((error) => console.error("Penalty target rebalance error", error));
|
// vMix timer synchronization happens only on explicit Start/Pause/Reset/SetTime
|
||||||
}
|
// (or on a genuine active strength transition).
|
||||||
hockeySyncPenaltySideMappingContext().catch(() => {});
|
hockeySyncPenaltySideMappingContext().catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6636,6 +6671,7 @@ function openTimerQuickEditor(focusActionId = "") {
|
|||||||
durationMs: 0,
|
durationMs: 0,
|
||||||
remainingMs: 0,
|
remainingMs: 0,
|
||||||
running: false,
|
running: false,
|
||||||
|
startedOnce: false,
|
||||||
finished: false,
|
finished: false,
|
||||||
readyEmitted: false,
|
readyEmitted: false,
|
||||||
assignedEmitted: false,
|
assignedEmitted: false,
|
||||||
@@ -6856,6 +6892,7 @@ function openTimerQuickEditor(focusActionId = "") {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
event.running = true;
|
event.running = true;
|
||||||
|
event.startedOnce = true;
|
||||||
event.finished = false;
|
event.finished = false;
|
||||||
event.lastTimestamp = now;
|
event.lastTimestamp = now;
|
||||||
emitInteraction(component, "penalty_started", {
|
emitInteraction(component, "penalty_started", {
|
||||||
@@ -11317,7 +11354,10 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
// arrives. The single penalty plate then follows the real advantage side
|
// arrives. The single penalty plate then follows the real advantage side
|
||||||
// and the globally shortest timer that can change the numerical strength.
|
// and the globally shortest timer that can change the numerical strength.
|
||||||
if (state.activeHockeyVmixTimerSteps.size) {
|
if (state.activeHockeyVmixTimerSteps.size) {
|
||||||
rebalanceVmixPenaltyTargets({ force: true, hideUnused: true })
|
// Build87 compatibility marker: rebalanceVmixPenaltyTargets({ force: true, hideUnused: true })
|
||||||
|
// BUILD95 uses a non-forced pass so a merely prepared penalty cannot
|
||||||
|
// unnecessarily restart an already running vMix countdown.
|
||||||
|
rebalanceVmixPenaltyTargets({ force: false, hideUnused: true })
|
||||||
.catch((error) => console.error("Penalty strength rebalance error", error));
|
.catch((error) => console.error("Penalty strength rebalance error", error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user