Files
MBN_STOCK_WEBVIEW/docs/LEGACY_BEHAVIOR_PARITY_WORKLOG.md

16 KiB

MBN_STOCK_N 동작 동등성 작업 기록

기준 원본은 C:\Users\MD\source\repos\MBN_STOCK_N이며 항상 읽기 전용으로 분석한다. 대상은 MBN_STOCK_WEBVIEW.LegacyParityApp이다. 화면 모양만 비슷하게 만드는 것이 아니라 원본 이벤트의 입력, 상태 전이, DB 조회, K3D 호출 순서와 화면 피드백을 코드 단위로 대조한다.

판정 절차

  1. 원본 이벤트 핸들러와 관련 필드를 추적한다.
  2. 정상 동작과 명백한 원본 버그를 구분한다.
  3. 정상 동작을 특성화 테스트로 고정한다.
  4. C# authoritative state와 WebView 표시를 함께 수정한다.
  5. 안전 게이트를 유지한 상태에서 Debug/Release x64 빌드와 전체 테스트를 실행한다.

Slice 1: 선택 행, F8, NEXT, TAKE OUT

상태: 코드 및 자동 테스트 완료, 실제 PGM 명령은 실행하지 않음.

원본 근거:

  • MainForm.cs:1078 PlayList_CellClick
  • MainForm.cs:1915 btnTake_Click
  • MainForm.cs:2020 ONAirMode
  • MainForm.cs:2081 btnTakeout_Click
  • MainForm.cs:2120 btnNext_Click
  • MainForm.cs:2176 Next_Scene
  • MainForm.cs:2495 Show_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:1078 PlayList_CellClick
  • MainForm.cs:1464 btnup_Click
  • MainForm.cs:1579 Spd_UpDown
  • MainForm.cs:1649 btndelete_Click
  • MainForm.cs:1730 btntdelete_Click
  • MainForm.cs:3140 checkBox1_CheckedChanged
  • MainForm.cs:3622 PlayList_DragDrop
  • MainForm.cs:3805 PlayList_KeyPress
  • MainForm.cs:3887 ProcessCmdKey
  • MainForm.Designer.cs:1598 AllowRowMove

고정한 계약:

  • 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:2120 btnNext_Click
  • MainForm.cs:2176 Next_Scene
  • MainForm.cs:2495 Show_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의 새 구현은 불완전할 수 있는 GetPlayingScene proxy가 아니라 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_parser
  • MainForm.cs:844 button9_Click
  • MainForm.cs:859 searchStock
  • MainForm.cs:3039 txtSearch_KeyPress
  • MainForm.cs:3427 listView1_MouseDown
  • MainForm.cs:3534 listView1_MouseMove
  • MainForm.cs:3635 PlayList_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:964 MainForm_KeyDown
  • MainForm.cs:3887 ProcessCmdKey
  • MainForm.cs:4008 btnback_Click
  • MainForm.cs:4037 ckback_CheckedChanged
  • MainForm.cs:488, MainForm.cs:1059 ComboDi.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가 서로 대신하지 않게 했다.
  • 화면에 원본 DissolveTime 1..20을 표시하고 C#에는 인덱스 0..19만 보낸다. 기본 인덱스는 6(화면 7)이며, PREPARE/F8 때 현재 전역 인덱스를 기존 playlist 모든 entry에 적용한다. 행 생성 시 저장된 기본 6이 전역 선택을 덮어쓰던 경로는 제거했다.

Slice 6: 탭 재진입과 dragover 교환

상태: 코드 및 자동 테스트 완료, 실제 DB 쓰기/Tornado 명령은 실행하지 않음.

원본 근거:

  • MainForm.cs:2937 tabControl1_SelectedIndexChanged
  • MainForm.cs:3183 tab_MouseDown
  • MainForm.cs:3214 tab_MouseMove
  • MainForm.cs:3234 tab_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-1407
  • Scene/s8010.cs:431-453, 765-807
  • MainForm.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.csSetSceneEffectType(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-3906 Home/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# 상태가 어긋나지 않게 한다.

다음 대조 순서

  1. 컷 ListView의 drag 시작 임계값과 실제 package 키보드 회귀
  2. GraphE/FSell/VI/PList/AList/ThemeA/EList modal별 남은 키·focus·취소 이벤트
  3. 자동 갱신 UI의 실제 실행 상태 기반 통합 테스트와 종료 상태 행렬
  4. Debug/Release x64와 새 package에서 실제 마우스·키보드·drag 회귀

각 항목은 실제 원본 코드 근거와 회귀 테스트가 모두 생기기 전에는 완료로 판정하지 않는다.