Files
MBN_STOCK_WEBVIEW/docs/PLAYOUT.md

44 KiB

Tornado/K3D x64 송출 운영 가이드

안전 기준

기본 모드는 DryRun입니다. DryRun은 COM 객체를 만들거나 Tornado 출력에 명령을 보내지 않고 WebView 메시지, 큐, 상태 및 오류 표시 흐름만 검증합니다. 현재 방송 PROGRAM(PGM)에 연결된 Tornado 프로세스는 항상 안전하지 않은 대상으로 취급합니다. 프로세스 이름이나 실행 여부만으로 테스트 대상이라고 판단하지 않습니다.

이 저장소의 작업과 자동 검증은 실제 PGM 출력 또는 라이브 TAKE IN을 허가하지 않습니다. Test 검증은 PGM과 분리된 전용 테스트 인스턴스, 테스트 출력 채널 및 허용 목록에 든 테스트 씬을 모두 확인한 뒤에만 수행합니다. Live는 운영 책임자의 명시적인 회차별 허가 없이는 사용하지 않습니다.

확인된 x64 COM 등록

현재 개발 장비에서 확인할 등록 기준은 다음과 같습니다.

항목 기대값
레지스트리 뷰 HKLM\SOFTWARE\ClassesRegistry64; 관련 HKCU override 없음
TypeLib GUID {2B7F2D64-3A8D-401C-BE73-5C0747BA342C}
TypeLib 버전/대상 1.0 / 0\win64
KAEngine CLSID {D756CDBE-AA31-42B2-9CC7-018753CA61BF}
ProgID K3DAsyncEngine.KAEngine.1
KAEventHandler CLSID / ProgID {39828C77-EFF0-4E59-979B-8673C028C718} / K3DAsyncEngine.KAEventHandler.1
ThreadingModel Apartment
DLL PE 대상 TypeLib, KAEngine, KAEventHandler 모두 AMD64

Inspect-K3DRegistration.ps1은 64비트 HKLM 등록에서 GUID, 버전과 win64 TypeLib을 확인합니다. KAEngine과 KAEventHandler 각각의 CLSID→ProgID 및 ProgID→CLSID 양방향 매핑, Apartment 모델, InprocServer32 존재와 AMD64 PE 헤더도 검사합니다. HKCU 64비트 Software\Classes에 같은 TypeLib GUID, CLSID 또는 ProgID override가 하나라도 있으면 fail-closed합니다. 레지스트리와 파일을 읽기만 하며 COM을 활성화하거나 등록을 변경하지 않습니다.

MBN_STOCK_WEBVIEW.PlayoutSmoke --probe는 런타임과 같은 검사기를 통해 KAEngine과 KAEventHandler 등록을 함께 확인합니다. 이 probe도 COM 객체를 생성하지 않습니다.

powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\scripts\Inspect-K3DRegistration.ps1

검사가 실패하면 x86 등록으로 대체하거나 DLL을 앱 폴더에 복사하지 않습니다. 벤더가 제공한 x64 설치 프로그램과 라이선스 절차로 장비 상태를 복구한 뒤 다시 검사합니다.

런타임 참조 방식

앱은 빌드 시점의 Interop.K3DAsyncEngineLib.dll을 참조하거나 패키징하지 않습니다. 런타임에는 HKLM Registry64의 TypeLib·KAEngine·KAEventHandler 세 등록이 모두 같은 DLL\x64\Release\K3DAsyncEngine.dll을 가리키고 HKCU override가 없을 때만, 그 벤더 루트의 정확한 Bin\x64\C#\Interop.K3DAsyncEngineLib.dll 하나를 사용합니다. 상위 폴더 검색이나 obj/artifacts fallback은 없습니다. 두 파일과 모든 상위 경로는 reparse point가 아니어야 하며 x64 PE인지 확인합니다. COM 객체와 호출 세부 사항은 IPlayoutEngine 구현 안에 격리합니다.

등록된 네이티브 K3DAsyncEngine.dll과 설치된 C# Interop은 모두 로드 전에 운영자가 승인한 SHA-256과 일치해야 합니다. 현재 프로세스 환경의 MBN_STOCK_K3D_NATIVE_SHA256MBN_STOCK_K3D_INTEROP_SHA256에 각각 공백 없는 64자리 16진수만 허용하며, 일반 Test/Live 어댑터와 PGM 연결 전용 진단이 같은 두 핀을 사용합니다. 먼저 벤더 배포 해시, 신뢰된 설치 매체 또는 관리자 검수로 두 파일을 독립적으로 승인한 뒤 그 정확한 파일들을 대상으로 다음 값을 설정합니다.

$approvedNative = '<vendor-or-admin-approved-x64-native-file>'
$approvedInterop = '<vendor-or-admin-approved-x64-interop-file>'
$nativeHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $approvedNative).Hash
$interopHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $approvedInterop).Hash
if ($nativeHash -notmatch '^[0-9A-Fa-f]{64}$' -or
    $interopHash -notmatch '^[0-9A-Fa-f]{64}$') { throw 'Invalid SHA-256.' }
$env:MBN_STOCK_K3D_NATIVE_SHA256 = $nativeHash
$env:MBN_STOCK_K3D_INTEROP_SHA256 = $interopHash

검수하지 않은 현재 설치 파일의 해시를 계산해 그대로 승인하는 것은 독립적인 신뢰 확인이 아닙니다. 파일 업데이트 후 어느 핀이든 바꾸는 행위는 새 바이너리를 신뢰한다는 명시적 운영 결정이므로 변경 이력과 승인 근거를 남기고, 위 $env:를 설정한 같은 셸에서 앱 또는 진단을 시작합니다. 핀과 설치 경로는 결과 JSON이나 로그에 출력하지 않습니다. 핀이 없거나 형식·해시·경로·PE·Interop 메타데이터 검사가 하나라도 실패하면 installed-interop-metadata-unavailable로 COM 활성화 전에 종료합니다. 검사를 통과한 두 파일은 프로세스 수명 동안 쓰기·교체 공유를 허용하지 않는 읽기 핸들로 유지합니다.

