feat: migrate legacy playout workflow and scenes
This commit is contained in:
@@ -1,57 +1,89 @@
|
||||
# 원본 Tornado 송출 흐름 분석
|
||||
|
||||
이 문서는 `MBN_STOCK_N`의 `MainForm`, `Scene` 35개 및 `PageN`/`Nxt_PageN`을 새 송출 어댑터와 대조한 기준선입니다. 원본 파일은 읽기만 했으며 새 저장소로 복사하지 않았습니다.
|
||||
이 문서는 `C:\Users\MD\source\repos\MBN_STOCK_N`의 `MainForm`, `Scene` 35개와 `PageN`/`Nxt_PageN`을 새 송출 runtime과 대조한 기준선이다. 원본은 읽기 전용으로만 조사했으며 소스, DB 비밀번호, 운영 설정과 실제 자산을 새 저장소로 복사하지 않았다.
|
||||
|
||||
## MainForm 호출 순서
|
||||
|
||||
원본 연결은 UI STA에서 `KTAPConnect(1, "127.0.0.1", 30001, 0, event)`를 호출한 뒤 `GetScenePlayer()`를 얻습니다. 연결 성공 판정은 재연결 코드와 동일하게 반환값 `1`입니다.
|
||||
원본 연결은 UI STA에서 `KTAPConnect(1, "127.0.0.1", 30001, 0, event)`를 호출한 뒤 `GetScenePlayer()`를 얻는다. 원본의 `30001`은 당시 운영값일 뿐 새 Test endpoint의 기본값이 아니다. 새 회차에서는 Tornado2 `Tools > Option > Control > Network Server > TCP Port`의 실값을 사용하고, 반환값 `1`뿐 아니라 `OnHello`와 Network Monitoring `[R]`/`[S]`를 함께 확인해야 한다.
|
||||
|
||||
여기의 `30001`은 원본 시스템의 당시 값일 뿐 새 Test endpoint의 기본값이나 검증값이 아닙니다. 새 설정은 격리 Test Tornado의 현재 `Tools > Option > Control > Network Server > TCP Port`를 직접 확인해 사용합니다. 반환값 `1`도 매뉴얼의 `OnHello` 또는 Network Monitoring `[R]`/`[S]` 확인을 대신하지 않습니다.
|
||||
|
||||
원본 장면 PREPARE의 호출 순서는 다음과 같습니다.
|
||||
원본 PREPARE의 순서는 다음과 같다.
|
||||
|
||||
1. `LoadScene(Cuts\<file>.t2s, <scene alias>)`
|
||||
2. IN effect 플래그에 fade effect `7`과 `FadeInSec` 적용
|
||||
2. IN effect flag `1`에 fade effect `7`과 `FadeInSec` 적용
|
||||
3. `BeginTransaction()`
|
||||
4. 장면별 데이터와 오브젝트 변경
|
||||
4. 장면별 object 값과 시각 속성 변경
|
||||
5. `scene.QueryVariables()`
|
||||
6. `EndTransaction()`
|
||||
7. `player.Prepare(10, scene)`
|
||||
|
||||
TAKE IN은 준비된 장면에 `Play(10)`을 호출하고 `m_TakeIn=true`로 전환합니다. TAKE OUT은 과거 `CutOut(10)` 대신 현재 운영 코드와 동일하게 `StopAll()`을 사용하고 on-air 상태를 해제합니다. NEXT는 `m_TakeIn`이 참일 때만 실행되므로 IDLE 또는 PREPARED 상태에서 바로 출력을 시작해서는 안 됩니다.
|
||||
새 `DynamicK3dSession`도 같은 순서를 사용한다. output channel이 명시된 경우에만 `EndTransactionOnChannel`을 사용하며 layout `10`은 `Prepare`/`Play`/`CutOut`에만 전달한다. transaction 중 실패하면 `RollbackTransaction`을 시도하고 원래 실패를 보존한다.
|
||||
|
||||
새 `DynamicK3dSession`은 매뉴얼의 transaction 제한에 맞춰 오브젝트 변경을 `BeginTransaction`/`EndTransaction[OnChannel]` 안에서 끝낸 뒤 `QueryVariables`를 호출하고 `Prepare`합니다. `SetSceneEffectType`의 첫 인수도 layout이 아니라 IN effect 플래그 `1`로 전달하며, layout `10`은 `Prepare`/`Play`/`CutOut`에만 사용합니다. `TornadoPlayoutEngine`은 on-air 상태가 없으면 NEXT를 COM 호출 전에 거부합니다.
|
||||
공통 scene fade/background는 Web 입력이 아니라 로컬 trusted 설정이다. 원본 `ComboDi.SelectedIndex`에 맞춘 fade 기본값은 6이다. background kind와 scene root 아래 상대 asset, video loop를 `PlayoutSceneCompositionFactory`가 검사하며 `DryRun`도 실제 COM 전에 파일 존재, 확장자, root 탈출과 reparse point를 fail-closed 검증한다. 실제 asset 경로는 Web preview나 wire status에 노출하지 않는다.
|
||||
|
||||
K3D의 Play/Stop 계열은 완료 이벤트가 별도인 비동기 명령입니다. 현재 callback handler를 아직 포팅하지 않았으므로 이전/on-air Scene을 명령 반환 직후 `Unload`하지 않고 연결 종료까지 보존합니다. 실제 운영의 장기 세션 정리는 `OnScenePlayed`/`OnCutOut`/`OnStopAll` 성공 콜백 기반으로 구현해야 합니다.
|
||||
## PREPARE, TAKE IN, NEXT, TAKE OUT 상태 전이
|
||||
|
||||
## Scene 빌더의 범위
|
||||
- PREPARE는 선택 위치부터 다음 활성 row를 찾아 page 0의 실제 데이터를 조회하고 scene을 load/transaction/prepare한다. 성공한 전체 playlist와 선택 index는 immutable native snapshot으로 고정된다.
|
||||
- PREPARE/PROGRAM이 이미 활성인 상태에서 PREPARE를 다시 누르면 원본 toggle과 같이 `TakeOut(All)`/`StopAll`로 정리한다.
|
||||
- TAKE IN은 PREPARE 때의 DTO를 그대로 play하지 않는다. 원본이 `m_Super`를 초기화하고 `ONAirMode`를 다시 호출하는 것처럼 같은 frozen entry/page를 DB에서 새로 조회하고 `LoadScene` → transaction → `Prepare(10)`한 뒤 성공한 경우에만 `Play(10)`한다.
|
||||
- NEXT는 on-air 상태에서만 허용한다. 다음 page가 있으면 Page NEXT, 마지막 page면 다음 활성 playlist entry의 page 0으로 구분한다. 끝에서 wrap하지 않는다.
|
||||
- TAKE OUT은 현재 원본 운영 경로와 같이 `StopAll()`을 사용하고 on-air 상태를 해제한다.
|
||||
|
||||
원본 `Scene` 폴더에는 35개 빌더가 있습니다. 단순 텍스트와 가시성 외에도 색상, 위치, 크기, crop key, path point, 그래프 데이터, 배경 texture/video 등 장면별 K3D 변형을 수행합니다. 이 로직은 데이터 조회와 WinForms 컨트롤에 강하게 결합되어 있어 단순한 Web 제목/설명 문자열로 대체할 수 없습니다.
|
||||
timeout, dispatch 뒤 cancellation 또는 결과가 불명확한 COM 실패는 `OutcomeUnknown` latch로 남긴다. 취소나 retry 가능한 실패로 낮추거나 같은 명령을 다시 보내지 않는다.
|
||||
|
||||
현재 어댑터의 `PlayoutField`는 COM 경계를 검증하는 공통 `SetValue`/`SetVisible`만 표현합니다. Web bridge는 presentation용 `title`/`detail`을 장면 데이터인 것처럼 버리거나 추측하지 않고, PREPARE/NEXT에 검증된 scene code만 보냅니다. 따라서 승인된 `5001.t2s`/`5006.t2s` 연결 시험은 파일 load·prepare·play·stop 경로를 검증하지만 원본 시장 데이터가 채워진 방송 화면의 동등성을 증명하지 않습니다.
|
||||
## PageN, Page NEXT와 timer refresh
|
||||
|
||||
장면 데이터를 포팅할 때는 scene code별 builder가 Core의 조회 결과를 명시적인 mutation DTO로 변환하고, 허용된 K3D 메서드만 어댑터가 실행하도록 확장해야 합니다. 오브젝트 이름이나 메서드를 Web 입력에서 임의로 전달하는 범용 reflection API는 만들지 않습니다.
|
||||
`PageN`과 `Nxt_PageN`은 조회 행 수를 5·6·12개 단위로 나눠 최대 20페이지의 `m_pcnt`를 계산한다. 새 구현의 대상은 `s5074`(5), `s5077`(6), `s5088`(12)이며 `pageCount = min(20, ceil(itemCount/pageSize))`를 사용한다.
|
||||
|
||||
## PageN과 NEXT
|
||||
원본에는 같은 scene을 바꾸는 두 경로가 있다. 서로 혼동하면 안 된다.
|
||||
|
||||
`PageN`과 `Nxt_PageN`은 조회 행 수를 5·6·12개 단위로 나눠 최대 20페이지의 `m_pcnt`를 계산합니다. `MainForm.Next_Scene`은 5단/6종목/12종목 장면에서 다음 플레이리스트 항목으로 즉시 이동하지 않고 다음 페이지 데이터를 같은 scene에 다시 채웁니다. 경로에 따라 새 scene을 load하거나 `GetPlayingScene(10)`을 얻어 transaction 후 다시 prepare/play합니다.
|
||||
1. Operator Page NEXT는 `btnNext_Click` → `Next_Scene(0)` 경로다. 다음 page의 fresh 데이터를 조회하고 새 scene을 `LoadScene` → transaction → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)`한다. playlist index는 유지하지만 `GetPlayingScene` in-place 갱신은 아니다.
|
||||
2. Timer refresh는 `timer1_Tick` → `Show_PlayList(idx: 1)` 경로다. current entry/page의 fresh DB DTO를 사용하고 K3D 호출은 `Play(10)` → `GetPlayingScene(10)` → transaction → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)` 순서로 현재 scene을 갱신한다. scene-level background와 transition effect는 다시 적용하지 않는다.
|
||||
|
||||
현재 Web NEXT는 on-air 상태에서 다음 플레이리스트 cue를 prepare/play하는 어댑터 수준의 동작입니다. `m_pcnt`, 현재 페이지, 같은 scene의 in-place update 및 `GetPlayingScene` 기반 갱신은 아직 장면 builder 계층이 없으므로 구현 범위에 포함되지 않습니다. 운영 동등성 검증에서는 이 항목을 별도 완료 조건으로 추적해야 하며, 현재 Test 시퀀스 성공을 PageN 포팅 완료로 해석하지 않습니다.
|
||||
operator command를 시작할 때 timer를 먼저 멈춘다. TAKE IN과 playlist NEXT 성공 뒤 해당 cut의 원본 `m_time`으로 첫 refresh를 예약하고, 첫 성공 이후에는 3초 간격으로 반복한다. Page NEXT 뒤에는 원본처럼 timer를 다시 시작하지 않는다. refresh 실패, timeout 또는 `OutcomeUnknown`이면 fault latch를 세우고 자동 반복을 중단하며 TAKE OUT 외 mutation 명령을 막는다.
|
||||
|
||||
## 현재 Test 판정 범위
|
||||
마지막 부분 page는 남은 row만 채우고 나머지 object를 clear/hide한다. `s5088` NXT 비교와 조회 index는 모두 `i + pageIndex * 12`를 사용한다. page 경계값과 partial-page clearing은 자동 테스트로 검증했다.
|
||||
|
||||
격리 Test에서 확인할 수 있는 범위는 다음과 같습니다.
|
||||
## 35개 Scene builder와 실제 데이터
|
||||
|
||||
- x64 COM 활성화와 KTAP 연결/해제
|
||||
- 승인된 `.t2s`의 load와 scene alias
|
||||
- 오브젝트 transaction 종료 뒤 `QueryVariables`, layout 10 prepare
|
||||
- TAKE IN, 다음 cue의 NEXT, TAKE OUT `StopAll`
|
||||
- STA 직렬화, timeout, 프로세스 교체 및 오류 상태
|
||||
원본 `Scene` 폴더의 35개 builder는 모두 typed DTO와 COM-neutral mutation builder로 포팅했다. 값, visibility, face color, position, position key, scale, crop key, circle angle, path/path-shape, image/texture/video와 scene background를 개별 mutation으로 표현하고 `IPlayoutEngine` 뒤에서만 K3D 호출로 변환한다.
|
||||
|
||||
다음 항목은 별도 장면 마이그레이션 작업이 필요합니다.
|
||||
- registry/catalog: 35개 builder 1:1
|
||||
- MainForm 도달 runtime: 34개 builder, active cut alias 45개
|
||||
- `s5032`/`s8018`: `5032`, `8018`, `8032` shared alias를 closed selection으로 분기
|
||||
- `s8086`: 원본 MainForm dispatch가 없어 active alias와 앱 runtime route 없이 diagnostic으로 유지
|
||||
- 실제 데이터 smoke: 33개 Oracle/MariaDB loader와 `s5025` trusted 외부 CP949 파일 통과
|
||||
- `s8086` diagnostic 조회 통과, 전체 Oracle/MariaDB query 55건 통과
|
||||
|
||||
- 35개 scene builder의 데이터/시각 속성 동등성
|
||||
- `PageN`/`Nxt_PageN` 페이지 계산과 같은 scene 갱신
|
||||
- 배경 영상·texture 및 그래프/path mutation
|
||||
- 실제 시장 데이터와 원본 화면의 픽셀/내용 비교
|
||||
- `OnScenePlayed`/`OnCutOut`/`OnStopAll` callback 기반 Scene unload
|
||||
builder별 object/mutation과 검증 상태는 [`SCENE_EQUIVALENCE.md`](SCENE_EQUIVALENCE.md)에 있다. 실제 `.t2s`, DB 계정과 외부 CP949 파일은 Git에 넣지 않는다.
|
||||
|
||||
## WebView 상태와 안전 경계
|
||||
|
||||
Web catalog는 35개 builder와 도달 가능한 45개 alias를 제공하고 playlist row별 `enabled` flag를 보존한다. PREPARE가 성공하면 native snapshot을 freeze하며 pending command, `OutcomeUnknown`과 timeout quarantine 중에도 편집 잠금을 유지하므로 이후 Web 편집으로 NEXT 대상을 바꿀 수 없다.
|
||||
|
||||
native status는 현재 entry, builder, page size/index/count, current row 수, last-page, next kind와 bounded typed preview를 authoritative 값으로 보낸다. preview는 object 값과 상태를 보여주되 asset path는 숨긴다. refresh active/next/last-success/fault도 표시한다. 성공한 TAKE OUT으로 native refresh state가 reset되면 전용 refresh error marker만 제거하고 다른 unknown/quarantine latch는 유지한다.
|
||||
|
||||
Web 응답 제한 시간이 지나면 strict `ParseTimeoutQuarantine` 요청을 native에 보내고 MainWindow가 process-lifetime correlation latch를 먼저 세운 뒤 vendor session을 quarantine한다. 명령 진행 중 trusted navigation, reload 또는 WebView2 process failure도 JavaScript 상관관계를 잃기 전에 같은 latch를 세우므로 WebView reload로 해제되지 않는다. pending request와 맞지 않는 늦은 응답은 state 전이 근거로 쓰지 않으며 같은 명령을 다시 보내지 않는다. `OutcomeUnknown`, native fault와 Web timeout은 UI를 닫는 것으로 해제되지 않는다.
|
||||
|
||||
## Callback과 scene 수명
|
||||
|
||||
vendor event handler의 `OnScenePlayed`, `OnCutOut`, `OnStopAll`은 managed callback queue로 연결돼 있다.
|
||||
|
||||
- `OnScenePlayed` 성공 뒤에만 이전 retired scene을 unload/release한다.
|
||||
- pending Play callback이 있으면 TAKE OUT을 제외한 PREPARE/TAKE IN/NEXT/timer refresh를 fail-closed 차단한다.
|
||||
- `CutOut`/`StopAll` dispatch 전에 completion counter를 올리고 동기 호출 실패 시 원복한다. 성공 callback은 대응 counter를 하나 줄이고, stop/cut으로 중단된 Play는 별도 `OnScenePlayed`가 없을 수 있으므로 pending Play accounting을 취소한다.
|
||||
- pending lifecycle callback, queue overflow, callback failure 또는 connection generation 불일치가 있으면 Disconnect와 조기 unload를 하지 않고 session을 abandon/quarantine한다.
|
||||
- 이전 generation의 늦은 callback은 현재 scene state를 변경하지 않는다.
|
||||
|
||||
따라서 장기 실행 시에도 callback으로 안전성이 확인된 retired scene만 unload된다.
|
||||
|
||||
## 검증 판정 범위
|
||||
|
||||
다음 자동·통합 검증은 완료됐다.
|
||||
|
||||
- 35개 builder, loader, resolver, runtime coverage와 PageN 경계
|
||||
- 실제 Oracle/MariaDB 및 trusted CP949 source → DTO → mutation preflight
|
||||
- Debug/Release x64 Core, Playout, Infrastructure suite와 Web safety suite
|
||||
- Visual Studio 2026 Debug/Release x64 빌드
|
||||
- trusted Release x64 MSIX 생성, 설치와 package context 실행
|
||||
|
||||
하지만 이번 마이그레이션 WebView workflow로 실제 Tornado2 PGM에 PREPARE/TAKE IN/Page NEXT/playlist NEXT/timer refresh/TAKE OUT을 보내고 Network Monitoring과 화면을 함께 확인하는 회차는 아직 승인되지 않았고 실행하지 않았다. 과거 고정 `5001 → 5006` runner 증거는 이 동등성 검증을 대신하지 않는다. 실제 운영 검증은 [`PLAYOUT_OPERATIONS.md`](PLAYOUT_OPERATIONS.md)의 회차 승인과 반복 금지 절차를 따른다.
|
||||
|
||||
@@ -53,23 +53,21 @@ WebView는 `https://app.mbn.local` 가상 호스트로 패키지 내부 파일
|
||||
- 완료: `IPlayoutEngine` 경계, bounded STA FIFO/message pump, timeout 후 `OutcomeUnknown` 격리
|
||||
- 완료: 연결/해제, 명시적 재연결(no replay), `Tornado2` 접두사 프로세스 감시
|
||||
- 완료: `PREPARE`, `TAKE IN`, `NEXT`, `TAKE OUT` WebView 메시지 및 상태/오류 UI 연결
|
||||
- 완료: 기본 DryRun, Test의 단일 loopback 테스트 인스턴스·채널·씬 allowlist, Live 이중 승인
|
||||
- 완료: 기본 DryRun, Test의 단일 loopback 테스트 인스턴스·채널, Test/Live 공통 폐쇄형 씬 allowlist, Live 이중 승인
|
||||
- 완료: 현재 Tornado2 PGM 렌더 창에 대한 x64 K3D `KTAPConnect → Disconnect` 실제 왕복 및 Network Monitoring `[R] HELLO`/`[S] SUCCESS HELLO` 확인. 렌더 명령은 호출하지 않음
|
||||
- 완료: 네이티브/Interop 이중 SHA-256 핀, 프로세스 수명 파일 잠금, 실제 PGM listener 소유권 및 KTAP 지연 dispatch 차단
|
||||
- 완료: 승인된 실제 PGM에서 고정 해시의 `5001 → 5006 → TAKE OUT` 호출. `Connect → Prepare/Play(5001) → Prepare/Play(5006) → StopAll → Disconnect` 전 단계 성공, 세 관찰 구간 `5051/5052/5093ms`, PGM의 5001·5006 화면과 최종 검은 화면 확인
|
||||
- 완료: 같은 회차 Network Monitoring의 `HELLO/LOAD_SCENE/SCENE_PREPARE/PLAY/STOPAL/BYE` 기록과 39개 연속 캡처를 manifest SHA-256으로 검증. 로컬 증거와 재현 절차는 [Tornado/K3D 운영 가이드](PLAYOUT.md)에 기록
|
||||
- 완료: 첫 회차의 출력 전 Prepare 거부 원인이 cue 이중 resolve임을 확인하고, 상대 cue와 승인 검사용 절대 자산 경로를 분리하는 수정 및 회귀 테스트 적용. 결과 불명확 시 재시도 금지 원칙 유지
|
||||
- 목표 완료 판정: 최초 완료 조건의 격리 Test 인스턴스 검증은 이후 운영자의 현재 PGM 대상 회차별 명시 승인으로 대체되었습니다. 따라서 위 고정 테스트 컷 `5001`/`5006`의 실제 SDK 호출과 화면·Network Monitoring 증거를 이번 목표의 실제 호출 검증으로 인정합니다. 이 예외는 현재 PGM을 일반 Test 인스턴스로 분류하거나 앱의 Test/Live 안전 게이트를 완화하지 않습니다.
|
||||
- 배포 전 후속: 별도 승인된 Test 환경에서 MSIX WebView 컨텍스트의 실제 COM 활성화와 네 버튼 출력 관찰
|
||||
- 후속: `OnScenePlayed`/`OnCutOut`/`OnStopAll` callback 기반 장기 세션 Scene unload
|
||||
- 후속: 35개 scene builder의 복합 K3D mutation 및 `PageN`/`Nxt_PageN` 같은-scene 페이지 갱신 포팅
|
||||
- 과거 기본 경로 증거: 승인된 PGM 고정 runner에서 `5001 → 5006 → TAKE OUT`과 Network Monitoring `HELLO/LOAD_SCENE/SCENE_PREPARE/PLAY/STOPAL/BYE`, 39개 연속 캡처를 검증했다. 이는 현재 WebView의 fresh TAKE IN, Page NEXT, timer refresh 또는 35개 builder 동등성 완료 증거가 아니다.
|
||||
- 완료: 첫 과거 회차의 출력 전 Prepare 거부 원인이 cue 이중 resolve임을 확인하고, 상대 cue와 승인 검사용 절대 자산 경로를 분리하는 수정 및 회귀 테스트 적용. 결과 불명확 시 재시도 금지 원칙 유지
|
||||
- 현재 목표 미완료: 설치된 MSIX WebView workflow의 실제 Tornado2 `PREPARE → fresh TAKE IN → playlist/Page NEXT → timer refresh → TAKE OUT`, Network Monitoring과 PGM 동시 검증은 새 회차 승인 전이며 아직 실행하지 않았다.
|
||||
- 완료: `OnScenePlayed`/`OnCutOut`/`OnStopAll` callback 기반 장기 세션 Scene unload와 pending callback fail-closed accounting
|
||||
- 완료: 35개 scene builder의 복합 K3D mutation 및 `PageN`/`Nxt_PageN` 5·6·12개/최대 20페이지 포팅과 자동·실제 DB 검증
|
||||
|
||||
### 화면 기능
|
||||
|
||||
- Oracle 플레이리스트 영구 저장/불러오기
|
||||
- 10개 업무 탭의 실제 데이터 바인딩
|
||||
- 테마, 전문가, VI, 비교, 수동 그래프 편집기
|
||||
- 35개 장면 빌더의 단계별 포팅
|
||||
- 35개 장면 빌더의 실제 Tornado2/PGM 화면 동등성 회차 검증
|
||||
- 실제 Preview 이미지 및 씬/영상 자산 연결
|
||||
|
||||
## 의도적으로 제외한 항목
|
||||
|
||||
@@ -93,8 +93,12 @@ SDK가 기본 위치에 없다면 x64 SDK의 `TlbImp.exe` 절대 경로를 `-Tlb
|
||||
| `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` | `Test`에서 허용한 테스트 scene name(code) 목록. 경로나 확장자는 넣지 않음 |
|
||||
| `testSceneAllowlist` | 실제 COM을 사용하는 `Test`와 `Live` 모두에서 허용한 scene name(code)의 폐쇄형 목록. 경로나 확장자는 넣지 않음(기존 설정 호환을 위해 이름 유지) |
|
||||
| `trustedLiveOutputEnabled` | 운영자가 로컬 파일에서만 설정하는 라이브 1차 게이트 |
|
||||
| `queueCapacity` | 직렬 STA 명령 큐의 최대 대기 항목 수 |
|
||||
| `*TimeoutMilliseconds` | 연결, 작업 및 해제 제한 시간 |
|
||||
@@ -112,6 +116,11 @@ 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
|
||||
@@ -123,7 +132,7 @@ MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS
|
||||
MBN_STOCK_PLAYOUT_RECONNECT_ENABLED
|
||||
```
|
||||
|
||||
`testSceneAllowlist`와 `trustedLiveOutputEnabled`는 환경 변수로 변경할 수 없으며 로컬 설정 파일에서만 관리합니다. `SceneDirectory`는 Test/Live에서 존재하는 비-reparse 외부 디렉터리여야 하며, 엔진은 상대 `.t2s` 파일을 정규화해 이 루트 밖으로 나가는 경로를 거부합니다. scene file의 basename과 scene name 및 Test allowlist 항목도 서로 일치해야 합니다. 설정 파일은 실행 계정만 읽을 수 있도록 ACL을 제한합니다. 라이선스 키나 인증정보를 이 파일에 기록하지 않습니다.
|
||||
`testSceneAllowlist`와 `trustedLiveOutputEnabled`는 환경 변수로 변경할 수 없으며 로컬 설정 파일에서만 관리합니다. 이름은 기존 설정 호환을 위해 유지하지만 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 판정
|
||||
|
||||
@@ -131,7 +140,7 @@ K3DAsyncEngine 매뉴얼의 `KTAPConnect(bTCP, HostAddress, nHostPort, nClientPo
|
||||
|
||||
Tornado2의 `View > Network Monitoring Window`에서 `[R]`은 서버가 클라이언트 요청을 받은 기록, `[S]`는 서버가 응답을 보낸 기록이며 `TCPSession`은 TCP 세션 수입니다(매뉴얼 26~27쪽). 프로세스 감지, COM 등록 probe 또는 COM 객체 생성만으로는 이 기록이 생기지 않습니다. 기본 앱과 `--dry-run`, `--probe`, `--test-plan`은 KTAP를 호출하지 않으므로 빈 모니터가 정상입니다.
|
||||
|
||||
상태의 `accepted-unconfirmed`는 `KTAPConnect`가 SDK 성공값 `1`을 반환했다는 뜻일 뿐입니다. 매뉴얼 41쪽의 `OnHello` 콜백이나 실제 `[R]`/`[S]`를 자동 확인했다는 뜻이 아닙니다. 현재 late-bound 어댑터는 282개 메서드 `IKAEventHandler` ABI를 안전하게 패키징하는 검증된 전략이 없어 `ktapHelloObserved`를 `null`로 보고합니다. `lastKtapConnectState`는 현재 연결 상태가 아니라 가장 최근 KTAP dispatch 시도의 증거이며, 화면은 `Connected`/`Faulted` 같은 현재 상태와 분리해 표시합니다. 따라서 격리 `--test-connect`가 성공했는데도 같은 시각의 `[R]`/`[S]`가 전혀 없다면 `--test-sequence`로 진행하지 말고 mode/config 파일, 실제 Network Server TCP Port와 안전 게이트 거부 여부를 먼저 확인합니다.
|
||||
상태의 `accepted-unconfirmed`는 `KTAPConnect`가 SDK 성공값 `1`을 반환했다는 뜻일 뿐입니다. 매뉴얼 41쪽의 `OnHello` 콜백이나 실제 `[R]`/`[S]`를 자동 확인했다는 뜻이 아닙니다. connect-only `--pgm-connect-diagnostic`은 렌더 API 표면을 제거한 별도 binding이라 callback을 관찰하지 않고 `ktapHelloObserved`를 `null`로 보고합니다. 일반 `IPlayoutEngine` 경로는 검증된 282-method `DynamicK3dEventHandler`로 `OnHello`와 lifecycle callback을 수신하지만, callback 미수신 상태를 성공으로 추정하지 않으며 Network Monitoring은 계속 사람이 대조합니다. `lastKtapConnectState`는 현재 연결 상태가 아니라 가장 최근 KTAP dispatch 시도의 증거이며, 화면은 `Connected`/`Faulted` 같은 현재 상태와 분리해 표시합니다. 따라서 격리 `--test-connect`가 성공했는데도 같은 시각의 `[R]`/`[S]`가 전혀 없다면 `--test-sequence`로 진행하지 말고 mode/config 파일, 실제 Network Server TCP Port와 안전 게이트 거부 여부를 먼저 확인합니다.
|
||||
|
||||
### PGM 네트워크 연결 전용 진단
|
||||
|
||||
@@ -206,7 +215,7 @@ powershell -NoProfile -ExecutionPolicy Bypass `
|
||||
|
||||
첫 회차는 `Connect: Success` 뒤 출력 전 `prepare-first: Rejected`로 중단되고 안전한 `Disconnect: Success`만 수행했습니다. 원인은 사전 검사에서 상대 cue를 절대 경로로 한 번 resolve한 뒤 그 이미 resolve된 cue를 `TornadoPlayoutEngine`에 전달해 엔진이 두 번째 resolve에서 거부한 것이었습니다. 수정 후 절대 경로는 승인 자산 검사와 파일 lease에만 사용하고, 엔진에는 고정 상대 cue인 `5001.t2s`와 `5006.t2s`를 전달합니다. 상대 cue와 검증용 절대 자산 경로가 분리되는 회귀 테스트도 추가했습니다. 이 회차는 `Play` 전에 결과가 명확히 거부되고 같은 대상의 Disconnect 성공까지 확인됐기 때문에 원인 수정 후 새 회차를 진행할 수 있었습니다. timeout, `OutcomeUnknown`, 대상 교체 또는 출력 결과가 불명확한 경우에는 자동·수동으로 반복하지 않고 quarantine 뒤 PGM 상태를 사람이 먼저 확인합니다.
|
||||
|
||||
PGM 전용 시퀀스의 `outputChannel`은 의도적으로 비워 원본 `MainForm` 연결과 같은 `GetScenePlayer()`를 사용합니다. 검증되지 않은 임의 채널을 추정해 `GetScenePlayerOnChannel()`을 호출하지 않기 위함입니다. TAKE OUT은 원본의 현재 운영 경로와 같이 `TakeOut(All)`을 `StopAll()`로 매핑합니다. 과거 `CutOut(10)`보다 현재 재생기의 모든 레이어를 정리해 최종 PGM이 검은 화면으로 돌아오는 동작과 일치합니다. 이 검증은 두 승인 컷의 load/prepare/play/stop 경로를 증명하지만, 아직 포팅되지 않은 35개 scene builder와 `PageN` 데이터 표현의 동등성을 증명하지는 않습니다.
|
||||
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와 실제 화면 동등성을 검증한 회차가 아니므로 완료 증거로 사용하지 않습니다.
|
||||
|
||||
## 모드와 안전 게이트
|
||||
|
||||
@@ -313,7 +322,7 @@ dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
|
||||
|
||||
시퀀스는 `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를 managed callback으로 아직 소비하지 않습니다. 따라서 `Play`, `CutOut`, `StopAll`의 COM 반환 직후 Scene을 `Unload`하지 않고 prepared/current/retired 참조를 연결 종료까지 보존합니다. 이는 두 장면만 쓰는 bounded 검증에서 조기 Unload로 화면 전환을 끊는 위험을 피하기 위한 동작입니다. 장시간 운영에서 Scene을 누적하지 않으려면 `OnScenePlayed`/`OnCutOut`/`OnStopAll` 성공 콜백 뒤에만 unload queue를 비우는 handler 포팅이 선행되어야 합니다.
|
||||
현재 런타임은 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 활성화 추정값일 뿐입니다. `lastKtapConnectState`는 `not-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-plan`의 `runtimeProcessGateChecked: false`는 자산 계획만 검증했다는 뜻이며 실제 연결 가능성을 증명하지 않습니다.
|
||||
|
||||
@@ -330,13 +339,15 @@ CLI용 Test JSON은 위처럼 앱 기본 경로인 `playout.local.json`과 다
|
||||
3. 잘못된 설정 또는 엔진 부재가 앱 종료가 아니라 연결 상태와 안전한 오류 메시지로 표시됩니다.
|
||||
4. x64 MSIX를 설치해도 같은 dry-run 흐름이 동작합니다.
|
||||
|
||||
WebView 상태 wire는 `Disconnected`, `Connecting`, `Connected`, `Reconnecting`, `Faulted`, `OutcomeUnknown` 등 native connection state를 별도로 전달합니다. `OutcomeUnknown`과 timeout은 `retryable: false`이며 오류 창을 닫아도 native 잠금은 앱 재시작 전까지 유지됩니다. 브라우저 응답 제한 시간은 고정 15초가 아니라 검증된 native operation timeout에 5초 전달 여유를 더해 사용하고, native 명령이 끝나면 상관 응답을 놓친 경우에도 authoritative status를 다시 게시합니다. NEXT는 원본의 `m_TakeIn` 조건처럼 on-air 장면이 있을 때만 native와 Web 양쪽에서 허용됩니다. Test on-air 배지는 실제 PROGRAM과 구분해 `TEST ON AIR`로 표시합니다.
|
||||
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 경로와 원본 Scene/PageN 데이터 표현력은 서로 다른 검증 범위입니다. 현재 지원 범위와 아직 포팅되지 않은 복합 mutation·페이지 갱신은 [원본 Tornado 송출 흐름 분석](LEGACY_PLAYOUT_ANALYSIS.md)을 기준으로 판단합니다.
|
||||
승인 컷의 과거 load/play 경로와 현재 WebView 기반 Scene/PageN 데이터 동등성은 서로 다른 검증 범위입니다. 복합 mutation·페이지 계산·fresh TAKE IN·Page NEXT·timer refresh는 구현 및 자동/실데이터 검증을 마쳤으며 [원본 Tornado 송출 흐름 분석](LEGACY_PLAYOUT_ANALYSIS.md)과 [35개 Scene 매트릭스](SCENE_EQUIVALENCE.md)에 기록합니다. 남은 단계는 회차 승인을 받은 실제 Tornado2 Network Monitoring/PGM 검증입니다.
|
||||
|
||||
일반 앱과 정규 Test 경로의 향후 실제 COM 스모크는 별도 테스트 인스턴스와 테스트 씬이 준비된 때에만 진행합니다. 위 안전 게이트를 독립적으로 재확인하고 `mode`를 `Test`로 바꾼 뒤 테스트 모니터에서 `PREPARE → TAKE IN → NEXT → TAKE OUT` 결과를 관찰합니다. PGM/운영 출력에 변화가 보이면 즉시 앱을 종료하고 롤백합니다. 명령 timeout 뒤 결과가 불명확하면 명령을 자동 또는 수동으로 반복하지 말고 테스트 출력 상태를 먼저 확인합니다.
|
||||
일반 앱과 정규 Test 경로의 실제 COM 스모크는 별도 테스트 인스턴스와 테스트 씬이 준비된 때에만 진행합니다. 위 안전 게이트를 독립적으로 재확인하고 `mode`를 `Test`로 바꾼 뒤 테스트 모니터에서 `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와 등록 검사 결과만 보고합니다.
|
||||
|
||||
|
||||
261
docs/PLAYOUT_OPERATIONS.md
Normal file
261
docs/PLAYOUT_OPERATIONS.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# MBN_STOCK_WEBVIEW 송출 운영·검증 절차
|
||||
|
||||
이 문서는 마이그레이션된 WebView → `IPlayoutEngine` → K3D/Tornado2 경로를 검증할 때의 승인, 실행, 감시, 장애 복구와 rollback 기준이다. 기존 [`PLAYOUT.md`](PLAYOUT.md)의 K3D 설치·해시 핀·격리 진단 규칙을 대체하지 않고, 35개 scene과 PageN 동등성 검증에 필요한 운영 절차를 추가한다.
|
||||
|
||||
## 현재 상태
|
||||
|
||||
| 항목 | 상태 |
|
||||
|---|---|
|
||||
| 기본 모드 | `DryRun`; COM 객체와 `KTAPConnect`를 만들지 않음 |
|
||||
| 자동 테스트 | 최신 전체 재검증에서 Core Debug/Release x64 각각 779/779, Playout 각각 355/355, Infrastructure 각각 64/64, Web safety 11/11 통과; 이후 테스트 추가 시 개수보다 실패 0건을 기준으로 재확인 |
|
||||
| Visual Studio 2026 | Debug/Release x64 빌드 성공 |
|
||||
| trusted Release x64 MSIX | 생성·서명 검증·x64 설치·package context 실행 성공. 실제 DB DryRun에서 5001 timer refresh, 5074 playlist/Page NEXT와 1~20페이지 마지막 경계, TAKE OUT 정리를 확인 |
|
||||
| 실제 데이터 전체 scene smoke | 34개 도달 가능 builder 통과: 33개 DB + `s5025` trusted 외부 CP949 파일. `s8086` diagnostic 포함 Oracle/MariaDB query 55건 통과 |
|
||||
| `s5025` trusted 수동 파일 | 외부 CP949 source 통합 검증 통과; 실제 파일/환경은 계속 Git 밖에서 회차별 preflight |
|
||||
| `s5006` `Video\큐브배경.vrv` | 현재 승인 Cuts root에서 누락 |
|
||||
| 이번 마이그레이션 WebView workflow의 실제 Tornado2 검증 | **미승인·미실행** |
|
||||
|
||||
과거 고정 `5001 → 5006` runner의 실제 PGM 기록은 연결과 기본 K3D 명령 표면의 증거다. 현재 WebView playlist, fresh TAKE IN, Page NEXT, timer refresh, callback/unload의 동등성 완료 증거는 아니다. 이번 WebView runtime의 실제 Tornado2 Network Monitoring/PGM 회차는 여전히 승인되지 않았고 실행하지 않았다.
|
||||
|
||||
## 절대 안전 규칙
|
||||
|
||||
1. 앱의 기본값은 항상 `DryRun`으로 유지한다.
|
||||
2. `DryRun`, `--probe`, `--dry-run`, `--test-plan`은 `KTAPConnect`를 호출하지 않는다. 이때 Tornado2 Network Monitoring에 기록이 없는 것이 정상이다.
|
||||
3. 실제 출력은 승인된 테스트 scene, 지정된 PGM, 지정된 회차에서만 허용한다.
|
||||
4. Connect/PREPARE를 시작하기 전에 회차 범위 승인이 있어야 한다. **현재 PGM에 TAKE IN을 보내기 직전에는 같은 회차의 별도 명시적 승인을 다시 받아야 한다.**
|
||||
5. 승인에 없는 NEXT, Page NEXT, TAKE OUT 이외 명령 또는 다른 scene으로 범위를 넓히지 않는다.
|
||||
6. timeout, `OutcomeUnknown`, `WEB_TIMEOUT`, refresh fault, 응답/화면 불일치, 대상 프로세스 변경, callback 누락 상태에서는 같은 명령을 자동 또는 수동으로 반복하지 않는다.
|
||||
7. 실제 검증 회차에는 `reconnectEnabled=false`, `maximumReconnectAttempts=0`을 사용한다. 재접속은 새 회차와 새 승인으로만 수행한다.
|
||||
8. 안전 게이트, x64 vendor hash pin, scene allowlist, process/window/port ownership 검사를 완화하지 않는다.
|
||||
9. Web 입력으로 object 이름, 파일 경로, K3D method, 임의 SQL을 받지 않는다.
|
||||
10. 실제 on-air일 수 있는 scene을 수동으로 unload하거나 COM RCW를 강제 release하지 않는다.
|
||||
11. pending Play callback이 있으면 TAKE OUT 이외의 PREPARE/TAKE IN/NEXT/timer refresh를 실행하지 않는다. lifecycle callback이 남아 있으면 Disconnect하지 않는다.
|
||||
|
||||
## Git 밖에서 준비할 항목
|
||||
|
||||
다음 항목은 로컬 운영 설정 또는 승인된 외부 자산이다. Git, MSIX, 로그 첨부, 테스트 fixture에 복사하지 않는다.
|
||||
|
||||
- 원본 test Cuts root: `C:\Users\MD\source\repos\MBN_STOCK_N\MBN_STOCK_N\bin\Debug\Cuts`
|
||||
- 실제 `.t2s`, image, texture, video와 기타 scene 자산
|
||||
- `s5006`의 상대 자산 `Video\큐브배경.vrv`; 현재 위 Cuts root에는 없으므로 제공 전 실제 PREPARE 금지
|
||||
- `s5025` trusted CP949 수동 파일 디렉터리와 환경 변수 `MBN_STOCK_S5025_MANUAL_DATA_DIRECTORY`
|
||||
- Oracle/MariaDB host, SID/service, database, user, password와 운영 query selector
|
||||
- K3D/Tornado vendor DLL, Interop, license와 설치 경로
|
||||
- `MBN_STOCK_K3D_NATIVE_SHA256`, `MBN_STOCK_K3D_INTEROP_SHA256` 승인 값과 승인 근거
|
||||
- MSIX 서명 인증서, 개인 키, 암호와 배포용 secrets
|
||||
- 실제 Tornado host/port, output channel, PGM 창 정보와 운영 설정
|
||||
- Network Monitoring/PGM screenshot·영상·manifest 등 실제 방송 증거
|
||||
|
||||
Cuts root는 이번 작업에서 읽기 전용으로 취급한다. [`Test-LegacyCutCoverage.ps1`](../scripts/Test-LegacyCutCoverage.ps1)은 45개 active alias의 cut 존재 여부만 검사하며 자산을 복사하거나 수정하지 않는다.
|
||||
|
||||
## 승인 게이트
|
||||
|
||||
### Gate A: 회차 범위 승인
|
||||
|
||||
Connect 또는 실제 PREPARE 전에 다음 내용을 운영 기록에 남긴다.
|
||||
|
||||
```text
|
||||
[MBN_STOCK_WEBVIEW Tornado 검증 회차]
|
||||
회차 ID:
|
||||
예정 시각/최대 종료 시각:
|
||||
대상 Tornado2/PGM 식별값:
|
||||
승인된 test cut alias:
|
||||
승인된 selector와 시작 page:
|
||||
허용 동작과 횟수: CONNECT, PREPARE, TAKE IN, NEXT/Page NEXT, timer refresh 관찰, TAKE OUT, DISCONNECT
|
||||
Network Monitoring/PGM 관찰 담당자:
|
||||
비상 TAKE OUT 담당자:
|
||||
```
|
||||
|
||||
대상, cut, selector, page, 횟수 중 하나라도 바뀌면 같은 회차 승인을 재사용하지 않는다.
|
||||
|
||||
### Gate B: TAKE IN 직전 승인
|
||||
|
||||
PREPARE 결과와 Network Monitoring 상태를 운영자가 확인한 후, TAKE IN 직전에 다음과 같이 명시적인 승인을 받아야 한다.
|
||||
|
||||
```text
|
||||
회차 <ID>의 준비된 cut <alias>, page <N>을 현재 PGM에 TAKE IN 1회 실행하는 것을 승인한다.
|
||||
```
|
||||
|
||||
이 문구가 없거나 회차 ID/cut/page가 다르면 TAKE IN을 실행하지 않는다. 이전 대화의 일반적 동의, 과거 회차 승인, DryRun 성공은 Gate B를 충족하지 않는다.
|
||||
|
||||
## 실제 연결 전 offline preflight
|
||||
|
||||
아래 단계는 Tornado에 명령을 보내지 않는다.
|
||||
|
||||
1. 원본 기준선과 cut alias를 확인한다.
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-LegacySceneBaseline.ps1
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-LegacyCutCoverage.ps1 `
|
||||
-CutRoot "C:\Users\MD\source\repos\MBN_STOCK_N\MBN_STOCK_N\bin\Debug\Cuts"
|
||||
```
|
||||
|
||||
2. Debug/Release x64 테스트와 Web bridge 검사를 실행한다.
|
||||
|
||||
```powershell
|
||||
dotnet test .\tests\MBN_STOCK_WEBVIEW.Core.Tests\MBN_STOCK_WEBVIEW.Core.Tests.csproj -c Debug -p:Platform=x64
|
||||
dotnet test .\tests\MBN_STOCK_WEBVIEW.Core.Tests\MBN_STOCK_WEBVIEW.Core.Tests.csproj -c Release -p:Platform=x64
|
||||
dotnet test .\tests\MBN_STOCK_WEBVIEW.Playout.Tests\MBN_STOCK_WEBVIEW.Playout.Tests.csproj -c Debug -p:Platform=x64
|
||||
dotnet test .\tests\MBN_STOCK_WEBVIEW.Playout.Tests\MBN_STOCK_WEBVIEW.Playout.Tests.csproj -c Release -p:Platform=x64
|
||||
dotnet test .\tests\MBN_STOCK_WEBVIEW.Infrastructure.Tests\MBN_STOCK_WEBVIEW.Infrastructure.Tests.csproj -c Debug -p:Platform=x64
|
||||
dotnet test .\tests\MBN_STOCK_WEBVIEW.Infrastructure.Tests\MBN_STOCK_WEBVIEW.Infrastructure.Tests.csproj -c Release -p:Platform=x64
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-WebPlayout.ps1
|
||||
```
|
||||
|
||||
3. 실제 DB smoke는 read-only query만 사용하는 별도 단계로 실행한다. DB 오류가 있으면 Tornado 단계로 넘어가지 않는다.
|
||||
|
||||
```powershell
|
||||
dotnet run --project .\tools\MBN_STOCK_WEBVIEW.DbSmoke\MBN_STOCK_WEBVIEW.DbSmoke.csproj -c Release --no-restore
|
||||
```
|
||||
|
||||
4. 설치된 trusted Release x64 MSIX가 package context에서 실행되는지 확인한다. `bin` 또는 loose EXE를 실제 검증에 사용하지 않는다.
|
||||
5. K3D Registry64, AMD64 PE, TypeLib/Interop metadata, 두 SHA-256 pin, license를 확인한다. DLL을 repo 또는 앱 폴더로 복사해 우회하지 않는다.
|
||||
6. 정확히 하나의 승인 대상 Tornado2 프로세스, PGM 창, Network Server TCP port와 LISTEN 소유권을 확인한다. 매뉴얼 예시 port를 추정해 사용하지 않는다.
|
||||
7. `s5025`를 선택했다면 trusted 외부 디렉터리와 파일 preflight가 성공해야 한다. `s5006`를 선택했다면 `Video\큐브배경.vrv`가 승인 asset root 안에 있어야 한다.
|
||||
8. 실제 COM을 사용하는 Test/Live playlist의 모든 cut이 로컬 폐쇄형 allowlist 안에 있고 selector가 closed enum/lookup 규칙을 통과하는지 DryRun에서 확인한다. allowlist가 비어 있으면 실제 모드 초기화 자체를 거부해야 한다.
|
||||
9. 로컬 playout 설정의 `legacySceneFadeDuration` 기본값 6과 `legacySceneBackgroundKind`를 확인한다. 공통 background를 쓸 때 `legacySceneBackgroundAssetPath`는 `sceneDirectory` 아래의 승인된 상대 경로여야 한다. `PlayoutSceneCompositionFactory`의 DryRun preflight가 파일 존재, 허용 확장자, root 탈출, 절대 경로와 reparse point를 모두 거부하는지 확인한다. Web에는 이 asset 경로를 보내지 않는다.
|
||||
10. Web DryRun에서 45개 active alias, row별 `enabled`, PREPARE 뒤 snapshot freeze, current entry/builder/page size/current rows/last-page/preview와 refresh 상태를 확인한다. pending command와 `OutcomeUnknown`/timeout quarantine에서도 playlist 편집이 잠겨야 한다. preview에 image/texture/video 경로가 나타나면 실제 회차를 중단한다.
|
||||
|
||||
하나라도 실패하면 회차를 시작하지 않는다. 설정을 수정한 뒤 처음부터 새 preflight 결과를 만든다.
|
||||
|
||||
## 정상 실행 순서와 관찰점
|
||||
|
||||
### 1. Connect
|
||||
|
||||
- Gate A와 모든 preflight를 다시 확인한다.
|
||||
- Connect는 한 번만 보낸다.
|
||||
- `OnHello`와 Network Monitoring의 request/response를 함께 확인한다.
|
||||
- Connect 결과가 accepted이지만 `OnHello` 또는 monitor 왕복이 확인되지 않으면 준비 완료로 간주하지 않는다.
|
||||
|
||||
### 2. PREPARE
|
||||
|
||||
PREPARE는 다음 native 순서를 수행해야 한다.
|
||||
|
||||
1. 현재 활성 playlist 항목과 page 0을 native loader가 조회한다.
|
||||
2. scene load, trusted common background와 기본 fade 6 또는 승인된 로컬 fade/scene effect를 적용한다.
|
||||
3. `BeginTransaction` → allowlisted mutation → `QueryVariables` → `EndTransaction`을 실행한다.
|
||||
4. layout 10을 `Prepare`한다.
|
||||
|
||||
관찰자는 Network Monitoring의 load/prepare 관련 왕복이 한 번씩인지, 앱이 `Prepared`와 정확한 cue/page를 표시하는지 확인한다. PGM에 의도하지 않은 on-air 변화가 있으면 즉시 회차를 중단하고 장애 절차를 따른다.
|
||||
|
||||
### 3. TAKE IN
|
||||
|
||||
- Gate B를 받은 뒤 한 번만 실행한다.
|
||||
- PREPARE 때의 DTO를 재사용하지 않고 frozen entry/page를 실제 DB에서 새로 조회하는지 확인한다.
|
||||
- fresh scene의 `LoadScene` → transaction → `Prepare(10)`이 명확히 성공한 뒤 `Play(10)`이 한 번 실행되는지와 `OnScenePlayed`를 확인한다.
|
||||
- PGM에서 원본과 비교할 object 이름별 값, 표시 상태, 색, 위치/크기, crop, path, image/texture/video, fade를 기록한다.
|
||||
- Network Monitoring의 두 번째 load/prepare와 PLAY request/response, PGM 화면 시각을 같은 증거 묶음에 보존한다.
|
||||
|
||||
### 4. NEXT
|
||||
|
||||
앱이 반환한 `nextKind`가 기준이다. Web에서 임의로 page/playlist 유형을 정하지 않는다.
|
||||
|
||||
- `PageNext`: playlist index를 유지하고 원본 `Next_Scene(0)`처럼 다음 page의 fresh 데이터를 조회한 뒤 새 scene을 `LoadScene` → transaction mutation → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)`한다. 이 경로에서 `GetPlayingScene` in-place 갱신을 기대하지 않는다.
|
||||
- `PlaylistNext`: 현재 page가 마지막일 때 다음 활성 playlist 항목을 load/prepare/play한다. 비활성 항목은 앞으로 건너뛰고 끝에서 wrap하지 않는다.
|
||||
- 각 NEXT는 Gate A에 승인된 유형과 횟수 안에서만 한 번씩 실행한다.
|
||||
- `pageIndex`, `pageCount`, `itemCount`, 마지막 부분 page의 빈 object clear/hide와 PGM 화면을 함께 확인한다.
|
||||
- 모든 operator command는 timer를 먼저 멈춘다. TAKE IN/playlist NEXT 뒤에는 원본 `m_time`으로 첫 timer가 시작되지만 Page NEXT 성공 뒤에는 timer가 정지 상태로 남아야 한다.
|
||||
|
||||
### 5. Timer refresh
|
||||
|
||||
- 승인 범위에 포함된 경우에만 자동 refresh를 관찰한다. 첫 실행은 해당 cut의 원본 `m_time` 뒤, 이후 성공한 실행은 3초 간격이어야 한다.
|
||||
- 각 tick은 current entry/page의 fresh DB DTO를 사용하며 K3D 호출은 원본처럼 `Play(10)` → `GetPlayingScene(10)` → transaction mutation → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)` 순서다.
|
||||
- timer refresh는 scene-level background와 fade effect를 다시 적용하지 않는다.
|
||||
- 앱의 `refreshActive`, `refreshNextAt`, `refreshLastSuccessAt`, fault code/message와 PGM 데이터를 함께 기록한다.
|
||||
- refresh가 실패하거나 timeout/unknown이면 fault latch가 켜지고 반복이 즉시 끝나야 한다. TAKE OUT 이외의 명령을 시도하지 않는다.
|
||||
- 명확히 성공한 TAKE OUT 뒤 native refresh state가 reset되면 전용 refresh fault marker만 사라져야 한다. `OutcomeUnknown`, `WEB_TIMEOUT` 또는 native quarantine 표시는 함께 reset되면 안 된다.
|
||||
|
||||
### 6. TAKE OUT과 Disconnect
|
||||
|
||||
- 승인된 TAKE OUT 한 번으로 `TakeOut(All)`/`StopAll`을 실행한다.
|
||||
- `OnCutOut` 또는 `OnStopAll`, 앱 state clear와 PGM의 검은 화면 또는 승인된 종료 상태를 확인한다.
|
||||
- pending callback과 retired scene 정리가 끝난 뒤에만 Disconnect한다.
|
||||
- Network Monitoring에서 stop 계열 왕복과 BYE/disconnect를 확인한다.
|
||||
|
||||
## Network Monitoring과 PGM 증거 체크리스트
|
||||
|
||||
vendor monitor의 실제 명령 표기는 버전에 따라 다를 수 있으므로 추정 문자열로 성공을 만들지 않는다. raw 화면과 시각을 보존하고 다음 의미 단위로 대조한다.
|
||||
|
||||
| 단계 | Network Monitoring | PGM/앱 |
|
||||
|---|---|---|
|
||||
| Connect | HELLO 또는 대응 connect request/response 한 쌍 | 앱 Connected, 대상 process generation 일치 |
|
||||
| PREPARE | scene load, mutation transaction, prepare의 중복 없는 왕복 | Prepared cue/page/row 수 일치, 의도하지 않은 on-air 없음 |
|
||||
| TAKE IN | fresh DB 결과의 두 번째 load/prepare 뒤 PLAY request/response | `OnScenePlayed`, 실제 object 값과 시각 상태 일치 |
|
||||
| Page NEXT | 같은 alias의 새 scene load/transaction/prepare/play 왕복 | playlist index 유지, page만 +1, 빈 slot clear/hide, refresh timer 정지 |
|
||||
| Playlist NEXT | 다음 cut load/prepare/play 왕복 | 다음 활성 항목과 page 0 표시 |
|
||||
| Timer refresh | PLAY, `GetPlayingScene` update transaction, prepare, PLAY의 중복 없는 왕복 | 첫 `m_time` 뒤 실행, 이후 3초, 같은 entry/page의 fresh 값, refresh 상태 정상 |
|
||||
| TAKE OUT | STOP/STOPALL 대응 왕복 | `OnCutOut`/`OnStopAll`, 승인된 종료 화면 |
|
||||
| Disconnect | BYE 또는 대응 disconnect 왕복 | callback pending 0, 연결 해제 |
|
||||
|
||||
회차 증거에는 다음을 포함한다.
|
||||
|
||||
- 회차 ID, package version, Git commit, 실행 시각과 timezone
|
||||
- cut alias, closed selector, page index/count, DB 조회 식별값; password와 connection string 제외
|
||||
- 각 앱 명령의 결과 code와 `OutcomeUnknown` 여부
|
||||
- 같은 시각의 Network Monitoring과 PGM 화면
|
||||
- PREPARE/TAKE IN/NEXT/timer refresh/TAKE OUT 전후 screenshot 또는 연속 capture
|
||||
- callback 순서, retired/unloaded scene 수, disconnect 결과
|
||||
- Web의 frozen entry/builder/page size/current rows/last-page/preview와 refresh status; asset path는 제외
|
||||
- 운영자 최종 판정과 불일치 목록
|
||||
|
||||
증거는 Git 제외 로컬 경로 또는 승인된 증거 저장소에 보관한다. 캡처에 host, port, 계정, license 정보가 보이면 외부 전달 전에 별도 보안 절차로 처리하며 원본 증거를 repo에 넣지 않는다.
|
||||
|
||||
## 장애 판정과 반복 금지
|
||||
|
||||
| 상황 | 즉시 조치 | 재시도 조건 |
|
||||
|---|---|---|
|
||||
| DB/selector/asset preflight 실패, COM 호출 전 명시적 `Rejected` | 회차 중단, DryRun으로 복귀, 원인 기록 | offline 수정과 전체 preflight 후 새 회차 승인 |
|
||||
| Connect timeout 또는 `OnHello`/monitor 불일치 | 결과를 불명확으로 격리, Connect 반복 금지 | 운영자가 세션과 process/port 상태를 확인한 뒤 새 회차 승인 |
|
||||
| PREPARE/TAKE IN/NEXT/refresh/TAKE OUT timeout | `OutcomeUnknown`으로 취급, 같은 명령과 반대 명령 자동 실행 금지 | PGM/monitor/콜백을 사람이 확인하고 출력 안전을 복구한 뒤 새 회차 승인 |
|
||||
| `WEB_TIMEOUT` | strict timeout-quarantine message를 native에 전달. MainWindow가 process-lifetime latch를 먼저 세우고 vendor session을 quarantine; 늦은 응답, UI 재시도와 WebView reload로 해제 금지 | native status와 PGM을 사람이 대조하고 process를 새로 시작한 뒤 새 승인 |
|
||||
| refresh DB/scene/COM fault | refresh loop 중단, fault latch 유지, TAKE OUT 외 명령 금지 | 정상 TAKE OUT과 실제 화면 확인 후 offline 원인 수정, 새 회차 승인 |
|
||||
| 명령 성공 응답과 PGM 화면 불일치 | 앱 state를 신뢰하지 말고 회차 중단 | PGM 운영자가 실제 상태를 판정하고 새 회차 승인 |
|
||||
| callback 누락/지연 | scene을 unload하거나 disconnect 강제하지 않음 | callback 도착 또는 운영자의 수동 안전 판정 후 새 회차 |
|
||||
| Tornado process generation, 창, LISTEN 소유권 변경 | 모든 자동 동작 중지, 대상 격리 | 새 process를 처음부터 검증하고 새 승인 |
|
||||
| 연결 장애/Disconnect timeout | 자동 reconnect 금지, 연결·출력 상태를 불명확으로 기록 | 운영자가 Tornado 세션을 확인한 뒤 새 회차 |
|
||||
|
||||
`OutcomeUnknown`은 단순 실패 code가 아니라 실제 출력 결과를 모른다는 latch다. Cancelled, Rejected, `WEB_TIMEOUT` 또는 UI 오류로 낮추지 않는다. timeout quarantine은 native process-lifetime latch이므로 WebView reload나 오류 창으로 해제되지 않는다. refresh fault marker는 성공한 TAKE OUT 뒤 native reset에만 맞춰 제거할 수 있지만 unknown/quarantine latch에는 영향을 주지 않는다. process를 재시작해 표시를 지우는 행위도 실제 출력 복구가 아니다.
|
||||
|
||||
## Rollback과 unload
|
||||
|
||||
### 명확히 출력 전 실패한 경우
|
||||
|
||||
COM 명령 전 validation에서 명시적으로 거부됐고 Network Monitoring/PGM에 변화가 없음을 확인한 경우에만 offline 수정으로 돌아간다. 수정 후에는 기존 회차를 이어가지 않고 새 회차로 시작한다.
|
||||
|
||||
### 출력이 명확히 active이고 엔진이 정상인 경우
|
||||
|
||||
Gate A에 포함된 TAKE OUT을 한 번 실행한다. 성공 callback과 PGM 종료 화면을 확인한 뒤 Disconnect한다. 같은 TAKE OUT을 확인용으로 반복하지 않는다.
|
||||
|
||||
### 결과가 불명확한 경우
|
||||
|
||||
1. 앱에서 추가 PREPARE, TAKE IN, NEXT, timer refresh, TAKE OUT, Disconnect를 자동으로 보내지 않는다.
|
||||
2. 현재 앱 status, 마지막 명령, Network Monitoring, PGM을 캡처한다.
|
||||
3. 방송 운영자가 Tornado 본 프로그램/PGM에서 실제 출력과 세션을 판정한다.
|
||||
4. 필요한 수동 정리는 방송 운영 권한과 현장 절차로 수행한다. Codex나 앱이 임의 명령을 추정하지 않는다.
|
||||
5. 안전한 black/approved fallback과 세션 종료가 사람에게 확인된 뒤 앱을 종료한다.
|
||||
6. 장애 원인과 조치가 확정될 때까지 새 검증을 승인하지 않는다.
|
||||
|
||||
### Scene 수명 규칙
|
||||
|
||||
- `OnScenePlayed`가 새 scene의 재생을 확정하면 이전 scene만 retired queue로 이동한다.
|
||||
- `OnCutOut`은 해당 layout, `OnStopAll`은 전체 player의 on-air 참조를 정리할 근거다.
|
||||
- pending Play callback이 있으면 TAKE OUT 이외의 명령을 fail-closed 차단한다. pending `OnScenePlayed`/`OnCutOut`/`OnStopAll`이 있으면 Disconnect와 unload를 시도하지 않는다.
|
||||
- `CutOut`/`StopAll` pending counter는 SDK dispatch 직전에 증가하고 동기 호출 실패 시 감소한다. 성공 callback은 대응 counter를 하나 감소시키고 중단된 Play의 pending accounting을 취소한다. dispatch 뒤 cancellation/timeout이면 counter 상태를 성공으로 추정하지 않는다.
|
||||
- 이전 connection generation에서 늦게 온 callback은 현재 generation의 state나 scene을 정리하는 근거로 쓰지 않는다.
|
||||
- 장기 실행 중 retired scene은 callback으로 안전성이 확인된 뒤 STA queue 안에서 `Unload`와 release를 수행한다.
|
||||
- 강제 GC, 임의 RCW release, 현재 on-air scene unload를 rollback으로 사용하지 않는다.
|
||||
|
||||
## 완료 판정
|
||||
|
||||
실제 검증 회차는 다음을 모두 만족할 때만 성공이다.
|
||||
|
||||
1. Gate A와 Gate B가 같은 회차에 기록돼 있다.
|
||||
2. 허용된 test cut과 selector만 사용했다.
|
||||
3. PREPARE → fresh DB TAKE IN → 승인된 Page NEXT/playlist NEXT → 원본 간격 timer refresh → TAKE OUT 순서가 중복 없이 완료됐다.
|
||||
4. 모든 결과가 명확한 성공이며 `OutcomeUnknown=false`다.
|
||||
5. Network Monitoring request/response와 PGM 화면이 같은 시각 증거로 남아 있다.
|
||||
6. 원본과 object 값, 표시, 색, 위치/크기, crop, path, asset, fade, page와 refresh 상태가 일치한다. Web preview에는 실제 asset path가 없다.
|
||||
7. callback과 unload 순서가 정상이고 Disconnect가 성공했다.
|
||||
8. DB password, vendor DLL/license, 인증서/private key, 실제 cut/asset, 운영 설정이 Git 변경에 포함되지 않았다.
|
||||
|
||||
검증 후 결과를 [`SCENE_EQUIVALENCE.md`](SCENE_EQUIVALENCE.md)의 해당 행에 기록한다. 일부 scene 성공이나 과거 runner 성공으로 나머지 행을 일괄 완료 처리하지 않는다.
|
||||
168
docs/SCENE_EQUIVALENCE.md
Normal file
168
docs/SCENE_EQUIVALENCE.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 35개 Scene 동등성 완료 매트릭스
|
||||
|
||||
기준 시각은 2026-07-10이다. 원본 `C:\Users\MD\source\repos\MBN_STOCK_N`은 읽기 전용으로만 분석했으며, 이 문서 작업에서도 원본 파일을 수정하지 않았다. 원본 35개 builder의 파일 해시는 [`legacy-scene-source-hashes.json`](legacy-scene-source-hashes.json), 원본 구조 기준선 검사는 [`Test-LegacySceneBaseline.ps1`](../scripts/Test-LegacySceneBaseline.ps1)에 있다.
|
||||
|
||||
이 문서에서 **구현 완료**는 DTO → mutation builder, 실제 데이터 loader, playlist selection resolver 또는 명시적 runtime route, 자동 테스트가 모두 존재한다는 뜻이다. **동등성 완료**는 여기에 실제 데이터와 승인된 Tornado2/PGM 검증까지 통과해야 한다. 따라서 현재 전체 목표는 아직 완료가 아니다.
|
||||
|
||||
## 현재 판정
|
||||
|
||||
| 항목 | 상태 | 근거와 제한 |
|
||||
|---|---|---|
|
||||
| 원본 inventory와 hash 기준선 | 완료 | 35/35 builder, 원본 기준선 스크립트 35/35 |
|
||||
| DTO와 mutation builder | 완료 | registry가 정확히 35개 builder를 발견하고 catalog와 1:1 대조 |
|
||||
| loader와 runtime route | 완료 | MainForm 도달 가능 builder 34개, active alias 45개를 fail-closed route로 등록; `s8086`은 원본과 같이 무alias 진단 전용 |
|
||||
| 자동 테스트 | 완료 | 최신 전체 재검증에서 Core Debug/Release x64 각각 779/779, Playout 각각 355/355, Infrastructure 각각 64/64, Web safety 11/11 통과. 테스트 추가에 따라 개수는 달라질 수 있으며 핵심 판정은 각 suite의 실패 0건이다. |
|
||||
| Visual Studio 2026 빌드 | 완료 | Debug/Release x64 빌드 성공 |
|
||||
| Release x64 MSIX | 완료 | trusted 개발 MSIX 생성·서명 검증·x64 설치·package context 실행 성공. 실제 DB DryRun에서 `5001 PREPARE → fresh TAKE IN → timer refresh → 5074 playlist NEXT → Page NEXT → TAKE OUT`과 5074의 `1/20`~`20/20`, 마지막 `END OF PLAYLIST`/NEXT 비활성, 편집 잠금 해제를 확인 |
|
||||
| 실제 데이터 전체 장면 smoke | 완료 | 34개 도달 가능 builder 전체 통과: 33개 Oracle/MariaDB loader와 `s5025` trusted 외부 CP949 파일. 무alias `s8086` diagnostic도 통과했고 실제 Oracle/MariaDB query 55건이 모두 성공했다. |
|
||||
| 이번 마이그레이션의 실제 Tornado2/PGM | **미승인·미실행** | 현재 WebView → native workflow로 실제 PREPARE/fresh TAKE IN/Page NEXT/playlist NEXT/timer refresh/TAKE OUT, Network Monitoring, PGM 화면을 함께 검증하지 않음 |
|
||||
|
||||
과거 고정 runner로 수행한 `5001 → 5006` PGM 왕복은 K3D 연결과 기본 load/play/stop 경로의 증거일 뿐이다. 35개 builder, 실제 DB mutation, Page NEXT와 현재 WebView workflow의 동등성 증거로 재사용하지 않는다.
|
||||
|
||||
## 표기
|
||||
|
||||
Mutation 약어는 실제 COM 메서드를 Web 입력에 노출하지 않는 COM-neutral 모델을 뜻한다.
|
||||
|
||||
| 약어 | mutation |
|
||||
|---|---|
|
||||
| `V` | `PlayoutSetValue` |
|
||||
| `A` | `PlayoutSetAssetValue` |
|
||||
| `Vis` | `PlayoutSetVisible` |
|
||||
| `C` | `PlayoutSetFaceColor` |
|
||||
| `Pos` | `PlayoutSetPosition` |
|
||||
| `PosK` | `PlayoutSetPositionKey` |
|
||||
| `Scale` | `PlayoutSetScale` |
|
||||
| `Crop` | `PlayoutSetCropKey` |
|
||||
| `Angle` | `PlayoutSetCircleAngleKey` |
|
||||
| `Path` | `PlayoutSetPathPoints` |
|
||||
| `Shape` | `PlayoutSetPathShapePoints` |
|
||||
| `BgV` | `PlayoutSetBackgroundVideo` 및 background 사용 상태 |
|
||||
|
||||
실제 데이터 상태는 다음과 같이 기록한다.
|
||||
|
||||
- `DB-P`: 실제 Oracle/MariaDB 조회 → typed DTO → mutation preflight가 통과했다.
|
||||
- `FILE-P`: `s5025`의 trusted 외부 CP949 수동 파일 → DTO → mutation preflight가 통과했다. 실제 파일과 디렉터리는 계속 Git 밖에 둔다.
|
||||
- `DB-DP`: 원본 MainForm에서 도달하지 않는 `s8086` diagnostic 조회와 mutation preflight가 통과했다. 이 결과로 runtime alias를 만들지는 않는다.
|
||||
- `TOR-W`: 이번 마이그레이션 runtime으로 해당 scene을 실제 Tornado2/PGM에서 검증하지 않았다. 회차 승인 전에는 실행하지 않는다.
|
||||
- `TOR-NA`: active alias가 없어 운영 송출 대상이 아니다.
|
||||
|
||||
## 자동 테스트 묶음
|
||||
|
||||
아래 묶음은 최신 Core Debug/Release x64 전체 실행에 포함돼 실패 0건으로 통과했다. 모든 행에는 공통으로 `LegacySceneCatalogTests`, `LegacySceneMutationBuilderRegistryTests`, `LegacySceneRuntimeCoverageTests`, `LegacySceneDataSourceRouterTests`가 적용된다.
|
||||
|
||||
| 코드 | 테스트 파일 |
|
||||
|---|---|
|
||||
| `T-PARAM` | `ParameterizedMarketSceneBuildersTests`, `ParameterizedMarketSceneDataLoadersTests`, `LegacyParameterizedSceneRequestResolverTests` |
|
||||
| `T-PANEL` | `ReadOnlyPanelSceneBuildersTests`, `EquityPanelSceneDataLoadersTests`, `PanelSceneDataLoadersTests`, `MarketPanelSceneDataLoadersTests` |
|
||||
| `T-GRID` | `TabularMarketSceneBuildersTests`, `FoundationalSceneBuildersTests`, `GridMarketSceneDataLoadersTests`, `TraderQuoteSceneDataLoadersTests`, `LegacyGridMarketSceneRequestResolverTests` |
|
||||
| `T-MANUAL` | `TrustedManualSceneDataLoader5025Tests`, Infrastructure의 `S5025TrustedManualFileDataSourceTests` |
|
||||
| `T-COMP` | `ComparisonAndYieldSceneBuildersTests`, `ComparisonAndYieldSceneDataLoadersTests`, `ComparisonAndYieldLegacyRequestResolverTests` |
|
||||
| `T-CHART` | `ChartSceneBuilders5078To5084Tests`, `ChartSceneDataLoadersTests`, `ChartLegacySceneRequestResolverTests` |
|
||||
| `T-PAGED` | `PagedQuoteSceneBuildersTests`, `PagedQuoteSceneDataLoadersTests`, `ScenePagingTests`, `LegacyPlayoutWorkflowTests` |
|
||||
| `T-FOUND` | `FoundationalSceneBuildersTests`, `ManualSceneDataLoadersTests`, 해당 panel/grid loader 테스트 |
|
||||
| `T-5082` | `GridSceneBuilder5082Tests` |
|
||||
| `T-CANDLE` | `CandleSceneBuilderTests`, `CandleSceneDataLoaderTests` |
|
||||
|
||||
## 35개 builder별 완료 판정
|
||||
|
||||
| Builder / alias / page | 원본 기능 | 새 builder → loader → resolver/route | mutation | 자동 테스트 | 실제 데이터 | 실제 Tornado |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `s5001` / `5001`, `N5001` / — | 국내·NXT·해외 지수, 환율, 업종, 종목 단일 시세와 등락 표식 | `S5001SceneMutationBuilder` → `S5001SceneDataLoader` → `LegacyParameterizedSceneRequestResolver` | `V, Vis, A` | `T-PARAM` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5006` / `5006` / — | 국내·NXT 종목 현재·시가·고가·저가와 비율, 등락 상태, 큐브 배경 영상 | `S5006SceneMutationBuilder` → `S5006DomesticSceneDataLoader`/`S5006NxtSceneDataLoader` → market 직접 route | `V, Vis, C, BgV` | `T-PANEL` 통과 | `DB-P`; `Video\큐브배경.vrv` 외부 자산 누락 | `TOR-W` |
|
||||
| `s5011` / `5011` / — | 국내·NXT 종목 시세, 액면가, 자본금, 시가총액, 순위 | `S5011SceneMutationBuilder` → `S5011SceneDataLoader` → branch 직접 route | `V, Vis, C` | `T-PANEL` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5016` / `5016` / — | 미국·중화권·유럽·아시아 지수와 채권·환율·원자재 3열 panel | `S5016SceneMutationBuilder` → `S5016SceneDataLoader` → closed target 직접 route | `V, Vis` | `T-PANEL` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s50160` / `50160` / — | 원면·국제금·국내금 2열 panel | `S50160SceneMutationBuilder` → `S50160SceneDataLoader` → closed target 직접 route | `V, Vis` | `T-PANEL` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5023` / `5023` / — | 코스피·코스닥 일별/월합계 주체별 매매동향 grid | `S5023SceneMutationBuilder` → `S5023SceneDataLoader` → `LegacyGridMarketSceneRequestResolver` | `V, C` | `T-GRID` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5024` / `5024` / — | 코스피·코스닥 매매동향 막대, 중앙선과 양·음 크기/위치 | `S5024SceneMutationBuilder` → `S5024SceneDataLoader` → `LegacyGridMarketSceneRequestResolver` | `V, Vis, Pos, Scale` | `T-GRID` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5025` / `5025` / — | 승인된 수동 파일의 개인·외국인·기관 순매도 좌·우 5쌍 | `S5025SceneMutationBuilder` → `S5025SceneDataLoader`/`S5025TrustedManualFileDataSource` → `LegacyGridMarketSceneRequestResolver` | `V` | `T-GRID`, `T-MANUAL` 통과 | `FILE-P`; 외부 CP949 파일 통합 검증 완료 | `TOR-W` |
|
||||
| `s5026` / `5026` / — | 두 국내 종목 주간 candle과 각 종목 시세/OHLC | `S5026SceneMutationBuilder` → `S5026SceneDataLoader` → `ComparisonAndYieldLegacyRequestResolver` | `V, Vis, C, Crop` | `T-COMP` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5029` / `5029` / — | 두 종목 candle·수익률 비교와 두 path-shape | `S5029SceneMutationBuilder` → `S5029SceneDataLoader` → `ComparisonAndYieldLegacyRequestResolver` | `V, Vis, Pos, Shape` | `T-COMP` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5032` / `8018`, `8032`, `5032` 중 선물 조건 / — | 선물을 포함한 좌·우 두 항목 plate | `S5032SceneMutationBuilder` → `S5032SceneDataLoader` → `LegacyParameterizedSceneRequestResolver` | `V, Vis, A` | `T-PARAM` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5037` / `5037` / — | 국내 종목 현재가와 매수·매도 거래원별 수량 | `S5037SceneMutationBuilder` → `S5037SceneDataLoader` → `LegacyGridMarketSceneRequestResolver` | `V, Vis` | `T-GRID` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5074` / `5074` / 5 | Oracle/MariaDB/DataManager 계열의 최대 5행 시세·수익률 목록 | `S5074SceneMutationBuilder` → `S5074SceneDataLoader` → typed paged 직접 route | `V, Vis, A` | `T-PAGED` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5076` / `5076` / — | 주요매출 구성, 기준일, 항목 비율과 누적 원형 각도 | `S5076SceneMutationBuilder` → `S5076SceneDataLoader` → subject 직접 route | `V, Vis, Angle` | `T-FOUND` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5077` / `5077` / 6 | Oracle/MariaDB/DataManager 계열의 최대 6행 시세·수익률 목록 | `S5077SceneMutationBuilder` → `S5077SceneDataLoader` → typed paged 직접 route | `V, Vis, A` | `T-PAGED` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5078` / `5078` / — | 미국·국내 섹터지수 값과 양·음 막대 크기 | `S5078SceneMutationBuilder` → `S5078SceneDataLoader` → `ChartLegacySceneRequestResolver` | `V, Vis, Scale` | `T-CHART` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5079` / `5079` / — | 성장성 지표 기간·값과 복수 path | `S5079SceneMutationBuilder` → `S5079SceneDataLoader` → `ChartLegacySceneRequestResolver` | `V, Vis, Path` | `T-CHART` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5080` / `5080` / — | 매출액 분기 시계열과 양·음 막대/중앙선 | `S5080SceneMutationBuilder` → `S5080SceneDataLoader` → `ChartLegacySceneRequestResolver` | `V, Vis, PosK, Scale` | `T-CHART` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5081` / `5081` / — | 영업이익 분기 시계열과 양·음 막대/중앙선 | `S5081SceneMutationBuilder` → `S5081SceneDataLoader` → subject 직접 route | `V, Vis, PosK, Scale` | `T-FOUND` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5082` / `5082` / — | 일자·개인·기관·외국인 매매동향 grid와 부호 variant | `S5082SceneMutationBuilder` → `S5082SceneDataLoader` → 직접 route | `V, Vis` | `T-5082` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5083` / `5083` / — | 개인·기관·외국인 매매 시계열, baseline과 세 path | `S5083SceneMutationBuilder` → `S5083SceneDataLoader` → `ChartLegacySceneRequestResolver` | `V, Pos, Path` | `T-CHART` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5084` / `5084` / — | 코스피·코스닥 매매 시계열, baseline과 path | `S5084SceneMutationBuilder` → `S5084SceneDataLoader` → `ChartLegacySceneRequestResolver` | `V, Pos, Path` | `T-CHART` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5085` / `5085` / — | 프로그램 매매 grid의 구분별 금액과 부호 색상 | `S5085SceneMutationBuilder` → `S5085SceneDataLoader` → `LegacyGridMarketSceneRequestResolver` | `V, C` | `T-GRID`, `T-FOUND` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5086` / `5086` / — | 국내·해외 지수/종목/업종 수익률 시계열과 path-shape | `S5086SceneMutationBuilder` → `S5086SceneDataLoader` → `ComparisonAndYieldLegacyRequestResolver` | `V, Vis, Pos, Shape` | `T-COMP` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s50860` / `50860` / — | 국내·해외 지수/종목/업종 line 시계열과 path-shape | `S50860SceneMutationBuilder` → `S50860SceneDataLoader` → `ComparisonAndYieldLegacyRequestResolver` | `V, Vis, C, Pos, Shape` | `T-COMP` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5087` / `5087` / — | 두 국내 종목 candle·수익률 비교와 두 line shape | `S5087SceneMutationBuilder` → `S5087SceneDataLoader` → `ComparisonAndYieldLegacyRequestResolver` | `V, Vis, Pos, Shape` | `T-COMP` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s5088` / `5088` / 12 | Oracle/MariaDB/DataManager 계열의 최대 12행 시세·수익률 목록 | `S5088SceneMutationBuilder` → `S5088SceneDataLoader` → typed paged 직접 route | `V, Vis, A` | `T-PAGED` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s6001` / `6001` / — | 해외지수와 유가·금 단일 plate, 연계 이미지·영상/방향 상태 | `S6001SceneMutationBuilder` → `S6001SceneDataLoader` → closed target 직접 route | `V, A, Vis, C` | `T-PANEL` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s6067` / `6067` / — | 기관 순매수 grid와 부호 색상 | `S6067SceneMutationBuilder` → `S6067SceneDataLoader` → `LegacyGridMarketSceneRequestResolver` | `V, C` | `T-GRID`, `T-FOUND` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s8001` / `8001`, `8002` / — | 코스피·코스닥 업종 square chart와 cube 색상 | `S8001SceneMutationBuilder` → `S8001SceneDataLoader` → `LegacyParameterizedSceneRequestResolver` | `V, C` | `T-PARAM` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s8003` / `8003` / — | 국내 종목 호가·시세와 매수·매도 잔량 막대 | `S8003SceneMutationBuilder` → `S8003SceneDataLoader` → `LegacyGridMarketSceneRequestResolver` | `V, Vis, C, Scale` | `T-GRID` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s8010` / `8035`, `8061`, `8040`, `8046`, `8051`, `8056` / — | 지수·종목·거래정지·해외 candle, 거래량, 예상지수, 이동평균 path | `S8010SceneMutationBuilder` → `S8010SceneDataLoader` → alias/market/mode 직접 route | `V, Vis, C, Pos, Crop, Path` | `T-CANDLE` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s8018` / `8018`, `8032`, `5032` 중 비선물 조건 / — | 국내·NXT·해외·업종 좌·우 두 항목 plate | `S8018SceneMutationBuilder` → `S8018SceneDataLoader` → `LegacyParameterizedSceneRequestResolver` | `V, Vis, A` | `T-PARAM` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s8067` / `8067`, `5068`, `5070`, `5072` / — | 글로벌 world-map의 지역별 현재가·등락과 방향/배경 상태 | `S8067SceneMutationBuilder` → `S8067SceneDataLoader` → 직접 route | `V, Vis, C` | `T-FOUND`, `T-PANEL` 통과 | `DB-P` | `TOR-W` |
|
||||
| `s8086` / active alias 없음 / — | 유가·금 3열 원천. 원본 파일은 있으나 MainForm dispatch 없음 | `S8086SceneMutationBuilder` → `S8086DiagnosticSceneDataLoader`; 앱 runtime route 없음 | `V, C` | `T-FOUND`, `T-PANEL` 통과 | `DB-DP` | `TOR-NA` |
|
||||
|
||||
## 공통 호출 순서 동등성
|
||||
|
||||
모든 runtime scene은 Web에서 object 이름이나 K3D 메서드를 받지 않는다. Web은 closed playlist selection만 보내고, loader가 조회 결과를 명시적 DTO로 만들며, registry의 typed builder가 allowlisted mutation을 생성한다.
|
||||
|
||||
PREPARE의 공통 순서는 다음과 같다.
|
||||
|
||||
1. 활성 playlist 항목과 cut alias를 결정하고 native loader로 page DTO를 조회한다.
|
||||
2. scene을 load하고 cue-level background와 fade/scene effect를 적용한다.
|
||||
3. `BeginTransaction`을 호출한다.
|
||||
4. builder가 생성한 mutation을 원본 순서대로 적용한다.
|
||||
5. `QueryVariables`를 호출한다.
|
||||
6. `EndTransaction`을 호출한다.
|
||||
7. layout 10을 `Prepare`한다.
|
||||
|
||||
TAKE IN은 PREPARE 때의 오래된 DTO를 그대로 재생하지 않는다. 원본 `ONAirMode`와 같이 현재 playlist snapshot의 같은 entry/page를 실제 DB에서 다시 조회하고 새 scene을 `LoadScene` → transaction → `Prepare(10)`한 뒤, 그 prepare가 명확히 성공한 경우에만 `Play(10)`을 한 번 호출한다. PREPARE를 다시 누르는 원본 toggle은 `TakeOut(All)`/`StopAll`로 state를 정리한다. timeout 또는 `OutcomeUnknown`은 retry 가능한 실패로 낮추지 않는다.
|
||||
|
||||
공통 fade와 배경은 Web 입력이 아니라 로컬 trusted playout 설정에서만 온다. `legacySceneFadeDuration`의 원본 기본값은 6이며, `legacySceneBackgroundKind`, scene root 아래의 상대 `legacySceneBackgroundAssetPath`, video loop 설정을 `PlayoutSceneCompositionFactory`가 검증한다. `DryRun`에서도 경로 탈출, 절대 경로, 허용되지 않은 확장자, reparse point, 누락 파일을 실제 COM 전에 fail-closed preflight한다. asset 경로는 Web 상태와 preview에 노출하지 않는다.
|
||||
|
||||
이 순서는 `RegisteredLegacySceneCueProviderTests`, `LegacyPlayoutWorkflowTests`, Playout의 `DynamicK3dSessionTests`, `TornadoPlayoutEngineTests`에서 검증한다. 실제 COM 형식과 vendor 구현은 계속 `IPlayoutEngine` 뒤에 있다.
|
||||
|
||||
## PageN과 NEXT 동등성
|
||||
|
||||
페이지 대상은 `s5074`(5), `s5077`(6), `s5088`(12)이다. 일반 시장과 NXT 시장이 같은 계산기를 사용하며 page index는 0부터 시작한다.
|
||||
|
||||
- `pageCount = min(20, ceil(itemCount / pageSize))`다.
|
||||
- 경계값 `0`, `1`, `size-1`, `size`, `size+1`, `20*size`, `20*size+1`을 테스트한다.
|
||||
- 마지막 부분 페이지는 남은 행만 채우고 나머지 object를 clear/hide한다.
|
||||
- `s5088`의 NXT 인덱스는 비교와 조회 모두 `i + pageIndex * 12`를 사용한다.
|
||||
- 현재 항목에 다음 page가 있으면 operator Page NEXT는 playlist index를 유지하되 원본 `Next_Scene(0)`처럼 다음 page 데이터를 조회하고 새 scene을 `LoadScene` → transaction mutation → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)`한다. 이 경로는 `GetPlayingScene` in-place refresh가 아니다.
|
||||
- 마지막 page면 다음 활성 playlist 항목으로 이동한다. 비활성 항목은 원본처럼 앞으로 건너뛰며 끝에서 wrap하지 않는다.
|
||||
- 모든 operator command는 기존 refresh timer를 먼저 멈춘다. TAKE IN과 playlist NEXT 성공 뒤에는 해당 cut의 원본 `m_time`으로 첫 timer를 시작하고, 첫 성공 뒤부터 3초 간격으로 갱신한다. Page NEXT 성공 뒤에는 timer를 다시 시작하지 않는다.
|
||||
- timer refresh는 current entry/page를 fresh DB DTO로 다시 만든 뒤, 원본 `timer1_Tick`의 K3D 호출 순서인 `Play(10)` → `GetPlayingScene(10)` → transaction mutation → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)`으로 on-air scene을 갱신한다.
|
||||
|
||||
## WebView 동등성 및 안전 상태
|
||||
|
||||
- Web catalog는 35개 builder와 도달 가능한 45개 active cut alias를 모두 보존하며 `s8086`은 선택 불가 진단 항목으로 유지한다.
|
||||
- 각 playlist row는 원본 active flag에 대응하는 `enabled`를 제공한다. PREPARE 성공 시 전체 playlist와 선택 index를 immutable native snapshot으로 고정한다. pending command 또는 `OutcomeUnknown`/timeout quarantine 중에도 snapshot 편집을 잠그며, 그 상태가 해제될 때까지 Web 편집값으로 NEXT 대상을 바꾸지 않는다.
|
||||
- native 결과가 authoritative source이며 `currentEntryId`, `builderKey`, `pageSize`, `currentPageItemCount`, `isLastPage`, `nextKind`와 bounded mutation preview를 표시한다.
|
||||
- preview는 object별 typed 값/상태를 보여주되 image, texture, video와 공통 background의 실제 asset path는 항상 `configured` 같은 비밀 없는 상태로 치환한다.
|
||||
- refresh의 active/next/last-success/fault 상태를 표시한다. refresh DB·scene·COM 실패는 fault latch를 세워 TAKE OUT 외의 mutation 명령을 막고 자동 반복하지 않는다. 성공한 TAKE OUT 뒤 native refresh state가 reset되면 전용 refresh fault marker만 지우며 `OutcomeUnknown`이나 timeout quarantine은 함께 지우지 않는다.
|
||||
- native 응답 상관관계가 Web 제한 시간 안에 끝나지 않으면 `WEB_TIMEOUT`을 native에 보고한다. `ParseTimeoutQuarantine`이 검증한 요청은 MainWindow의 process-lifetime latch를 먼저 세운 뒤 vendor session을 quarantine하므로 WebView reload로 해제되지 않는다. 늦은 응답이나 UI 재시도로 같은 명령을 다시 보내지 않으며 authoritative native/PGM 상태를 사람이 확인한다.
|
||||
|
||||
## Callback과 scene 수명
|
||||
|
||||
- `OnScenePlayed`가 성공한 뒤 이전 scene을 retired queue로 옮겨 안전하게 unload한다.
|
||||
- `OnCutOut`은 해당 layout의 on-air 참조를, `OnStopAll`은 player의 모든 on-air 참조를 정리할 근거다.
|
||||
- 이전 connection generation의 늦은 callback은 현재 state를 바꾸지 않는다.
|
||||
- pending Play callback이 있으면 TAKE OUT을 제외한 PREPARE/TAKE IN/NEXT/timer refresh를 fail-closed 차단한다. 어떤 lifecycle callback이든 pending이면 Disconnect와 session recycle을 하지 않고 abandon/quarantine 및 `OutcomeUnknown`으로 보수적으로 전환한다.
|
||||
- `StopAll`/`CutOut`은 SDK dispatch 전에 각 pending completion counter를 올리고 동기 호출 실패 시 되돌린다. 성공 callback은 대응 counter를 정확히 하나 줄이며, 성공한 stop/cut은 중단된 Play가 뒤늦게 `OnScenePlayed`를 보내지 않을 수 있으므로 해당 pending Play accounting을 취소한다. dispatch 뒤 cancellation/timeout은 결과 불명확으로 취급한다.
|
||||
- 장기 실행 시 retired scene은 callback으로 안전성이 확인된 뒤 unload/release한다.
|
||||
|
||||
운영·장애 복구·승인 절차는 [`PLAYOUT_OPERATIONS.md`](PLAYOUT_OPERATIONS.md)를 따른다.
|
||||
|
||||
## 남은 완료 조건
|
||||
|
||||
다음 항목이 끝나기 전에는 이 문서의 전체 상태를 동등성 완료로 바꾸지 않는다.
|
||||
|
||||
1. 승인된 외부 asset root에 `s5006`의 `Video\큐브배경.vrv`가 제공돼야 한다.
|
||||
2. 설치된 Release x64 MSIX의 WebView workflow로 승인된 테스트 scene에 대해 PREPARE → fresh TAKE IN → Page NEXT/playlist NEXT → timer refresh → TAKE OUT을 실행해야 한다.
|
||||
3. 같은 회차의 Tornado2 Network Monitoring 명령/응답과 PGM 데이터·페이지·종료 화면을 함께 보존하고 비교해야 한다.
|
||||
4. timeout, `OutcomeUnknown`, refresh fault, callback/연결 장애 복구 절차를 실제 검증 결과에 적용하고 운영자가 판정해야 한다.
|
||||
|
||||
실제 DB/CP949 통합 검증과 자동 suite는 완료됐지만, 위 실제 Tornado2 검증은 여전히 회차 승인 전이며 실행하지 않았다.
|
||||
42
docs/legacy-scene-source-hashes.json
Normal file
42
docs/legacy-scene-source-hashes.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"sourceRootHint": "MBN_STOCK_N/MBN_STOCK_N/Scene (external read-only source)",
|
||||
"algorithm": "SHA-256",
|
||||
"builders": [
|
||||
{ "builder": "s5001", "sourceFile": "Scene/s5001.cs", "sha256": "70A8DEBCF3E68E3EEDE93DF2B0D5F49F48800F01E75273D7B06BF9032B7136EC" },
|
||||
{ "builder": "s5006", "sourceFile": "Scene/s5006.cs", "sha256": "FCF9001E837B5D48C724867B8D3717E87E893732E3685CD82057E12C9B2E4E10" },
|
||||
{ "builder": "s5011", "sourceFile": "Scene/s5011.cs", "sha256": "A2DA771481016E6F009BE2E6B0C66A67EA836A5AEA343CD3046BC2E61D869AA6" },
|
||||
{ "builder": "s5016", "sourceFile": "Scene/s5016.cs", "sha256": "43E7113E495A379C2D1A0F0B2F07AA244104BA1286DE3FCCE18225D74934C7A5" },
|
||||
{ "builder": "s50160", "sourceFile": "Scene/s50160.cs", "sha256": "3A5F3591F3678AF2578224A9E01C233BE6A99185E3D96C1CEA1D716D06CEF42C" },
|
||||
{ "builder": "s5023", "sourceFile": "Scene/s5023.cs", "sha256": "7FF3855BB9B1D63314B28B3614CB887E82855D0EF65FB40DE07F8FDB2973F678" },
|
||||
{ "builder": "s5024", "sourceFile": "Scene/s5024.cs", "sha256": "306062555EAAC5AEC1EFAC4D6C75A930D207C5CF62652BB3E1531CFF60DD7A42" },
|
||||
{ "builder": "s5025", "sourceFile": "Scene/s5025.cs", "sha256": "123EEAAEB40EA3D43AB85F098BE26838D711927F3F4D50A353F9DB44E873D011" },
|
||||
{ "builder": "s5026", "sourceFile": "Scene/s5026.cs", "sha256": "F56550975963B0787D15901175747F4A5725C364E0AA83BA41D4C5588D8FE409" },
|
||||
{ "builder": "s5029", "sourceFile": "Scene/s5029.cs", "sha256": "84E566A2AA7D960CA5136710B8E50466C4A8757E7E63B494B4AA0771C6BE985E" },
|
||||
{ "builder": "s5032", "sourceFile": "Scene/s5032.cs", "sha256": "1199B47E833C9B2ECF70772D338DD22D82D25E4D2700C4B47B49FD78D7198576" },
|
||||
{ "builder": "s5037", "sourceFile": "Scene/s5037.cs", "sha256": "62E442802D4CE329782B2B3CA95890EECCB469A94FBF6E3085F2385837EAFBD5" },
|
||||
{ "builder": "s5074", "sourceFile": "Scene/s5074.cs", "sha256": "A9DF084C412F53DCB12AFF59B6D60B093EC793636D984F684C51EF383EAD7F5D" },
|
||||
{ "builder": "s5076", "sourceFile": "Scene/s5076.cs", "sha256": "4C1212F3062C8B02004882688BA07488E0A6C79467AC79D2341A869D47A7E8FC" },
|
||||
{ "builder": "s5077", "sourceFile": "Scene/s5077.cs", "sha256": "F806AF7A406E56C9523E236543D98B87148687D3BDB4F7914C1B303A54F3DDBC" },
|
||||
{ "builder": "s5078", "sourceFile": "Scene/s5078.cs", "sha256": "F233605E97C74CB502192EB540100E20F09E734EDC13A4579296705217B866C4" },
|
||||
{ "builder": "s5079", "sourceFile": "Scene/s5079.cs", "sha256": "848359AA045B2F98D02D36A425FFD3FEEA24E98151680654141F7FF831E81537" },
|
||||
{ "builder": "s5080", "sourceFile": "Scene/s5080.cs", "sha256": "5BEBC6C222A43D00BD14D02416407D934CBB7555E488ADCCC8CDD8C0DE88581F" },
|
||||
{ "builder": "s5081", "sourceFile": "Scene/s5081.cs", "sha256": "58413871174A117F619D0D4FF8040600FCF8F3C97349D0DBA4FFB3D7554A1090" },
|
||||
{ "builder": "s5082", "sourceFile": "Scene/s5082.cs", "sha256": "0A9D6D473053D1A737527432913A1D9153113E51A096410D9C964DF4DE4027D4" },
|
||||
{ "builder": "s5083", "sourceFile": "Scene/s5083.cs", "sha256": "7EACE01BA34CECC30E6B8F9895ED400A8E25376BC7616789A8130AD46AF2349E" },
|
||||
{ "builder": "s5084", "sourceFile": "Scene/s5084.cs", "sha256": "DE8AFF7CCA22400E6F9F4CECCBC136D48646AFEA7FD5DE7E5B8282B65B62947F" },
|
||||
{ "builder": "s5085", "sourceFile": "Scene/s5085.cs", "sha256": "45EB9637B4C5D7DF6C7EAA8F45C3D51E58968F50F75DDBCEC3015B09E6C5085C" },
|
||||
{ "builder": "s5086", "sourceFile": "Scene/s5086.cs", "sha256": "A6E390000D810E34E04267E309181C7B58F092EC682960DA8C4D7A7D9A0CEE91" },
|
||||
{ "builder": "s50860", "sourceFile": "Scene/s50860.cs", "sha256": "B9498FD3BCFCF3C7716CC9C070FF43D7DD22047DDAD2E02B04B6DFBDFAA5D91E" },
|
||||
{ "builder": "s5087", "sourceFile": "Scene/s5087.cs", "sha256": "75BEB7843D373A26355B848280DED477EBF79E2569A06623E8E4715351A8B18B" },
|
||||
{ "builder": "s5088", "sourceFile": "Scene/s5088.cs", "sha256": "8F8BF4204056D04D6ECA6681735B52A86C0B6EB14187E54F43685B13B57FA3D1" },
|
||||
{ "builder": "s6001", "sourceFile": "Scene/s6001.cs", "sha256": "1FEC555C61D78C5C0E605687CC30B81B0E46D9D45506F663A80D054E32CB9EDF" },
|
||||
{ "builder": "s6067", "sourceFile": "Scene/s6067.cs", "sha256": "D37CA7D6D719ABEEC52F8A38865CAD84913E04A5281D4AA852881EA0EED5E852" },
|
||||
{ "builder": "s8001", "sourceFile": "Scene/s8001.cs", "sha256": "EE70C6D8AF855948184E1291FAF03C7D8D084E2246B3FDC03D3029F406CE0C16" },
|
||||
{ "builder": "s8003", "sourceFile": "Scene/s8003.cs", "sha256": "03A01A164CB25F4B22630227B718EB7297C4224EDA0A1397CC99C79159A13A60" },
|
||||
{ "builder": "s8010", "sourceFile": "Scene/s8010.cs", "sha256": "CC28074583D84F630FBD7CBC6A3364EB66AB4E82B9F6F387A81E414EEB0AC448" },
|
||||
{ "builder": "s8018", "sourceFile": "Scene/s8018.cs", "sha256": "01B3EE85D728CCA922393FE80303475E959D578BEAED83ABA937BB20BC290170" },
|
||||
{ "builder": "s8067", "sourceFile": "Scene/s8067.cs", "sha256": "1E8A7C4367AA7E56FEE1DF0C13CACD28FC243E393850B865D6585FCFF570D465" },
|
||||
{ "builder": "s8086", "sourceFile": "Scene/s8086.cs", "sha256": "5373B8CBFF07163A2719317EBB69A57011A0006726C9899F9F63357C6AA52D3B" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user