Files
MBN_STOCK_WEBVIEW/docs/LEGACY_BEHAVIOR_PARITY_WORKLOG.md

21 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

고정한 계약:

  • 원본 child form에는 CancelButton/KeyPreview/Escape handler가 없고 ShowDialog가 MainForm을 비활성화한다. 따라서 modal의 Escape는 form을 닫지도 TAKE OUT을 보내지도 않으며, F2/F3/F8과 playlist 전역 키도 owner로 전달하지 않는다.
  • Delete는 원본 KeyPreview처럼 playlist 삭제 intent를 보내되 기본 키를 억제하지 않아, 텍스트 입력이 focus를 가진 경우 문자 삭제도 정상 동작한다.
  • 시작 기본 배경은 Cuts\video\기본.vrv, F2/F3 기본 배경은 Cuts\..\배경\기본.vrv로 분리하고 두 신뢰 root가 서로 대신하지 않게 했다.
  • 원본 DissolveTime을 소유한 panel3은 Designer에서 항상 숨겨져 있으므로 WebView selector도 hidden/disabled로 유지하고 운영자 변경 intent를 노출하지 않는다. C#의 기존 0..19 인덱스와 기본값 6은 scene effect 상태로만 보존한다.

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# 상태가 어긋나지 않게 한다.

Slice 10: modal 소유권·PList 완료 순서와 컷 drag 임계값

상태: 코드·자동 테스트·unsigned Release package-context 시작/정상 종료 완료. 실제 컷 drag 마우스 실측과 DB 쓰기/Tornado 명령은 실행하지 않음.

원본 근거:

  • Form/PList.cs:173-196, 222-237 행 double-click과 확인 버튼 save/load/close 순서
  • Form/PList.cs:184, 226, 229저장하겠습니까?, 저장되었습니다
  • Form/*Designer.cs의 child form에 없는 CancelButton, KeyPreview, Escape handler
  • MainForm.cs:3427-3459, 3534-3549, 3635-3643 컷 mouse-down rectangle, drag 시작과 체크 행 physical-order drop
  • MainForm.Designer.cs:1391-1399 숨겨진 panel3

고정한 계약:

  • named playlist, GraphE, FSell/VI, ThemeA/EList modal은 opener를 기억하고 최초 legacy control에 focus하며 Tab/Shift+Tab을 modal 안에 가둔다. 중첩 alert를 닫으면 child modal의 이전 control로, child modal을 닫으면 owner control로 focus를 돌려준다.
  • child modal의 Escape는 원본처럼 no-op으로 소비하고 MainForm TAKE OUT으로 새지 않는다.
  • PList save는 정확히 저장하겠습니까?를 묻는다. 확인 버튼 save 성공만 저장되었습니다를 표시하고, 행 double-click save는 성공 알림 없이 닫는다. No를 선택해도 원본의 unconditional Close()처럼 modal을 닫는다.
  • 비동기 DB 명령 중에는 원본 동기 ShowDialog 호출이 반환되지 않던 구간처럼 close/cancel과 중복 명령을 잠근다. 정확한 command/requestId/definition/sequence receipt만 modal을 닫으며 실패 또는 결과 불명확 상태에서는 성공 알림·추정 close를 하지 않는 안전 교정을 유지한다.
  • PList.Get_FileName_Folder가 새 DataSource를 지정할 때 non-empty ListBox의 첫 행이 자동 선택되는 동작을 열기·새로고침·추가·삭제 뒤 모두 유지한다. 다른 행 click은 즉시 시각 선택을 바꾸되 exact native selection receipt 전에는 확인·삭제를 잠그고, double-click은 같은 opaque definition id에 묶어 receipt 뒤 한 번만 실행하므로 이전 첫 행을 잘못 저장/삭제하지 않는다.
  • DB commit이 확정되고 read-back만 실패한 CommittedOptimistic은 원본처럼 저장 완료로 처리하되 warning은 유지한다. 반대로 이전 성공 outcome이 남은 채 현재 입력 검증이 실패한 경우에는 named workflow revision이 증가하지 않으므로 성공 receipt·알림·close를 만들지 않는다.
  • category tree 검색 input을 state render가 다시 만들더라도 id, caret/selection 범위와 focus를 복원해 Enter 검색 뒤 원본 textbox의 연속 입력 동작을 유지한다.
  • 컷 drag rectangle은 Point(x-SM_CXDRAG, y-SM_CYDRAG)와 Windows drag size를 사용하고 left/top 포함, right/bottom 제외 Rectangle.Contains 판정을 그대로 적용한다. 브라우저 native drag 임계값은 사용하지 않으며 pointer capture 뒤 playlist 경계 안에서 놓았을 때만 기존 DropSelectedCutsOnPlaylist를 한 번 호출한다.
  • SM_CXDRAG/SM_CYDRAG는 DPI 비인지 GetSystemMetrics 값을 그대로 쓰지 않는다. GetSystemMetricsForDpi(..., 96)로 XAML host DIP 값을 읽고 XamlRoot.RasterizationScale도 state에 싣는다. Web은 이를 window.devicePixelRatio와 대조해 과거 profile에 남은 browser zoom까지 제거한 pointer-event CSS pixel 임계값을 계산한다. 100%/150% DPI와 retained zoom을 자동 테스트하며 비정상 scale 비율은 1로 fail-safe한다.
  • WM_SETTINGCHANGE/WM_DISPLAYCHANGE/WM_DPICHANGED와 모든 state 게시 경계에서 값을 다시 읽어 시작 시 한 번만 캐시하지 않는다. cut-pointer-down 회신이 도착했을 때 아직 시작되지 않은 pointer gesture의 rectangle도 최신 값으로 다시 만든다. 이후 browser/pinch zoom 입력은 비활성화하고 API 실패나 0 이하는 축별 4 DIP로 fail-safe한다. 실제 drop 내용은 payload가 아니라 C#의 현재 선택 집합과 physical 순서가 계속 권위본이다.

다음 대조 순서

  1. 새 package에서 실제 마우스·키보드·컷/playlist drag 회귀
  2. ThemeA/EList 편집 save/delete/close의 세부 확인 문구와 focus 순서 대조
  3. 자동 갱신 UI의 실제 실행 상태 기반 통합 테스트와 종료 상태 행렬
  4. 다음 Designer handler 묶음의 원본 코드·상태 전이 대조

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