이 이중 핀과 장기 파일 잠금은 개발 워크스테이션에서 우발적 교체 및 검사-사용 시점(TOCTOU) 교체를 완화하는 방어입니다. 운영 장비에서는 이것만으로 충분하지 않으며, 벤더 설치 루트 ACL을 Administrators/SYSTEM만 쓰기 가능하고 일반 Users는 읽기·실행만 가능하도록 배포 단계에서 구성해야 합니다. 또한 서명된 벤더 패키지 또는 관리자가 별도로 승인한 배포물을 사용해야 합니다. 앱과 진단은 ACL을 수정하거나 취약한 설치를 자동으로 신뢰하지 않습니다.

타입 정보를 조사해야 할 때만 Generate-K3DInterop.ps1을 선택적으로 사용합니다. 생성 전 Inspect와 같은 HKLM 양방향 매핑·두 COM 서버 AMD64·HKCU override 부재 검사를 통과해야 합니다. 그 뒤 알려진 Windows SDK의 x64 TlbImp.exe 위치를 선택하거나 명시적으로 받은 x64 경로를 검증하고 /machine:X64로 생성합니다. 입력 TypeLib과 도구 및 결과물 모두 AMD64인지 확인하며, 결과는 Git에서 제외된 다음 위치에만 씁니다.

artifacts\K3DInterop\Interop.K3DAsyncEngineLib.dll

실행 전 계획만 확인:

powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\scripts\Generate-K3DInterop.ps1 -WhatIf

진단용 Interop 생성:

powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\scripts\Generate-K3DInterop.ps1

SDK가 기본 위치에 없다면 x64 SDK의 TlbImp.exe 절대 경로를 -TlbImpPath에 지정합니다. 생성물은 타입 조사용일 뿐 프로젝트 참조나 MSIX 콘텐츠로 추가하지 않습니다. 런타임 resolver도 이 artifacts 생성물을 찾거나 로드하지 않습니다. 이 스크립트도 COM 활성화와 등록 변경은 하지 않습니다.

로컬 설정

런타임 설정 기본 경로는 다음과 같습니다.

%LOCALAPPDATA%\MBN_STOCK_WEBVIEW\Config\playout.local.json

playout.example.json을 구조 참고용으로 사용합니다. 예시는 DryRun, 외부 씬 루트와 출력 채널 미지정, 빈 씬 허용 목록, 라이브 신뢰 플래그 해제 상태이므로 실제 출력에 사용할 수 없습니다. 테스트 장비의 호스트, 채널, 창 제목 패턴, 외부 씬 루트와 허용할 씬 이름은 로컬 파일에만 기록하고 Git, 로그 또는 지원 첨부파일에 넣지 않습니다.

속성 의미
mode Disabled, DryRun, Test, Live 중 하나
host, port 테스트 또는 운영 승인을 받은 KTAP endpoint
tcpMode, clientPort KTAPConnect의 전송 모드와 로컬 client port 인자. Test/Live는 유실 방지를 위해 tcpMode: 1만 허용
sceneDirectory Test/Live에서 사용하는 외부 .t2s 루트의 절대 경로. null은 안전한 미설정 상태
outputChannel 확인된 전용 출력 채널. null은 안전한 미설정 상태
layoutIndex 씬 플레이어 layout 위치
legacySceneFadeDuration 원본 ComboDi.SelectedIndex에 대응하는 fade. 기본값 6, 허용 범위 0~60
legacySceneBackgroundKind trusted 공통 배경 None, Texture, Video; Web에서 변경할 수 없음
legacySceneBackgroundAssetPath sceneDirectory 아래의 상대 asset. 경로 탈출·reparse·누락 파일은 DryRun에서도 거부
legacySceneBackgroundVideoLoopCount, legacySceneBackgroundVideoLoopInfinite 공통 video 배경의 bounded loop 설정
testProcessWindowTitlePattern 전용 테스트 인스턴스만 식별하는 창 제목 패턴
testSceneAllowlist 실제 COM을 사용하는 TestLive 모두에서 허용한 scene name(code)의 폐쇄형 목록. 경로나 확장자는 넣지 않음(기존 설정 호환을 위해 이름 유지)
trustedLiveOutputEnabled 운영자가 로컬 파일에서만 설정하는 라이브 1차 게이트
queueCapacity 직렬 STA 명령 큐의 최대 대기 항목 수
*TimeoutMilliseconds 연결, 작업 및 해제 제한 시간
processPollIntervalMilliseconds Tornado2 접두사 프로세스 감시 주기
reconnect* 재연결 지연, 최대 횟수 및 활성화 여부

JSON보다 다음 환경 변수가 우선합니다.

MBN_STOCK_PLAYOUT_MODE
MBN_STOCK_PLAYOUT_HOST
MBN_STOCK_PLAYOUT_PORT
MBN_STOCK_PLAYOUT_TCP_MODE
MBN_STOCK_PLAYOUT_CLIENT_PORT
MBN_STOCK_PLAYOUT_SCENE_DIRECTORY
MBN_STOCK_PLAYOUT_OUTPUT_CHANNEL
MBN_STOCK_PLAYOUT_LAYOUT_INDEX
MBN_STOCK_PLAYOUT_LEGACY_FADE_DURATION
MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_KIND
MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_ASSET
MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_VIDEO_LOOP_COUNT
MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_VIDEO_LOOP_INFINITE
MBN_STOCK_PLAYOUT_TEST_WINDOW_TITLE_PATTERN
MBN_STOCK_PLAYOUT_QUEUE_CAPACITY
MBN_STOCK_PLAYOUT_CONNECT_TIMEOUT_MS
MBN_STOCK_PLAYOUT_OPERATION_TIMEOUT_MS
MBN_STOCK_PLAYOUT_DISCONNECT_TIMEOUT_MS
MBN_STOCK_PLAYOUT_PROCESS_POLL_INTERVAL_MS
MBN_STOCK_PLAYOUT_RECONNECT_DELAY_MS
MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS
MBN_STOCK_PLAYOUT_RECONNECT_ENABLED

