test 5
This commit is contained in:
2
app.py
2
app.py
@@ -29,7 +29,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.08.24.9"
|
||||
BUILD_VERSION = "2026.08.24.10"
|
||||
# compatibility: BUILD_VERSION = "2026.08.24.8"
|
||||
# compatibility: BUILD_VERSION = "2026.08.24.7"
|
||||
# compatibility: BUILD_VERSION = "2026.08.24.6"
|
||||
|
||||
@@ -1539,7 +1539,7 @@ class VmixAgentHub:
|
||||
if no_ack_delivery:
|
||||
# BUILD106: realtime operator traffic bypasses Mapping's ACK queue. The new
|
||||
# timer-fast-ordered mode deliberately sends each timer command as its own WebSocket
|
||||
# frame in strict order (Stop -> Set -> Start) and never waits for Agent/vMix ACK.
|
||||
# frame in strict caller order and never waits for Agent/vMix ACK.
|
||||
# Legacy timer-fast keeps vmix.batch compatibility. The shared realtime lock prevents
|
||||
# interleaving with F-key traffic without reintroducing the slow Mapping lock.
|
||||
async with self._device_vmix_shortcut_send_lock(target_device_id):
|
||||
|
||||
46
tests/test_build107_pause_change_countdown.py
Normal file
46
tests/test_build107_pause_change_countdown.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
APP_JS = (ROOT / "ui_builder" / "static" / "app.js").read_text(encoding="utf-8")
|
||||
APP_PY = (ROOT / "app.py").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def helper_block():
|
||||
start = APP_JS.index("function vmixCountdownSyncCommands")
|
||||
end = APP_JS.index("function buildShortcutRuntimeContext", start)
|
||||
return APP_JS[start:end]
|
||||
|
||||
|
||||
def test_pause_uses_pause_and_change_without_stop_reset():
|
||||
block = helper_block()
|
||||
pause_start = block.index('if (action === "pause" || action === "stop")')
|
||||
set_start = block.index('if (action === "set")', pause_start)
|
||||
pause_branch = block[pause_start:set_start]
|
||||
assert 'Function: "PauseCountdown"' in pause_branch
|
||||
assert 'Function: "ChangeCountdown"' in pause_branch
|
||||
assert 'Function: "StopCountdown"' not in pause_branch
|
||||
assert pause_branch.index('Function: "PauseCountdown"') < pause_branch.index('Function: "ChangeCountdown"')
|
||||
|
||||
|
||||
def test_start_uses_change_then_start_without_stop_or_set_duration():
|
||||
block = helper_block()
|
||||
start_branch = block[block.index("// Start/Resume:"):]
|
||||
assert 'Function: "ChangeCountdown"' in start_branch
|
||||
assert 'Function: "StartCountdown"' in start_branch
|
||||
assert 'Function: "StopCountdown"' not in start_branch
|
||||
assert 'Function: "SetCountdown"' not in start_branch
|
||||
assert start_branch.index('Function: "ChangeCountdown"') < start_branch.index('Function: "StartCountdown"')
|
||||
|
||||
|
||||
def test_set_only_changes_current_position_and_does_not_toggle_pause():
|
||||
block = helper_block()
|
||||
set_start = block.index('if (action === "set")')
|
||||
start_branch = block.index("// Start/Resume:", set_start)
|
||||
set_branch = block[set_start:start_branch]
|
||||
assert 'Function: "ChangeCountdown"' in set_branch
|
||||
assert 'Function: "PauseCountdown"' not in set_branch
|
||||
assert 'Function: "StopCountdown"' not in set_branch
|
||||
|
||||
|
||||
def test_runtime_version():
|
||||
assert 'BUILD_VERSION = "2026.08.24.10"' in APP_PY
|
||||
@@ -4153,12 +4153,10 @@ function startCustomTooltips() {
|
||||
}
|
||||
|
||||
function vmixCountdownSyncCommands(input, selectedName, millisecondsProvider, action = "start") {
|
||||
// BUILD106: Runtime is the ONLY source of truth for game/penalty time.
|
||||
// Important vMix detail: SetCountdown changes the countdown Duration; it does not
|
||||
// reliably replace the current position of a countdown that was previously started
|
||||
// or suspended. Therefore every operator state change performs a hard reseed:
|
||||
// freeze title rendering -> reset native countdown -> set Runtime value -> unfreeze.
|
||||
// Start/Resume then starts from that freshly seeded Runtime value.
|
||||
// BUILD107: Runtime remains the source of truth, but normal operator control no
|
||||
// longer uses StopCountdown because vMix defines it as Stop + Reset.
|
||||
// ChangeCountdown updates the CURRENT countdown position; SetCountdown only changes
|
||||
// Duration. PauseCountdown is used only for an actual Runtime pause transition.
|
||||
const target = { Input: String(input || "").trim(), SelectedName: String(selectedName || "").trim() };
|
||||
const renderTarget = { Input: target.Input };
|
||||
const currentValue = () => {
|
||||
@@ -4167,24 +4165,32 @@ function startCustomTooltips() {
|
||||
};
|
||||
if (!target.Input || !target.SelectedName) return [];
|
||||
|
||||
const reseedStopped = [
|
||||
{ Function: "PauseRender", ...renderTarget },
|
||||
{ Function: "StopCountdown", ...target },
|
||||
{ Function: "SetCountdown", ...target, Value: currentValue },
|
||||
{ Function: "ResumeRender", ...renderTarget },
|
||||
];
|
||||
|
||||
if (action === "stop" || action === "pause" || action === "set") {
|
||||
// Do not trust the native vMix current position on Pause. Freeze at the exact
|
||||
// Runtime value and leave the native countdown stopped. The next Resume will
|
||||
// hard-reseed again before StartCountdown.
|
||||
return reseedStopped;
|
||||
if (action === "pause" || action === "stop") {
|
||||
// Space pause: toggle the native countdown into Pause, then pin its current value
|
||||
// to the exact Runtime time. No StopCountdown reset is allowed in this path.
|
||||
return [
|
||||
{ Function: "PauseRender", ...renderTarget },
|
||||
{ Function: "PauseCountdown", ...target },
|
||||
{ Function: "ChangeCountdown", ...target, Value: currentValue },
|
||||
{ Function: "ResumeRender", ...renderTarget },
|
||||
];
|
||||
}
|
||||
|
||||
if (action === "set") {
|
||||
// Scoreboard/F1 pre-sync while Runtime is not running: update only the current
|
||||
// vMix position. Do not toggle PauseCountdown because it is a Pause/Resume toggle.
|
||||
return [
|
||||
{ Function: "PauseRender", ...renderTarget },
|
||||
{ Function: "ChangeCountdown", ...target, Value: currentValue },
|
||||
{ Function: "ResumeRender", ...renderTarget },
|
||||
];
|
||||
}
|
||||
|
||||
// Start/Resume: set the native CURRENT position from Runtime and then run it.
|
||||
// This avoids both StopCountdown reset and SetCountdown duration-only semantics.
|
||||
return [
|
||||
{ Function: "PauseRender", ...renderTarget },
|
||||
{ Function: "StopCountdown", ...target },
|
||||
{ Function: "SetCountdown", ...target, Value: currentValue },
|
||||
{ Function: "ChangeCountdown", ...target, Value: currentValue },
|
||||
{ Function: "ResumeRender", ...renderTarget },
|
||||
{ Function: "StartCountdown", ...target },
|
||||
];
|
||||
@@ -13915,7 +13921,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
||||
<div class="shortcut-finish-action-list">${timerFinishActionRows(step)}</div>
|
||||
|
||||
<div class="shortcut-vmix-inventory-note"><span>${escapeHtml(shortcutInventoryLabel())}</span><small>Для каждого countdown теперь обязательно выбирается конкретный Text / SelectedName. Это исключает отправку времени в первый текстовый элемент по умолчанию.</small></div>
|
||||
<p class="shortcut-step-note">Режим <b>Countdown vMix</b>: веб-таймер является единственным источником времени. Перед Start/Resume vMix выполняет <code>StopCountdown → SetCountdown(время Runtime) → StartCountdown</code>; при Pause/Stop vMix выполняет <code>StopCountdown → SetCountdown(время Runtime)</code> и остаётся остановленным на точном веб-времени. Команды идут в Agent без ACK отдельными кадрами строго по порядку. Каждую секунду значение в vMix не передаётся. <b>Text mirror</b> оставлен только для совместимости со старыми титрами. Для верхнего счёта используется одна penalty-плашка: при реальном большинстве она показывает ближайшее изменение численного состава; при чистом равном обоюдном удалении сама по себе не появляется.</p>
|
||||
<p class="shortcut-step-note">Режим <b>Countdown vMix</b>: веб-таймер является источником времени. Перед Start/Resume vMix выполняет <code>ChangeCountdown(время Runtime) → StartCountdown</code>; при Pause/Stop выполняет <code>PauseCountdown → ChangeCountdown(время Runtime)</code>. <code>StopCountdown</code> в обычном управлении не используется, потому что он сбрасывает countdown. Команды идут в Agent без ACK отдельными кадрами строго по порядку. Каждую секунду значение в vMix не передаётся. <b>Text mirror</b> оставлен только для совместимости со старыми титрами. Для верхнего счёта используется одна penalty-плашка: при реальном большинстве она показывает ближайшее изменение численного состава; при чистом равном обоюдном удалении сама по себе не появляется.</p>
|
||||
</div>`;
|
||||
} else if (step.type === "delay") {
|
||||
body.innerHTML = `<div class="shortcut-step-grid"><label>Задержка, мс<input type="number" min="0" max="10000" step="10" data-step-field="milliseconds" value="${Number(step.milliseconds) || 0}"></label></div>`;
|
||||
|
||||
Reference in New Issue
Block a user