feat: migrate legacy playout workflow and scenes

This commit is contained in:
2026-07-10 23:58:45 +09:00
parent 491a740505
commit 8dae7b8e0d
128 changed files with 39177 additions and 205 deletions

View File

@@ -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)의 회차 승인과 반복 금지 절차를 따른다.