testSceneAllowlisttrustedLiveOutputEnabled는 환경 변수로 변경할 수 없으며 로컬 설정 파일에서만 관리합니다. 이름은 기존 설정 호환을 위해 유지하지만 allowlist는 Test뿐 아니라 Live의 PREPARE, TAKE IN 재검사, NEXT와 timer refresh에도 적용되고 비어 있으면 실제 모드를 시작하지 않습니다. 공통 background/fade도 Web payload가 아니라 이 trusted 프로세스 설정 경계에서만 결정합니다. PlayoutSceneCompositionFactory는 첫 DryRun 또는 실제 PREPARE 전에 background asset의 상대 경로, 허용 확장자, 존재 여부와 reparse ancestry를 검사하며 실제 경로는 Web status/preview에 노출하지 않습니다. SceneDirectory는 Test/Live에서 존재하는 비-reparse 외부 디렉터리여야 하며, 엔진은 상대 .t2s 파일을 정규화해 이 루트 밖으로 나가는 경로를 거부합니다. scene file의 basename과 scene name 및 allowlist 항목도 서로 일치해야 합니다. 설정 파일은 실행 계정만 읽을 수 있도록 ACL을 제한합니다. 라이선스 키나 인증정보를 이 파일에 기록하지 않습니다.

KTAP 포트와 Network Monitoring 판정

K3DAsyncEngine 매뉴얼의 KTAPConnect(bTCP, HostAddress, nHostPort, nClientPort, handler)에서 Test/Live는 tcpMode: 1(TCP)만 허용하므로 로컬 JSON의 port는 격리 Test Tornado의 Tools > Option > Control > Network Server > TCP Port와 정확히 같아야 합니다. clientPort는 UDP일 때만 의미가 있고 TAP TCP Port/TAP UDP Port는 이 연결의 host port가 아닙니다. 매뉴얼 18쪽의 설명과 26쪽 및 192쪽 예시에 표시된 포트 숫자가 서로 다르므로 30001/30002를 추정하거나 원본·예제 값을 복사하지 않습니다. 해당 회차 Test 인스턴스 화면의 설정값이 유일한 기준입니다. playout.example.json30001도 DryRun 구조 예시일 뿐 새 Test endpoint의 검증값이 아닙니다.

Tornado2의 View > Network Monitoring Window에서 [R]은 서버가 클라이언트 요청을 받은 기록, [S]는 서버가 응답을 보낸 기록이며 TCPSession은 TCP 세션 수입니다(매뉴얼 26~27쪽). 프로세스 감지, COM 등록 probe 또는 COM 객체 생성만으로는 이 기록이 생기지 않습니다. 기본 앱과 --dry-run, --probe, --test-plan은 KTAP를 호출하지 않으므로 빈 모니터가 정상입니다.

상태의 accepted-unconfirmedKTAPConnect가 SDK 성공값 1을 반환했다는 뜻일 뿐입니다. 매뉴얼 41쪽의 OnHello 콜백이나 실제 [R]/[S]를 자동 확인했다는 뜻이 아닙니다. connect-only --pgm-connect-diagnostic은 렌더 API 표면을 제거한 별도 binding이라 callback을 관찰하지 않고 ktapHelloObservednull로 보고합니다. 일반 IPlayoutEngine 경로는 검증된 282-method DynamicK3dEventHandlerOnHello와 lifecycle callback을 수신하지만, callback 미수신 상태를 성공으로 추정하지 않으며 Network Monitoring은 계속 사람이 대조합니다. lastKtapConnectState는 현재 연결 상태가 아니라 가장 최근 KTAP dispatch 시도의 증거이며, 화면은 Connected/Faulted 같은 현재 상태와 분리해 표시합니다. 따라서 격리 --test-connect가 성공했는데도 같은 시각의 [R]/[S]가 전혀 없다면 --test-sequence로 진행하지 말고 mode/config 파일, 실제 Network Server TCP Port와 안전 게이트 거부 여부를 먼저 확인합니다.

PGM 네트워크 연결 전용 진단

Tornado2의 PGM 창은 본 프로그램이 보낸 KTAP 송출 명령을 표시하는 렌더 출력 창입니다. 창 제목만으로 실제 방송 라우팅 여부를 판정할 수 없으므로 기존 Test 게이트에서 PGM을 허용하지 않습니다. 대신 운영자가 Network Monitoring 창을 먼저 열고 승인한 회차에만 다음 별도 명령으로 네트워크 세션 왕복을 확인합니다.

dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
  -c Debug -p:Platform=x64 -- `
  --pgm-connect-diagnostic `
  --i-understand-this-will-contact-current-pgm-tornado-via-ktap-connect-and-disconnect-only `
  --host 127.0.0.1 `
  --port <Tornado-Network-Server-TCP-Port> `
  --expected-pgm-window-title PGM

이 명령은 설정 파일과 MBN_STOCK_PLAYOUT_* 환경 override를 받지 않으며 host는 숫자형 loopback만 허용합니다. 정확히 하나의 Tornado2* 프로세스, 지정한 PGM 창 제목, 프로세스 세대와 해당 TCP LISTEN 소유권을 COM 전·KTAP 직전·Disconnect 직전에 반복 확인합니다. 자동 재연결과 재시도는 없고 연결·해제 timeout은 각각 3초로 고정됩니다.

COM 경로에는 KTAPConnect와 성공 후 최대 한 번의 Disconnect만 있으며 GetScenePlayer[OnChannel], LoadScene, Prepare, Play, PlayDirect, CutOut, Stop, StopAll을 호출할 수 있는 API 표면이 없습니다. 따라서 명시적인 장면/레이어 출력 명령은 발생하지 않습니다. 다만 PGM에 TCP 제어 세션을 추가하므로 완전한 무영향을 보장하는 명령은 아니며, timeout이나 대상 변경 후에는 반복하지 않습니다.

성공 JSON의 lastKtapConnectState: "accepted-unconfirmed", completed: true, outcomeUnknown: false, renderCommandSurfaceExposed: false, renderCommandAttempted: false를 확인하고 같은 시각의 Network Monitoring [R] HELLO[S] SUCCESS HELLO를 사람이 확인합니다. GetScenePlayer를 호출하지 않으므로 이 진단의 성공은 장면 송출 준비 완료를 의미하지 않습니다.

x64 SDK의 네이티브 DLL과 정식 Interop은 위의 Registry64 단일 경로·reparse ancestry·AMD64 PE·운영자별 SHA-256 핀을 모두 통과해야 합니다. 두 파일을 쓰기/교체할 수 없게 연 핸들은 COM 활성화와 사용을 포함한 프로세스 수명 동안 유지합니다. Interop 로드 후에는 assembly/TypeLib 버전, COM import GUID와 허용 메서드 서명도 확인합니다. vendor DLL은 Git 또는 MSIX에 복사하지 않습니다.

실제 PGM 컷 시퀀스 검증

2026-07-10에 방송 운영자의 명시적 승인 아래 현재 Tornado2 PGM과 다음 고정 시퀀스의 실제 왕복을 완료했습니다.

최초 목표의 실제 호출 검증은 격리 Test 인스턴스를 전제로 했지만, 이후 운영자가 현재 PGM을 이번 회차의 검증 대상으로 명시적으로 지정하고 실제 출력을 승인했습니다. 이에 따라 아래 고정 테스트 컷의 성공 회차를 이번 목표의 일회성 대체 검증으로 사용합니다. 이는 PGM을 일반 Test 인스턴스로 인정한다는 뜻이 아니며, 앱의 기본 DryRun, Test 대상 격리 및 Live 이중 승인 조건은 그대로 유지됩니다. 별도 Test 환경의 MSIX WebView→COM 검증은 운영 배포 전 후속 검증입니다.

Connect → Prepare(5001) → Play → 5초 관찰
        → Prepare(5006) → Play → 5초 관찰
        → StopAll → 5초 관찰 → Disconnect

이 검증은 일반 Test/Live 설정을 완화하지 않는 별도 --pgm-cuts-sequence 경로입니다. 호출자가 장면 code나 관찰 시간을 바꿀 수 없고 숫자형 loopback, 정확한 PGM 창 제목, 단일 Tornado2 프로세스와 그 프로세스의 LISTEN 소유권을 각 SDK 명령과 관찰 구간 전후에 다시 검사합니다. MBN_STOCK_PLAYOUT_* 및 .NET startup hook/profiler 주입 환경 변수가 있으면 시작하지 않으며 자동 재연결과 자동 재생은 항상 꺼집니다. 사용한 컷은 다음 두 파일과 SHA-256으로 고정됩니다.

승인 SHA-256
5001.t2s 99CE3B689A42D8C42BEB09A86FA10C2D7C1AEF4F50D324D81276C1A1E4C4D8A7
5006.t2s 25CD0AE931F51E4E3B84CE3E6FD21A40DB85464F157A23CC3511D63B336D8757

재검증이 승인된 경우에는 임의의 dotnet run 대신 Invoke-PgmCutsSequenceEvidence.ps1을 사용합니다. 먼저 x64 빌드 산출물과 두 컷의 변경이 없는지 독립적으로 검토하고, vendor/관리자가 승인한 네이티브·Interop 해시와 검토한 runner 산출물 해시를 인수로 전달합니다. Approved*Sha256 값은 실행 시점의 파일을 단순 계산해 곧바로 승인한 값으로 사용하지 않습니다. 실제 vendor 설치 경로와 컷 루트는 로컬 승인 기록에만 보관합니다.

$smoke = '<reviewed-x64-runner>\MBN_STOCK_WEBVIEW.PlayoutSmoke.dll'
$cuts = '<approved-local-cut-root>'

powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\scripts\Invoke-PgmCutsSequenceEvidence.ps1 `
  -SmokeDll $smoke `
  -SceneRoot $cuts `
  -ApprovedNativeSha256 '<vendor-approved-native-sha256>' `
  -ApprovedInteropSha256 '<vendor-approved-interop-sha256>' `
  -ApprovedSmokeSha256 '<reviewed-smoke-sha256>' `
  -ApprovedPlayoutSha256 '<reviewed-playout-sha256>' `
  -ApprovedCoreSha256 '<reviewed-core-sha256>' `
  -ApprovedDepsSha256 '<reviewed-deps-sha256>' `
  -ApprovedRuntimeConfigSha256 '<reviewed-runtimeconfig-sha256>' `
  -ApprovedWindowsSdkSha256 '<reviewed-windows-sdk-sha256>' `
  -ApprovedWinRtSha256 '<reviewed-winrt-sha256>' `
  -ExpectedPgmWindowTitle PGM `
  -Port <verified-ktap-listener-port> `
  -IUnderstandPgmWillRenderCuts

스크립트는 실행 전에 PGM과 Network Monitoring 창을 같은 화면에 배치해 기준 프레임 네 장을 남기고, 실행 중 약 500ms 간격으로 두 창을 함께 캡처합니다. runner와 관련 managed 산출물을 승인 해시로 확인해 읽기 잠금을 유지하고, runner 내부도 네이티브·Interop 및 컷 파일의 해시와 안전한 로컬 경로를 다시 확인합니다. 종료 후에는 세 observation-start 표식, 각 관찰 시간이 4,900ms 이상인지, 단계별 성공 순서, 안전 상태, runner 종료 code와 최소 프레임 수를 검증해 manifest.json에 각 파일·프레임의 해시와 함께 기록합니다. 창 위치와 topmost 상태는 마지막에 원래대로 복원합니다.

성공 회차의 terminal 결과는 connect, prepare-first, take-in, next, take-out, disconnect가 모두 Success였고 completed: true, outcomeUnknown: false, renderCommandAttempted: true, disconnectAttempted: true, quarantineAttempted: false였습니다. 실제 관찰 구간은 각각 5051ms, 5052ms, 5093ms였습니다. 캡처에서 PGM은 먼저 5001 화면, 다음으로 5006 화면을 표시했고 StopAll 뒤 마지막 관찰 구간에는 검은 화면으로 돌아왔습니다. 같은 캡처의 Network Monitoring에는 시간 순서대로 HELLO, LOAD_SCENE, SCENE_PREPARE, PLAY, 두 번째 LOAD_SCENE, SCENE_PREPARE, PLAY, STOPAL, BYE 요청/응답 기록이 남았습니다. STOPAL은 모니터에 표시된 KTAP 명령 표기입니다.

성공 증거는 이 장비의 Git 제외 경로 artifacts/pgm-evidence/20260710_155214_success/manifest.json에 보존했으며 SHA-256은 2B3AA4F9B4AC4FD00613BE708D00BCDAA6687135EC06B8228F95F27482B230BB입니다. 이 로컬 증거는 Git/MSIX에 포함되지 않습니다. 장기 보관할 때는 승인된 증거 저장소로 디렉터리 전체를 복사한 뒤 이 manifest 해시와 내부 프레임 해시를 다시 대조합니다. 해당 manifest는 runner/evidence 종료 code 0, terminalValidated: true, 39개 프레임, 빈 captureErrorsvalidationErrors도 기록합니다.

첫 회차는 Connect: Success 뒤 출력 전 prepare-first: Rejected로 중단되고 안전한 Disconnect: Success만 수행했습니다. 원인은 사전 검사에서 상대 cue를 절대 경로로 한 번 resolve한 뒤 그 이미 resolve된 cue를 TornadoPlayoutEngine에 전달해 엔진이 두 번째 resolve에서 거부한 것이었습니다. 수정 후 절대 경로는 승인 자산 검사와 파일 lease에만 사용하고, 엔진에는 고정 상대 cue인 5001.t2s5006.t2s를 전달합니다. 상대 cue와 검증용 절대 자산 경로가 분리되는 회귀 테스트도 추가했습니다. 이 회차는 Play 전에 결과가 명확히 거부되고 같은 대상의 Disconnect 성공까지 확인됐기 때문에 원인 수정 후 새 회차를 진행할 수 있었습니다. timeout, OutcomeUnknown, 대상 교체 또는 출력 결과가 불명확한 경우에는 자동·수동으로 반복하지 않고 quarantine 뒤 PGM 상태를 사람이 먼저 확인합니다.

PGM 전용 시퀀스의 outputChannel은 의도적으로 비워 원본 MainForm 연결과 같은 GetScenePlayer()를 사용합니다. 검증되지 않은 임의 채널을 추정해 GetScenePlayerOnChannel()을 호출하지 않기 위함입니다. TAKE OUT은 원본의 현재 운영 경로와 같이 TakeOut(All)StopAll()로 매핑합니다. 과거 CutOut(10)보다 현재 재생기의 모든 레이어를 정리해 최종 PGM이 검은 화면으로 돌아오는 동작과 일치합니다. 현재 35개 scene builder, 34개 도달 runtime, PageN과 실제 데이터 mutation은 포팅·자동/DB 검증을 마쳤지만, 이 과거 고정 runner는 현재 WebView의 fresh TAKE IN, Page NEXT, timer refresh와 실제 화면 동등성을 검증한 회차가 아니므로 완료 증거로 사용하지 않습니다.

모드와 안전 게이트

Disabled는 모든 송출 명령을 거부하는 운영 롤백 모드입니다. DryRun은 COM 없이 WebView 동작을 성공 결과로 모의합니다. TestLive만 등록된 COM을 사용할 수 있습니다.

Test 전환 전 다음 조건을 모두 사람이 확인합니다.

  1. Tornado 인스턴스가 현재 PGM과 물리적·논리적으로 분리되어 있고 Test의 host가 로컬 loopback IP literal(127.0.0.1 또는 ::1)입니다. localhost를 포함한 호스트 이름은 허용하지 않습니다.
  2. 전용 테스트 창 제목에 독립된 TEST 토큰(예: Tornado2 TEST)이 있고 testProcessWindowTitlePattern이 그 인스턴스 하나만 식별합니다. 빈 제목이나 일반 Tornado2 제목은 허용하지 않습니다.
  3. outputChannel이 라우터/엔진에서 테스트 출력임을 확인했습니다.
  4. sceneDirectory가 승인된 테스트 자산 루트이고 사용할 scene name이 testSceneAllowlist에 있으며 운영 씬이 아닙니다.
  5. 같은 플레이리스트로 DryRunPREPARE, TAKE IN, NEXT, TAKE OUT 메시지와 화면 상태를 먼저 확인했습니다.

프로세스 감시는 이름이 Tornado2로 시작하는 모든 프로세스를 대소문자 구분 없이 찾습니다. 이는 가용성 신호일 뿐 송출 권한이 아닙니다. Test에서는 Tornado2 프로세스가 정확히 하나이고 그 창 제목이 독립된 TEST 토큰과 설정 규칙에 모두 일치해야 합니다. PGM/PROGRAM, 빈 제목, 일반 Tornado2 제목, TEST 표식 없는 제목 또는 여러 인스턴스가 모호하면 COM을 활성화하지 않습니다. 즉 현재 방송 PGM이 실행 중인 장비에서는 창 제목이 순간적으로 바뀌더라도 Test 연결을 거부합니다.

Test에서 로컬 프로세스/창 제목 검사를 원격 KTAP endpoint의 신원 증명으로 사용하지 않습니다. 따라서 Test 모드는 loopback endpoint만 허용하며, 원격 host는 아래의 Live 이중 승인을 모두 통과한 경우에만 사용할 수 있습니다.

Live에는 별도의 이중 승인이 필요합니다.

  1. 로컬 JSON의 trustedLiveOutputEnabled를 명시적으로 true로 설정합니다.
  2. 그 회차에만 프로세스 환경 변수 MBN_STOCK_PLAYOUT_AUTHORIZE_LIVE_OUTPUT을 정확히 I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH로 설정합니다.

둘 중 하나라도 없으면 라이브 출력을 거부해야 합니다. 이 값은 사용자/시스템 영구 환경 변수로 저장하지 않으며 앱 종료 후 제거합니다. 위에서 완료한 PGM 컷 검증은 승인된 한 회차에만 사용할 수 있는 별도 고정 runner를 통한 것이며, 일반 앱의 Live를 자동 승인하거나 이 이중 게이트를 완화하지 않습니다.

안전한 스모크 절차

먼저 modeDryRun이고 라이브 환경 변수가 없는지 확인합니다.

Remove-Item Env:MBN_STOCK_PLAYOUT_AUTHORIZE_LIVE_OUTPUT -ErrorAction SilentlyContinue
powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\scripts\Inspect-K3DRegistration.ps1
dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
  -c Debug -p:Platform=x64 -- --probe
dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
  -c Debug -p:Platform=x64 -- --dry-run
dotnet test .\MBN_STOCK_WEBVIEW.sln -c Debug -p:Platform=x64
dotnet test .\MBN_STOCK_WEBVIEW.sln -c Release -p:Platform=x64
powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\scripts\Test-WebPlayout.ps1

PlayoutSmoke --probe의 기본 출력은 등록 준비 여부·issue 이름, Tornado2 프로세스 수와 PROGRAM 감지 여부만 제공합니다. 벤더 DLL 전체 경로, 프로세스 ID/이름 및 창 제목은 출력하거나 로그에 남기지 않습니다. 정확한 로컬 등록 경로가 필요한 관리자는 Inspect-K3DRegistration.ps1 결과를 해당 장비에서만 확인하고 지원 첨부파일에 포함하지 않습니다.

격리 Test 단계별 CLI

실제 Test 검증은 아래 세 단계를 순서대로 사용합니다. 세 명령 모두 절대 경로의 일반 로컬 JSON 파일과 --acknowledge-isolated-non-program-test-output 승인이 필요합니다. MBN_STOCK_PLAYOUT_* 환경 변수가 하나라도 존재하면 파일과 환경의 합성 결과가 달라지는 것을 막기 위해 엔진 생성 전에 거부합니다. JSON은 반드시 mode: "Test", trustedLiveOutputEnabled: false여야 하며 기존 Test 안전 게이트를 그대로 통과해야 합니다.

이 장비의 Tornado2 설정은 실행 파일별 프로필이 아니라 현재 사용자 공용 HKCU\Software\Visual Research Inc\Tornado2\Option\Control에 저장됩니다. 설치된 Tornado2 매뉴얼 286쪽도 프로그램 실행 시 이 Network Server 설정의 자동 시작을 설명하며 별도 명령행 프로필이나 다중 인스턴스 절차를 제시하지 않습니다. 따라서 C:\Tornado2에 함께 설치된 다른 버전 EXE를 PGM 옆에서 실행해 TEST로 간주하지 않습니다. 동일 포트, 동일 출력 보드와 동일 사용자 설정을 공유할 수 있습니다.

권장 구성은 별도 TEST 장비 또는 독립 VM/Windows 사용자 프로필에서 출력 보드를 preview 전용 또는 None으로 두고, 그 환경에 Tornado2를 정확히 하나만 실행하는 것입니다. 같은 장비를 사용해야 한다면 방송 운영자가 승인한 정비 시간에 PGM을 정상 종료하고 Tornado UI에서 Network Server와 출력 채널을 TEST 전용으로 구성하며, 창 제목에 TEST 식별자가 나타나는 공식 운영 절차까지 확인한 뒤 진행합니다. 실행 중 PGM의 레지스트리나 옵션을 자동 변경하거나 다른 버전 EXE를 병렬 실행하지 않습니다.

먼저 씬 파일과 설정만 검사합니다. 이 단계는 엔진·STA·COM을 생성하지 않으며 실행 중 프로세스의 적격성도 아직 판단하지 않습니다. 승인된 Test 씬 후보인 5001.t2s5006.t2s를 사용할 때 로컬 JSON의 외부 sceneDirectorytestSceneAllowlist에는 각각 basename 5001, 5006을 설정합니다. 실제 절대 경로는 로컬 JSON에만 두고 저장소 문서나 예제 설정에 기록하지 않습니다.

$config = [IO.Path]::GetFullPath(
  "$env:LOCALAPPDATA\MBN_STOCK_WEBVIEW\Config\playout.test.local.json")

dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
  -c Debug -p:Platform=x64 -- `
  --test-plan `
  --acknowledge-isolated-non-program-test-output `
  --config $config `
  --prepare 5001 `
  --next 5006 `
  --observe-ms 5000

자산 검사가 끝나면 COM을 만들기 전, 즉 --test-connect 직전에 다음 읽기 전용 점검으로 프로세스, 현재 사용자 Network Server 설정과 LISTEN 소유권을 함께 확인합니다. ExpectedTestWindowTitle은 실제 창 제목과 대소문자만 무시하고 정확히 같아야 하며 독립된 TEST 토큰이 필요합니다. 결과에는 PID, IP 주소와 실제 창 제목을 기록하지 않습니다.

powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\scripts\Inspect-TornadoTestIsolation.ps1 `
  -ExpectedTcpPort <Test-Tornado-Network-Server-TCP-Port> `
  -ExpectedTestWindowTitle 'Tornado2 TEST'

ready: true인 경우에만 --test-connect로 진행합니다. 이 점검은 COM과 KTAPConnect를 호출하지 않으므로 Network Monitoring 기록이 없어야 합니다. program-window-detected, network-address-not-loopback, listener-address-not-loopback, listener-not-owned-by-test-instance 중 하나라도 있으면 현재 실행 환경은 격리 TEST가 아닙니다. 레지스트리 설정뿐 아니라 실제 LISTEN endpoint도 모두 loopback이어야 하며 wildcard(0.0.0.0, ::) 리스너는 거부합니다.

별도 Test Tornado만 정확히 하나 실행되고 PGM/PROGRAM 창이 전혀 없는 것을 사람이 다시 확인한 뒤 연결만 검증합니다. 이 명령은 씬이나 출력 상태를 변경하지 않고 Connect → Disconnect만 요청합니다.

명령 전에 Network Monitoring을 열고 TCPSession과 로그 시각을 기준선으로 기록합니다. --test-plan에는 변화가 없어야 합니다. --test-connect는 짧게 Connect → Disconnect하므로 세션 수가 곧 원래 값으로 돌아갈 수 있습니다. 일시적인 세션 수만 보지 말고 같은 시각의 [R]/[S] 양방향 기록을 확인합니다. [R]만 있고 [S]가 없거나 양쪽 모두 없으면 명령을 반복하지 말고 중단합니다. JSON의 completed: true, outcomeUnknown: false, lastKtapConnectState: "accepted-unconfirmed"와 운영자가 본 모니터 기록을 함께 확인한 뒤에만 5001→5006 시퀀스로 진행합니다.

dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
  -c Debug -p:Platform=x64 -- `
  --test-connect `
  --acknowledge-isolated-non-program-test-output `
  --config $config

