16 KiB
MBN_STOCK_N 동작 동등성 작업 기록
기준 원본은 C:\Users\MD\source\repos\MBN_STOCK_N이며 항상 읽기 전용으로 분석한다.
대상은 MBN_STOCK_WEBVIEW.LegacyParityApp이다. 화면 모양만 비슷하게 만드는 것이 아니라
원본 이벤트의 입력, 상태 전이, DB 조회, K3D 호출 순서와 화면 피드백을 코드 단위로 대조한다.
판정 절차
- 원본 이벤트 핸들러와 관련 필드를 추적한다.
- 정상 동작과 명백한 원본 버그를 구분한다.
- 정상 동작을 특성화 테스트로 고정한다.
- C# authoritative state와 WebView 표시를 함께 수정한다.
- 안전 게이트를 유지한 상태에서 Debug/Release x64 빌드와 전체 테스트를 실행한다.
Slice 1: 선택 행, F8, NEXT, TAKE OUT
상태: 코드 및 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.
원본 근거:
MainForm.cs:1078PlayList_CellClickMainForm.cs:1915btnTake_ClickMainForm.cs:2020ONAirModeMainForm.cs:2081btnTakeout_ClickMainForm.cs:2120btnNext_ClickMainForm.cs:2176Next_SceneMainForm.cs:2495Show_PlayList
고정한 계약:
- 운영자 선택 후보와 현재 PROGRAM 행을 독립 상태로 유지한다.
- F8은 IDLE/PREPARED/PROGRAM 모두에서 그 시점의 선택 후보를 새로 Prepare 후 Play한다.
- 선택 후보가 비활성이면 아래 방향의 첫 활성 행만 사용하며 순환하지 않는다.
- NEXT는 선택 후보가 아니라 현재 PROGRAM 행을 기준으로 Page NEXT 또는 다음 활성 컷을 결정한다.
- 선택 후보는 회색, 현재 PROGRAM 행은 분홍으로 동시에 표시한다.
- Page NEXT 결과를 상태 표시뿐 아니라 플레이리스트 Page 셀에도 남긴다.
- F8/PREPARE는 페이지형 컷을 page 1로 다시 시작한다.
- 마지막 항목에서는 NEXT를 비활성화하고 vendor 명령을 보내지 않는다.
- TAKE OUT은
StopAll범위이며 IDLE에서도 연결된 엔진에 긴급 정리를 요청할 수 있다.
남은 세부 감사:
- Page 총수를 행에 저장된 원본
current/total에서 고정할지, PREPARE 시 fresh DB 결과로 다시 계산할지에 대한 동등성은 PageN 전용 Slice에서 다시 판정한다. - column 3의 정확한 한글 문자열로 Page NEXT를 분기하던 원본 조건과 typed builder 식별자를 사용하는 현재 조건의 차이도 같은 Slice에서 검증한다.
유지한 안전 차이:
- SDK Play callback이 pending이면 TAKE OUT 이외 명령을 보내지 않는다.
- timeout 또는
OutcomeUnknown은 재시도하지 않고 격리한다. - 결과가 확인되기 전에는 새 PROGRAM 행으로 확정하지 않는다.
- PROGRAM 중 구조 변경은 현재 단계에서 계속 차단한다.
재현하지 않은 원본 버그:
- 마지막 컷에서 이전
m_ShowIndex때문에 같은 scene을 다시 Play하는 동작 - 빈
catch로 잘못된 page 문자열과 DB/엔진 오류를 숨기는 동작 - Home/End가 timer와 색상 상태를 깨뜨리는 동작
- 고정 크기
s_buf[100]에 의존하는 Space 토글
Slice 2: 체크박스, 키보드, 이동, 삭제, drag
상태: 코드 및 자동 테스트 완료, 변경을 합친 package의 실제 마우스 검증은 아직 수행하지 않음.
원본 근거:
MainForm.cs:1078PlayList_CellClickMainForm.cs:1464btnup_ClickMainForm.cs:1579Spd_UpDownMainForm.cs:1649btndelete_ClickMainForm.cs:1730btntdelete_ClickMainForm.cs:3140checkBox1_CheckedChangedMainForm.cs:3622PlayList_DragDropMainForm.cs:3805PlayList_KeyPressMainForm.cs:3887ProcessCmdKeyMainForm.Designer.cs:1598AllowRowMove
고정한 계약:
- F8 후보, 현재 PROGRAM, Ctrl/Shift 삭제 집합과 키보드 focus를 서로 다른 상태로 유지한다.
- F8/PREPARE 성공과 disabled-row skip은 실제 F8 후보만 옮기며
bufdel에 해당하는 삭제 집합과m_newrow/m_oldrow에 해당하는 Shift anchor를 바꾸지 않는다. - 체크박스 열을 누르면 삭제 집합과 Shift anchor는 그대로 두고 그 행만 F8 후보가 된다.
- Space는 포커스된 실제 행의 현재 enabled 값을 반전하며 별도 100칸 배열을 사용하지 않는다.
- Home/End는 원본처럼 자동 갱신 timer를 먼저 멈추고 첫/마지막 행을 후보로 만들며 그 행으로 scroll한다. DEL 집합, Shift anchor, 현재 입력/ListBox focus, PROGRAM과 Page 표시는 바꾸지 않는다.
- Up/Down은 마지막 활성 행 하나만 인접 행과 교환하고 이동한 stable row identity를 후보로 유지한다.
- playlist drag도 Ctrl/Shift 집합 전체가 아니라 실제 drag source 한 행만 before/after로 이동한다.
- 선택 삭제 뒤 기존 후보가 살아 있으면 같은 row identity를 유지하고, 삭제됐으면 다음 생존 행, 다음 행이 없으면 이전 생존 행을 명시적인 후보로 만든다.
- 빈 목록에 첫 행을 추가하거나 DB playlist를 불러오면 첫 행이 화면의 후보와 실제 F8 대상이 되지만, 실제 행 클릭 전에는 DEL 집합에 암묵적으로 들어가지 않는다. 화면에 후보가 없는데 내부적으로 0번 행을 재생하던 fallback은 제거했다.
- PREPARED/PROGRAM/pending/OutcomeUnknown에서는 enabled, 이동, 삭제, row/cut drag를 Web과 native 양쪽에서 차단한다. 후보 선택과 컷 단순 선택은 로컬 상태이므로 허용한다.
재현하지 않은 원본 버그:
bufdel[100],s_buf[100]의 제한, 중복 인덱스와 stale selection- 선택 삭제가 PROGRAM 행이나 현재 송출 순서를 손상하는 동작
- 이동·삭제 뒤
m_rowidx,m_newrow,m_crow숫자 인덱스가 다른 항목을 가리키는 동작 - Home/End 뒤 focus와 후보가 서로 다른 행을 가리키는 동작
- 컷 drag payload를 무시하거나 playlist 다중 삭제 집합을 다중 행 drag로 확대하는 동작
Slice 3: PageN 문자열 분기와 저장 total
상태: 코드 및 집중 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.
원본 근거:
MainForm.cs:2120btnNext_ClickMainForm.cs:2176Next_SceneMainForm.cs:2495Show_PlayList
고정한 계약:
- 실제 operator row는 column 3
GraphicType이 정확히5단 표그래프,6종목 현재가,12종목 현재가일 때만 같은 행 Page NEXT를 허용한다. - 위 세 문자열은 각각 5·6·12개 단위와 연결하고, row의
current/total중 total을 1..20 범위에서 읽어 PREPARE부터 TAKE OUT까지 고정한다. - on-air 중 DB 결과가 늘어도 저장 total 뒤의 새 page를 노출하지 않고, 줄어든 결과로 현재/다음 page를 만들 수 없으면 vendor NEXT 전에 fail-closed한다.
- 같은 paged scene code로 해석되더라도 column 3 문자열이 정확하지 않으면 일반 행으로 취급하여 다음 enabled playlist row로 이동한다.
- paged row의 마지막 page에서 다음 playlist row로 이동한 뒤에는 원본처럼 자동 갱신을 다시 시작하지 않는다. 일반 행에서 다음 row로 이동한 경우에만 다시 시작한다.
- 요구사항의 20 page cap, 0건 fail-closed, 마지막 row에서 재생 반복 금지는 유지한다.
원본의 101/121/241건에서 21 page가 되는 off-by-one,
1/0, stale scene 재생은 명시적인 안전 교정이며 문자 그대로 재현하지 않는다. - timer refresh의 새 구현은 불완전할 수 있는
GetPlayingSceneproxy가 아니라 adapter가 보존한 on-air scene reference를 갱신한다는 실제 구현 계약으로 주석과 테스트 이름을 바로잡았다.
자동 검증:
- Core: 저장 total 고정, DB 증가/감소, refresh, 없는 다음 page 차단, 비정규 문자열의 playlist NEXT, metadata 없는 직접 Core 호출의 기존 fresh-plan 동작.
- Application: 세 정확한 문자열의 5·6·12 mapping, suffix total 고정, 유사 문자열 차단,
0/21 및 잘못된
current/total거부.
Slice 4: 종목 검색과 런타임 컷 메뉴
상태: 코드 및 자동 테스트 완료, 실제 DB/Tornado 명령은 실행하지 않음.
원본 근거:
MainForm.cs:399,MainForm.cs:596종목.ini/INI_parserMainForm.cs:844button9_ClickMainForm.cs:859searchStockMainForm.cs:3039txtSearch_KeyPressMainForm.cs:3427listView1_MouseDownMainForm.cs:3534listView1_MouseMoveMainForm.cs:3635PlayList_DragDrop
고정한 계약:
- 빈 검색 버튼은 기존 결과·종목 선택·컷 선택을 유지하고, 빈 Enter만 전체 조회를 실행한다.
- 새 조회는 종목 선택만 지우며 컷 선택은 유지한다. 이후 새 종목을 선택해 drag/drop하면 남아 있던 컷을 새 종목 identity로 추가한다.
- 컷 Ctrl/Shift, 정확한 좌표와 500ms 미만 double-click, 물리 순서 drag, 중복 drop을 유지한다.
- 일반 시장의
-구분행은 무시하고 NXT에서는 원본 경고 modal 순서를 유지한다. - CP949
종목.ini는%LOCALAPPDATA%외부 override, package 후보 순서로 읽는다. 둘 다 없거나 유효하지 않으면 검증된 31개 내장 목록으로 폴백한다. 외부 파일이 존재하지만 잘못됐으면 다른 파일로 우회하지 않고 즉시 폴백한다. - 최대 50개 물리행, 64 KiB, 정확한
[종목], 알려진 컷과 구분행만 허용하고 부분 결과와 실제 경로는 상태에 노출하지 않는다. 실제 INI 파일은 Git/MSIX에 넣지 않는다. - 런타임 권위본은 원본의
bin\Debug\Res\종목.ini이며 현재 내장 fallback과 SHA-256이 같다. 이 파일과s5001이 사용하는시간외단일가를 유지한다. 소스 트리Res\종목.ini의 서로 다른시간외종가표기는 실제 실행 기준으로 되돌리지 않는다.
Slice 5: 전역 키, 배경과 DissolveTime
상태: 코드 및 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.
원본 근거:
MainForm.cs:964MainForm_KeyDownMainForm.cs:3887ProcessCmdKeyMainForm.cs:4008btnback_ClickMainForm.cs:4037ckback_CheckedChangedMainForm.cs:488,MainForm.cs:1059ComboDi.SelectedIndex
고정한 계약:
- modal에서 처리한 Escape/F2/F3/F8은 뒤의 playout key listener로 전달하지 않는다. 이름 있는 playlist modal의 Escape는 modal만 닫고 TAKE OUT을 보내지 않는다.
- Delete는 원본 KeyPreview처럼 playlist 삭제 intent를 보내되 기본 키를 억제하지 않아, 텍스트 입력이 focus를 가진 경우 문자 삭제도 정상 동작한다.
- 시작 기본 배경은
Cuts\video\기본.vrv, F2/F3 기본 배경은Cuts\..\배경\기본.vrv로 분리하고 두 신뢰 root가 서로 대신하지 않게 했다. - 화면에 원본
DissolveTime1..20을 표시하고 C#에는 인덱스 0..19만 보낸다. 기본 인덱스는 6(화면 7)이며, PREPARE/F8 때 현재 전역 인덱스를 기존 playlist 모든 entry에 적용한다. 행 생성 시 저장된 기본 6이 전역 선택을 덮어쓰던 경로는 제거했다.
Slice 6: 탭 재진입과 dragover 교환
상태: 코드 및 자동 테스트 완료, 실제 DB 쓰기/Tornado 명령은 실행하지 않음.
원본 근거:
MainForm.cs:2937tabControl1_SelectedIndexChangedMainForm.cs:3183tab_MouseDownMainForm.cs:3214tab_MouseMoveMainForm.cs:3234tab_DragOver
고정한 계약:
- 코스피/코스닥은 재진입마다 목록을 다시 조회하고 선택·비교쌍을 초기화한다.
- 비교는 저장된 CP949 비교쌍을 매 재진입마다 다시 읽고, 해외종목·거래정지는 검색 결과, 선택과 정렬 상태를 새 UC처럼 초기화한다. MA5/MA20은 MainForm 전역 상태로 유지한다.
- 폐기된 탭의 늦은 비동기 결과는 activation generation으로 무시한다.
- 좁은 탭을 넓은 탭 오른쪽으로 옮길 때는
target.right - source.width초과, 왼쪽은target.left + source.width미만에서만 교환한다. 같거나 넓은 source는 진입 즉시 교환한다. - 같은 crossing의 반복 dragover와 busy 거부가 중복/누락 교환을 만들지 않게 했다.
Slice 7: UC1 트리 이벤트와 이동평균선 샘플링
상태: 코드 및 자동 테스트 완료, 실제 DB/Tornado 명령은 실행하지 않음.
원본 근거:
Control/UC1.cs:121-139,1303-1340,1393-1407Scene/s8010.cs:431-453,765-807MainForm.cs:2495+,2937-2999
고정한 계약:
- 루트 라벨 단일 클릭은 선택만 하며 disclosure만 펼침/접기를 수행한다.
- 루트·리프 더블클릭은 펼침 상태를 바꾸지 않고 C# action을 정확히 한 번 보낸다.
- 같은 탭의 상태 render는 open/selection/focus/scroll을 보존하고, 실제 탭 재진입은 원본 UC1처럼 Expand=true, 전체 펼침, 첫 루트 선택·focus·scroll로 초기화한다.
- Busy/고정 섹션 배치 중에는 action뿐 아니라 로컬 disclosure 변경도 차단한다.
- MA5/MA20은 행 추가 시 저장하지 않고 s8010 PREPARE, F8, NEXT, 자동 갱신의 장면 생성 시점마다 현재 전역 체크 상태를 다시 읽는다. named playlist identity에도 MA 플래그를 저장하지 않는다.
- 원본의
uc_3 == null부모 더블클릭 예외와 부분 추가 버그는 재현하지 않는 명시적 결함 제외다.
상세 근거와 결함 제외는 docs/UC1_TREE_EVENT_PARITY.md에 기록했다.
Slice 8: K3D 원시 인자, callback 게이트, 컷 ListView 경계
상태: 코드 및 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.
원본 근거:
MainForm.cs:2628-2639공통SetBackgroundVideo(path, 2004, 10)MainForm.cs의SetSceneEffectType(10, FADE, ComboDi.SelectedIndex)호출MainForm.cs:2651+,1992의 3,000,000초 Int32 곱셈/Timer.Interval 결과MainForm.cs:3427-3525,3566-3588,3635-3643컷 선택·드롭MainForm.cs:3887-3906Home/End 후 base key 처리
고정한 계약:
- K3D scene effect 첫 인자는 bool로 정규화하지 않고 원본 raw
10을 전달한다. - 공통 배경은 raw
(2004, 10), s5006 장면 내부 배경은(1, 1)을 구분한다. - OnCutOut/OnStopAll 완료 전에는 PREPARE, F8, NEXT, 갱신과 중복 TAKE OUT을 SDK에 보내지 않고 Web 버튼과 운영 데이터 mutation도 잠근다. pending Play 중 비상 TAKE OUT 1회 경로는 유지한다.
- 5076/5079/5080/5081은 원본의 unchecked Int32 overflow 뒤 Timer.Interval 예외 때문에 실제 자동 갱신이 시작되지 않으므로 34.7일 타이머로 바꾸지 않고 명시적으로 비활성화한다.
- 런타임 컷 메뉴 bridge 범위를 원본 물리행과 같은 0..49로 통일한다.
- 연속 Shift/Ctrl+Shift는 Win32 ListView selection mark를 고정하고, 빈 영역 클릭은 선택을 지우되 native anchor는 유지한다. 빈 영역 뒤 stale 컷 drop은 0행이다.
- PREPARED/PROGRAM 중 컷 pointer double-click의 암묵적 행 추가를 native/Web 양쪽에서 차단한다.
- Home/End의 playlist/timer 부작용 뒤에도 검색 입력 caret과 검색결과 기본 선택 동작을 보존한다.
- DissolveTime의 모든 외부·Core 경계는 원본 ComboBox index인 0..19만 허용한다.
Slice 9: 종목 ListBox와 컷 ListView 키보드
상태: 코드 및 자동 테스트 완료, 실제 package 키보드 검증은 아직 수행하지 않음.
고정한 계약:
- 종목 결과는 하나의 tab stop을 갖고 ArrowUp/Down, Home/End와 10행 viewport의 PageUp/PageDown 9행 이동을 재현한다. double-click도 선택만 바꾸고 컷을 추가하지 않는다.
- 종목 Home/End는 결과 선택과 focus를 보존한 채 전역 playlist 경계 부수 효과도 실행한다.
- 컷 목록은 물리행과 separator를 포함해 ArrowUp/Down, Home/End, Space를 처리한다.
- 컷 Ctrl/Shift/Ctrl+Shift는 Win32 selection mark 규칙을 C# 권위 상태에서 적용하고 키보드
선택은 마우스 전용
selectedRowsList에 섞지 않는다. drop은 native 선택 집합을 사용한다. - PREPARED/PROGRAM/pending/unknown 또는 종목 미선택 상태에서는 컷 선택과 focus 이동을 함께 차단해 DOM과 C# 상태가 어긋나지 않게 한다.
다음 대조 순서
- 컷 ListView의 drag 시작 임계값과 실제 package 키보드 회귀
- GraphE/FSell/VI/PList/AList/ThemeA/EList modal별 남은 키·focus·취소 이벤트
- 자동 갱신 UI의 실제 실행 상태 기반 통합 테스트와 종료 상태 행렬
- Debug/Release x64와 새 package에서 실제 마우스·키보드·drag 회귀
각 항목은 실제 원본 코드 근거와 회귀 테스트가 모두 생기기 전에는 완료로 판정하지 않는다.