마지막으로 격리된 Test 모니터를 관찰하면서 실제 시퀀스를 실행합니다. --observe-msTAKE IN, NEXT, TAKE OUT 뒤에 각각 적용되는 필수 관찰 시간이며 1,000~30,000ms만 허용합니다.

dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
  -c Debug -p:Platform=x64 -- `
  --test-sequence `
  --acknowledge-isolated-non-program-test-output `
  --config $config `
  --prepare 5001 `
  --next 5006 `
  --observe-ms 5000

시퀀스는 Connect → Prepare(5001) → TakeIn → 관찰 → Next(5006) → 관찰 → TakeOut(All) → 관찰 → Disconnect 순서입니다. K3D의 Play/Stop 완료는 비동기 이벤트이므로 마지막 관찰 창이 끝나기 전에는 Disconnect하지 않습니다. 자동 재연결은 CLI가 강제로 비활성화합니다. 성공한 TakeIn 뒤 관찰 취소처럼 결과가 확정된 중단이면 TakeOut(All)을 한 번만 정리 단계로 요청한 뒤, 정리가 성공한 경우에만 Disconnect합니다. 이미 실행 결과가 불명확하거나 TakeOut이 어떤 비성공 결과라도 반환하면 출력이 남아 있을 수 있으므로 추가 출력 명령과 SDK Disconnect를 보내지 않습니다. 이때 QuarantineAsync가 같은 STA에서 제어 메서드 호출 없이 로컬 COM 참조만 해제한 다음 bounded 어댑터 폐기를 수행합니다. quarantine 자체를 완료하지 못하면 의도하지 않은 Disconnect보다 로컬 누수를 택해 일반 Dispose도 생략합니다. 어느 경우든 격리 모니터에서 최종 Test 출력 상태를 사람이 확인해야 합니다.

현재 런타임은 vendor의 282-method event handler에서 OnScenePlayed, OnCutOut, OnStopAll을 managed callback queue로 수신합니다. OnScenePlayed 성공 뒤에만 retired scene을 unload하고, pending Play callback이 있으면 TAKE OUT 이외의 mutation 명령을 fail-closed 차단합니다. CutOut/StopAll completion counter는 dispatch 직전에 증가하고 동기 실패 시 원복되며, 성공한 stop/cut callback은 중단된 pending Play accounting도 취소합니다. lifecycle callback이 pending이거나 실패·overflow가 발생하면 Disconnect나 조기 unload를 하지 않고 session을 abandon/quarantine하여 결과 불명확 상태를 보존합니다.

JSON 결과는 단계별 operation/result code와 connectRequestIssued, nullable comActivationAttempted, lastKtapConnectState, ktapConnectAttempted, nullable ktapConnectAccepted, nullable ktapHelloObserved, nullable networkMonitoringRecordExpected, networkMonitoringCheckRequired, nullable networkMonitoringVerified, outputMayBeActive, quarantine 시도·완료 여부를 제공합니다. connectRequestIssued는 엔진 API 요청일 뿐 KTAP 통신 증거가 아니며, comActivationAttempted도 COM 활성화 추정값일 뿐입니다. lastKtapConnectStatenot-attempted, attempted, accepted-unconfirmed, failed 중 하나입니다. networkMonitoringRecordExpected는 성공값을 받은 경우 true, dispatch가 없으면 false, local reflection/COM 실패 또는 timeout으로 서버 도달을 예측할 수 없으면 null입니다. networkMonitoringCheckRequired는 KTAP dispatch 경로에 들어간 모든 경우 true이며, networkMonitoringVerified는 앱이 Tornado2 UI를 판독하지 않으므로 항상 null입니다. 운영자가 직접 [R]/[S]를 확인해야 합니다. outputMayBeActive: true이면 자동 정리를 성공으로 확인하지 못했으므로 사람이 격리 출력을 확인해야 합니다. 특히 Unavailable, 취소, timeout은 연결 전 거부와 연결 도중 안전 게이트 변화가 같은 결과 code가 될 수 있으므로 추측하지 않습니다. 로컬 경로, 씬 code, PID, 창 제목, HRESULT 및 엔진 원문 오류는 출력하지 않습니다. --test-planruntimeProcessGateChecked: false는 자산 계획만 검증했다는 뜻이며 실제 연결 가능성을 증명하지 않습니다.

연결 timeout은 COM 활성화와 KTAP dispatch 사이의 원자적 게이트를 닫습니다. timeout이 먼저 게이트를 닫으면 늦게 끝난 STA 작업도 KTAPConnect에 진입할 수 없고 lastKtapConnectState: "not-attempted"로 남습니다. dispatch가 먼저 게이트를 획득한 경우에는 attempted 이상으로 보고하고 networkMonitoringCheckRequired: true로 남겨 운영자 확인을 요구합니다.

현재 장비처럼 PGM Tornado가 실행 중이거나 loopback 포트를 PGM이 소유한 상태에서는 --test-connect--test-sequence를 실행하지 않습니다. 기존 Test 엔진도 exactly-one Tornado2, non-PGM 제목 정규식, loopback literal, Test outputChannel, 외부 non-reparse scene root와 allowlist를 연결 전 및 각 명령 전에 재검사합니다.

CLI용 Test JSON은 위처럼 앱 기본 경로인 playout.local.json과 다른 파일명을 사용합니다. 기본 경로에 Test 설정을 두면 일반 앱 시작 시 자동 연결을 요청할 수 있으므로, 운영자가 의도적으로 앱 UI Test 모드를 검증하는 회차 외에는 사용하지 않습니다.

앱을 실행한 뒤 다음을 확인합니다.

  1. 시작 상태가 DRY RUN으로 보이고 앱이 COM 또는 Tornado 없이 종료되지 않습니다.
  2. WebView의 PREPARE, TAKE IN, NEXT, TAKE OUT이 native bridge를 통과해 명확한 결과를 표시합니다.
  3. 잘못된 설정 또는 엔진 부재가 앱 종료가 아니라 연결 상태와 안전한 오류 메시지로 표시됩니다.
  4. x64 MSIX를 설치해도 같은 dry-run 흐름이 동작합니다.

2026-07-10 최종 서명 Release x64 MSIX를 설치한 package context에서 실제 Oracle/MariaDB를 읽는 DryRun 검증을 완료했습니다. Web catalog 35개(34개 송출 가능), DB 상태 2/2 정상, alias 5001/N5001, fade 6, mutation preview와 asset 경로 비노출을 확인했습니다. 이어 5001 PREPARE → fresh TAKE IN → 최초 2초·이후 3초 timer refresh → 5074 playlist NEXT → 같은 entry/scene의 Page NEXT를 수행했고 Page NEXT 뒤 refresh가 정지했습니다. 5074는 5개 단위로 1/20부터 20/20까지 순서대로 진행되어 마지막에 isLastPage=YES, END OF PLAYLIST, NEXT 비활성이 되었으며, TAKE OUT 뒤 scene/refresh가 정리되고 playlist 편집 잠금이 해제되었습니다. 전 과정의 안전 배지는 DRY RUN · PROGRAM 차단이었고 COM/KTAP/Tornado2 연결은 발생하지 않았습니다.

WebView 상태 wire는 Disconnected, Connecting, Connected, Reconnecting, Faulted, OutcomeUnknown 등 native connection state를 별도로 전달합니다. Web catalog는 35개 builder와 도달 가능한 45개 alias, row별 enabled를 보존하고 PREPARE 성공 뒤 playlist snapshot을 freeze합니다. pending command, OutcomeUnknown과 timeout quarantine 중에도 snapshot 편집 잠금을 유지합니다. native status의 current entry/builder/page size/current rows/last-page와 bounded preview가 authoritative 값이며 asset path는 preview에서 숨깁니다. refresh active/next/last-success/fault도 전달하고 refresh fault는 TAKE OUT 외 mutation 명령을 막습니다. 성공한 TAKE OUT 뒤에는 전용 refresh error marker만 reset하며 다른 unknown latch를 지우지 않습니다. OutcomeUnknown과 timeout은 retryable: false이며 오류 창을 닫아도 native 잠금은 유지됩니다. 브라우저 응답 제한 시간은 검증된 native operation timeout에 5초 전달 여유를 더해 사용하며, 상관 응답이 오지 않으면 strict ParseTimeoutQuarantine 요청을 native에 보냅니다. MainWindow는 await 전에 process-lifetime latch를 세우고 vendor session을 quarantine하므로 WebView reload로도 해제되지 않습니다. 명령 진행 중 trusted navigation, reload 또는 WebView2 process failure로 JavaScript 상관관계가 사라지는 경우도 같은 native latch를 먼저 세웁니다. 늦은 응답이나 UI 재시도로 같은 명령을 다시 보내지 않으며 native 명령이 끝나면 authoritative status를 다시 게시합니다. NEXT는 원본의 m_TakeIn 조건처럼 on-air 장면이 있을 때만 native와 Web 양쪽에서 허용됩니다. Test on-air 배지는 실제 PROGRAM과 구분해 TEST ON AIR로 표시합니다.

On-air 표식이 남은 상태에서는 프로세스 감시, 연결 해제, 앱 종료 및 세션 재활용 경로도 SDK Disconnect를 호출하지 않습니다. 중앙 ReleaseSessionAsync 방어가 세션을 quarantine/abandon하고 결과를 불명확 상태로 승격하므로, 운영자는 먼저 성공한 TAKE OUT을 확인한 다음 정상 종료해야 합니다.

승인 컷의 과거 load/play 경로와 현재 WebView 기반 Scene/PageN 데이터 동등성은 서로 다른 검증 범위입니다. 복합 mutation·페이지 계산·fresh TAKE IN·Page NEXT·timer refresh는 구현 및 자동/실데이터 검증을 마쳤으며 원본 Tornado 송출 흐름 분석35개 Scene 매트릭스에 기록합니다. 첫 Live WebView 회차 MBNWEB-20260711-A는 CONNECT와 PREPARE까지 성공했지만 fresh TAKE IN의 Oracle 조회가 PLAY 전에 명확히 실패해 STOPAL/UNLOAD/BYE로 안전하게 종료했습니다. 회차 종료 뒤 Oracle/MariaDB 전체 스모크는 다시 통과했습니다. 남은 단계는 새 package와 회차 승인을 받아 실제 TAKE IN, timer refresh, playlist/Page NEXT, TAKE OUT을 Network Monitoring과 PGM에서 함께 통과하는 것입니다.

일반 앱과 정규 Test 경로의 실제 COM 스모크는 별도 테스트 인스턴스와 테스트 씬이 준비된 때에만 진행합니다. 위 안전 게이트를 독립적으로 재확인하고 modeTest로 바꾼 뒤 테스트 모니터에서 PREPARE → fresh TAKE IN → Page/playlist NEXT → timer refresh → TAKE OUT 결과를 관찰합니다. 의도하지 않은 PGM/운영 출력 변화가 보이면 추가 명령을 중단합니다. native 결과가 명확하고 Gate A에 포함된 경우에만 TAKE OUT을 한 번 요청하며, timeout·OutcomeUnknown·WEB_TIMEOUT·refresh fault라면 앱 종료나 반대 명령으로 자동 롤백하지 않고 session을 quarantine한 채 운영자가 실제 출력 상태를 먼저 확인합니다.

패키지 스모크에서는 벤더 x64 COM이 장비에 정식 등록되어 있어야 합니다. MSIX에 벤더 DLL을 복사해 활성화 오류를 우회하지 않습니다. 패키지 컨텍스트에서 COM 활성화가 막히면 DryRun 또는 Disabled를 유지하고 HRESULT와 등록 검사 결과만 보고합니다.

장애 및 롤백

가장 빠른 롤백은 로컬 설정을 다음처럼 바꾸고 앱을 재시작하는 것입니다.

{
  "mode": "Disabled"
}

그 다음 회차별 라이브 환경 변수를 제거하고 테스트/운영 라우팅 상태를 사람이 확인합니다. 필요하면 조직 배포 절차로 직전 서명 MSIX로 되돌립니다. COM 등록 해제, 벤더 설치 폴더 삭제 또는 DLL 교체는 앱 롤백 절차가 아니므로 수행하지 않습니다. 진단용 artifacts\K3DInterop을 삭제해도 런타임에는 영향이 없습니다.

연결 장애나 Tornado 재시작은 화면의 Disconnected, Reconnecting, Faulted 또는 OutcomeUnknown 상태로 판단합니다. 재연결 횟수를 모두 소진하거나 결과가 불명확한 송출 명령이 있으면 자동 재송출하지 않고 Disabled로 전환한 뒤 운영자가 테스트 출력 상태를 확인합니다.

저장소 반입 금지

다음 항목은 Git과 MSIX에 넣지 않습니다.

  • 벤더 COM DLL 및 생성한 Interop DLL
  • Tornado/K3D 라이선스 파일, 키 또는 인증서
  • 실제 .t2s 씬, 이미지, 영상 및 방송 자산
  • 운영 호스트, 채널, 창 제목 및 로컬 허용 목록이 든 설정
  • 실제 출력 캡처나 비밀정보가 포함된 진단 로그

벤더 바이너리와 자산은 승인된 설치·배포 위치에서 관리하고, 앱 저장소에는 COM 중립 인터페이스와 안전한 설정 예시만 유지합니다.