진행 상황
개발 과정에서 부딪힌 문제와 해결을 기록한 글, 그리고 릴리스마다 바뀐 내용입니다.
개발 기록
-
2026-10-09
Full-Precision Textures: Undoing the 4444 Cut Inside the Game's Own Driver (WIP)
pumpitea를 돌리면 곡 선택 화면의 얼굴이나 BGA의 옷 주름이 얼룩덜룩하게 뭉개져 보입니다. 에뮬레이터의 결함처럼 보이지만, 원인은 게임 실행 파일 안에 있었습니다. PIU는 OpenGL로 그리고, 그 OpenGL을 Glide로 옮기는 Mesa 3.x 드라이버를 실행 파일 안에 통째로 넣어 두었습니다. 이 드라이버가 모든 컬러 텍스처를 채널당 4bit(ARGB4444)나 5/6bit(RGB565)로 잘라 하드웨어에 넘깁니다. 원래 8bit 이미지는 그 뒤에도 게임 메모리에 그대로 남아 있습니다. 이번 작업은 그 원본을 찾아, 정확히 같은 텍스처임이 확인될 때만 대신 올리는 화질 옵션을 넣은 기록입니다.
-
2026-10-05
Post-Processing Shaders: An Arcade Monitor on Top of the Glide Frame (WIP)
원본 PIU 기판은 640×480 화면을 아케이드 CRT 모니터로 내보냈습니다. rePIU는 그 화면을 현대 모니터의 창에 2배로 그리는데, 정확할수록 오히려 "그 시절 화면"과 멀어집니다. 이번 작업은 게임이 다 그린 프레임 위에 표시 단계에서만 CRT 모니터 느낌을 입히는 후처리 shader를 넣은 기록입니다. 게스트가 보는 framebuffer는 한 바이트도 바뀌지 않습니다.
-
2026-08-12
The Four Bytes That Were Never There: A Colour Profile, a Missing Checksum, and the Wrong Suspect
pumpit8(Pump It Up: The Rebirth)은 ROM·CHD 마운트, 실행 파일 로딩, JAMMA, 보안 칩 초기화를 모두 통과한 뒤 배경 애니메이션을 읽다가 약 97초 지점에서 0xC0000005 접근 위반으로 종료됐습니다.
-
2026-08-06
The Clock That Ran at Half Speed: From a Note-Jumping Report to a One-Line Default
이 한 줄이 지목할 수 있는 층은 셋입니다. 음악이 잘못 흐르거나, 게스트의 시간이 잘못 흐르거나, 렌더가 늦거나. 네 개의 Task가 이 셋을 차례로 갈랐고, 도중에 우리가 여러 Task에 걸쳐 "게임이 멈춘다"고 불러 온 현상이 사실은 우리 계측 도구였다는 것이 밝혀졌습니다. 그리고 최종 수정은 이미 저장소 안에 있었습니다 — 새로 만든 기구는 없고, 환경 변수 하나의 기본값을 뒤집었습니다.
-
2026-07-28
Hunting the Missing Difficulty Dots: Four Wrong Answers and Two Real Bugs
MUSIC SELECT 화면의 난이도 표시 점이 실제 기판에서는 크고 선명한 붉은 원인데, rePIU에서는 몇 픽셀짜리 파란 점으로 그려졌다. 나머지 화면 — 폰트, 배경, 4개의 원판 — 은 모두 정상이었다. 이 한 가지 증상을 추적하는 데 실행 로그 다섯 번과 잘못된 결론 두 번이 필요했고, 결과적으로 서로 무관한 결함 세 개를 고쳤다.
-
2026-07-21
Glide Render Pipeline: From Black Screen to First Pixels — Work in Progress 3
지난 포스트의 주제는 "얼마나 빨리 실행되는가"였다. Native AOT 동적 번역기를 도입해 single-step trap 오버헤드를 줄이는 작업이었다.
-
2026-07-13
[WIP] rePIU 진행 상황: Native AOT 동적 번역 및 성능 향상
그동안 rePIU는 Legacy 모드에서의 기본 구동과 주변 HLE(High-Level Emulation) 환경 구축에 집중해 왔다. 하지만 원본 x86 명령어를 모두 Single-step Trap으로 처리하는 방식은 오버헤드가 매우 크다. 이를 극복하기 위해 최근 진행된 작업 브랜치에서는 Native AOT (Ahead-of-Time) 동적 번역기를 도입하고 고도화하는 데 집중했다.
-
2026-07-10
Preserving DOS4GW Execution: Work in Progress 1
이번 진행의 핵심은 원본 DOS/4G 게임 로직을 C++로 다시 쓰지 않고, 원본 32-bit x86 코드를 Win32 프로세스 안에서 직접 실행하기 위한 기반을 단계적으로 쌓은 것이다. 처음에는 PIU.EXE를 실행하지 않는 분석 도구로 MZ/LE 구조를 읽었고, 이후 LE object mapping, fixup decoding, internal relocation dry-run, relocated image buffer, Win32 process memory placement, guest stack 전환, 그리고 관측 기반 HLE trap 처리로 확장했다.
릴리스 타임라인
-
2026-10-16
v0.0.225 최신
이전 릴리스 v0.0.224 이후의 변경입니다(issue #76). 웹 페이지에서 키보드와 게임패드로 게임을 조작합니다. 키 배치는 Windows·Linux와 같습니다. 배포 빌드(Windows·Linux)의 동작은 바뀌지 않습니다.
전체 릴리스 노트
이전 릴리스 v0.0.224 이후의 변경입니다(issue #76). 웹 페이지에서 키보드와 게임패드로 게임을 조작합니다. 키 배치는 Windows·Linux와 같습니다. 배포 빌드(Windows·Linux)의 동작은 바뀌지 않습니다.
해결된 issue
issue 내용 커밋 #76 브라우저 페이지의 키보드·게임패드 입력(단계 4 단위 2의 입력 부분). 게임 안 확인은 남아 issue는 열어 둠 3f4c8e8바뀐 것
키보드. 지금까지 웹에는 입력이 없어 F5(COIN1)를 누르면 브라우저가 새로고침되었습니다.
- 게임이 도는 동안 페이지가 키를 엔진으로 보내고, 엔진은 네이티브 창의 키처럼 받습니다. 그래서 키 배치가 그대로입니다:
동전
F5, 테스트F1, 서비스F2, 클리어F3, P1QESZC, P2 키패드79513, OSDTab. - 키는 글자가 아니라 키의 자리(
KeyboardEvent.code)로 보내므로, 키보드 배열과 관계없이 같은 키가 같은 입력입니다. - 게임 키의 브라우저 기본 동작을 막습니다. F5는 새로고침하지 않고, Tab은 초점을 옮기지 않습니다. Ctrl·Meta 조합, F11(전체 화면), F12(개발자 도구)는 브라우저 몫으로 남깁니다.
- 페이지가 초점을 잃거나 숨겨지면 눌린 키를 모두 뗍니다(네이티브 창과 같음).
- 페이지에 키 안내가 있습니다.
게임패드. 브라우저가 표준 매핑으로 알아보는 패드(대부분의 XInput 패드)를 SDL 게임패드로 넘겨, 네이티브의 패드 바인딩과 조합이 그대로 적용됩니다: 동전 Back, OSD LT+RT+Y, 종료 LT+RT+L3+R3. 표준 매핑이 아닌 패드는 쓰지 않고 콘솔에 한 번 알립니다.
확인한 것
구성 확인 브라우저 headless Edge에서 F5를 눌러도 새로고침되지 않음 키 이름 표 core probe browser_key_code(범위, 이름 붙은 키, 기본 배치의 모든 키, 모르는 이름): Win32·Linux x64·i386·웹Win32 directReleasecore probe 45/45, pumpitea 40초 정상 정지 Linux x64·i386 Release core probe 47/47, 45/45 웹 ( repiu,repiu_browser,repiu_core_probe)빌드, Node core probe 37/37 CI Win32 Debug 빌드와 probe 알려진 것
- 게임 안에서 동전·OSD가 반응하는지, 게임패드가 동작하는지는 아직 브라우저에서 확인하지 못했습니다. 이 PC에서 headless 브라우저의 파일 읽기가 느려져 CHD 풀기에서 멈췄습니다(이 변경 전 빌드도 같음). 사용자 확인이 남아 #76을 열어 둡니다.
- 웹에는 아직 소리가 없습니다(다음 단위).
- Win32에서 pumpitea의 첫 텍스처 업로드에 GL 오류가 기록될 때가 있습니다. v0.0.224에도 있으며 #78에서 다룹니다.
- 게임이 도는 동안 페이지가 키를 엔진으로 보내고, 엔진은 네이티브 창의 키처럼 받습니다. 그래서 키 배치가 그대로입니다:
동전
-
2026-10-16
v0.0.224
이전 릴리스 v0.0.223 이후의 변경입니다(issue #72 후속). 브라우저 페이지를 쓰기 쉽게 했습니다. 롬을 폴더 하나로도 고를 수 있고, 시작한 뒤 무엇을 하고 있는지 페이지에서 바로 보입니다. 배포 빌드(Windows·Linux)는 첫 실행에 진행 줄이 더 나오는 것 말고는 바뀌지 않습니다.
전체 릴리스 노트
이전 릴리스 v0.0.223 이후의 변경입니다(issue #72 후속). 브라우저 페이지를 쓰기 쉽게 했습니다. 롬을 폴더 하나로도 고를 수 있고, 시작한 뒤 무엇을 하고 있는지 페이지에서 바로 보입니다. 배포 빌드(Windows·Linux)는 첫 실행에 진행 줄이 더 나오는 것 말고는 바뀌지 않습니다.
해결된 issue
issue 내용 커밋 #72 후속 롬을 폴더 하나 또는 ZIP·CHD 각각으로 고르기, CHD 풀기 진행을 페이지에 보이기 67b6e43바뀐 것
롬 고르기. 지금까지는 파일 칸 하나로 ZIP과 CHD를 함께 골라야 했는데, 둘은 보통 다른 폴더(
roms/pumpitea.zip,roms/pumpitea/010209_1821.chd)에 있어 한 번에 고를 수 없었습니다.- 폴더 하나:
roms처럼 게임 ZIP과 그 CHD가 든 폴더를 고르면 게임을 찾아 줍니다. 여러 게임이 있으면 목록에서 고릅니다. ZIP과 CHD가 한 폴더에 나란히 있어도 됩니다. 폴더의 다른 ZIP도 함께 넘겨, 부모 롬셋(pumpite.zip등)이 필요한 경우에도 됩니다. - 각각: ZIP 칸과 CHD 칸이 따로 있습니다.
- 파일은 사이트로 올라가지 않고 브라우저 안에서만 읽습니다.
진행이 보이는 페이지. 시작한 뒤 1분 넘게 아무것도 나오지 않던 것은, CHD에서 게임 파일(pumpitea는 736개, 409 MB)을 푸는 동안 엔진이 한 줄도 찍지 않았기 때문입니다.
- 엔진이 풀기 시작과 약 16 MB마다 진행률을 찍습니다. 예:
pumpitea unpacked 47% (194/736 files, 192/409 MB). Windows·Linux의 첫 실행에서도 같은 줄이 나옵니다. - 페이지는 엔진 출력을 캔버스 옆(좁은 화면에서는 아래)에 두고, 경과 시간·줄 수·마지막 줄을 계속 보여 줍니다. 로그 전체를 복사하는 버튼이 있습니다.
확인한 것
구성 확인 브라우저 사용자가 새 페이지로 게임 실행 확인. 헤드리스 Edge에서 폴더·각각 고르기와 시작, 시작 3초 안에 진행 줄 고르기 규칙 Node 시험 11/11, 이 PC의 실제 roms/에서 16개 게임Win32 directRelease빈 캐시에서 진행 줄 26개, 21초에 풀기 완료 웹 ( repiu,repiu_browser)빌드 CI Win32 Debug 빌드와 probe 알려진 것
- 웹에서는 아직 키보드·게임패드 입력과 소리가 없습니다(다음 단위). 그래서 F5(COIN1)를 누르면 브라우저가 새로고침됩니다.
- 폴더 하나:
-
2026-10-16
v0.0.221
이전 릴리스 v0.0.220 이후의 변경입니다(issue #67). 브라우저 호스트의 두 번째 단계입니다. 개발용
emulated엔진과 CPU 에뮬레이터 rex86을 WebAssembly로 빌드해, Node에서 pumpitea를 화면 없이 실행합니다. 배포 빌드(Windows·Linux)의 동작은 바뀌지 않습니다.전체 릴리스 노트
이전 릴리스 v0.0.220 이후의 변경입니다(issue #67). 브라우저 호스트의 두 번째 단계입니다. 개발용
emulated엔진과 CPU 에뮬레이터 rex86을 WebAssembly로 빌드해, Node에서 pumpitea를 화면 없이 실행합니다. 배포 빌드(Windows·Linux)의 동작은 바뀌지 않습니다.해결된 issue
issue 내용 커밋 #67 웹 이식 2B 단계 2: emulated엔진과 rex86의 Emscripten 빌드, Node 헤드리스 실행27e894d바뀐 것
웹 빌드가 게임을 실행합니다. 지금까지 웹 빌드는 게임을 실행하지 않는 공용 코어만 빌드했습니다. 이제 엔진 전체를
emulated모델로 빌드하며, 게임 코드는 rex86이 wasm 안에서 실행합니다.- 결과(pumpitea, Node): 30초 동안 약 6.1 MIPS로 돌았습니다. 같은 PC의 네이티브
emulated와 비슷한 속도입니다. HLE 경계 trace가 네이티브와 첫 벽시계 의존까지 같습니다(이미지가 놓인 주소 차이 제외). 화면은 Glide dummy, 소리는 없습니다(단계 3·4에서 화면과 소리). - 메모리: 게임의 주소를 wasm 메모리의 같은 주소에 두고, Emscripten의 데이터·스택·힙은
0x10000000위로 옮겼습니다. - 웹 플랫폼 계층: 가상 메모리(stub → 구현), 합성한 호스트 진입 상태(Windows 스레드가 보는 셀렉터 값), 스레드 없는 스레드 계층, 진단용 "없음" 답들. Glide 백엔드가 아직 쓰는 고정 기능 GL 11개는 불리면 멈추는 임시 stub입니다.
- 실행:
./scripts/build_web_wasm.sh --build-dir ~/repiu-web로 빌드하고, 저장소 루트에서node ~/repiu-web/repiu.js pumpitea로 실행합니다. - 쓰지 않게 된 실행 모델
none을 지웠습니다.
확인한 것
구성 확인 웹(Node) repiu링크, core probe 36/36, pumpitea 30초, 경계 trace 대조Win32 direct·emulatedReleasecore probe 44/44, pumpitea 실행 Linux i386·x64 (WSL) 빌드, core probe CI Win32 Debug 빌드와 probe 알려진 것
- 브라우저에서는 아직 돌지 않습니다. 다음은 Glide 렌더러를 WebGL2로 옮기는 단계 3입니다.
- 웹에서는 런처를 쓸 수 없습니다(업데이트 확인이 스레드를 씀).
emulated종료 중 힙 손상 크래시(사용자 실행 1회)는 재현되지 않아 확인 중입니다(#65).
- 결과(pumpitea, Node): 30초 동안 약 6.1 MIPS로 돌았습니다. 같은 PC의 네이티브
-
2026-10-16
v0.0.220
이전 릴리스 v0.0.219 이후의 변경입니다(issue #64, #61 후속). 브라우저 호스트의 첫 단계입니다. 브라우저의 단일 Worker에는 스레드가 없으므로, 개발용
emulated모델이 게임 코드·화면·타이머·소리·입력을 한 스레드에서 번갈아 처리하게 했습니다. 배포 빌드(Windows·Linux)의 동작은 바뀌지 않습니다.전체 릴리스 노트
이전 릴리스 v0.0.219 이후의 변경입니다(issue #64, #61 후속). 브라우저 호스트의 첫 단계입니다. 브라우저의 단일 Worker에는 스레드가 없으므로, 개발용
emulated모델이 게임 코드·화면·타이머·소리·입력을 한 스레드에서 번갈아 처리하게 했습니다. 배포 빌드(Windows·Linux)의 동작은 바뀌지 않습니다.해결된 issue
issue 내용 커밋 #64 웹 이식 2B: 브라우저 호스트 설계, 단계 1( emulated의 단일 스레드 호스트 루프)3288e17#61 후속 실행 모델 셋을 규칙·구조 문서에 반영, Linux i386 빌드 수정 3288e17바뀐 것
단일 스레드 호스트 루프. 지금까지 rePIU는 게임 코드를 도는 스레드와, 화면·타이머·창을 맡는 메인 스레드, 오디오 스레드들로 나뉘어 있었습니다.
emulated에서는 이제 메인 스레드 하나가 rex86으로 게임 코드를 조금 돌리고, 다음 타이머 tick이 되거나 2 ms가 지나면 호스트 일(창 이벤트, 화면, 타이머, 오디오 채우기)을 하는 것을 되풀이합니다.- 설계: 브라우저 호스트를 네 단계로 나눴습니다. 1) 단일 스레드 루프(이번), 2) Emscripten 빌드와 Node 헤드리스 실행, 3) Glide 렌더러를 WebGL2로, 4) 브라우저 페이지(Worker, OPFS, AudioWorklet, 입력).
- 결과(Windows, pumpitea): 사용자가 로고·오프닝 영상·음악 출력을 확인했습니다. 단계 1 전후의 HLE 경계 trace가 첫 벽시계 의존까지 같습니다.
- 처리량: 실행 명령 수는 29% 줄었지만 화면 교체 수는 같고 게임의 진행은 같거나 앞섰습니다. 줄어든 것은 게임이
기다리며 도는 루프입니다(추정). 따라서 "초당 명령 수"는
emulated의 진행 지표로 맞지 않습니다. - 공용 코드: 호스트 일 한 번(
HostTurn)을 폴링 루프에서 뽑았고, 오디오 출력 셋과 입력 스크립트를 한 단계씩 돌릴 수 있게 했습니다.direct·cache는 이것을 전과 같이 스레드에서 되풀이합니다.
#61 후속. v0.0.219부터 Linux i386 빌드가 컴파일되지 않던 것(
_mm_getcsr)을 고쳤습니다. AGENTS.md, CODING_STYLE.md, ARCHITECTURE.md가 실행 모델을 셋으로 적습니다.확인한 것
구성 확인 Win32 directReleasecore probe 44/44, pumpitea 70초 3,600프레임, 정상 종료 Win32 emulatedReleasecore probe 44/44, 경계 trace 대조, 입력 스크립트, 창 닫기·시간 제한, 사용자 확인(로고·오프닝·음악) Linux i386·x64 (WSL) 빌드, core probe CI Win32 Debug 빌드와 probe 알려진 것
emulated는 여전히 느려서, 측정한 PC에서는 타이틀까지 가지 못했습니다.emulated로 음악 뒤까지 돌린 사용자 실행 1회가 종료 중 힙 손상으로 크래시했습니다. 재현은 되지 않았고 다음 작업에서 봅니다(#65).
-
2026-10-15
v0.0.223
이전 릴리스 v0.0.222 이후의 변경입니다(issue #72). 브라우저 호스트 단계 4의 첫 단위입니다. 개발용 웹 빌드가 처음으로 브라우저 페이지에서 게임 화면을 냅니다. 배포 빌드(Windows·Linux)의 동작은 바뀌지 않습니다.
전체 릴리스 노트
이전 릴리스 v0.0.222 이후의 변경입니다(issue #72). 브라우저 호스트 단계 4의 첫 단위입니다. 개발용 웹 빌드가 처음으로 브라우저 페이지에서 게임 화면을 냅니다. 배포 빌드(Windows·Linux)의 동작은 바뀌지 않습니다.
해결된 issue
issue 내용 커밋 #72 웹 이식 2B 단계 4: 브라우저 페이지, Worker의 엔진이 WebGL2 캔버스에 그리기 59f2445바뀐 것
브라우저에서 pumpitea가 돕니다. 페이지에서 ROM ZIP과 CHD를 고르면 엔진이 페이지의 Worker에서 돌고 WebGL2 캔버스에 그립니다.
- 결과(pumpitea, Chrome): 시작 후 약 4분에 타이틀, 약 400초에 데모 플레이. 처음 약 55초는 CHD에서 게임 파일 428 MB를 메모리에 푸는 시간입니다. 소리와 입력은 아직 없습니다.
- 파일: CHD는 통째로 올리지 않고 필요한 부분만 읽습니다. 롬은 사이트에 두지 않고 사용자가 고릅니다.
- 화면: 창·GL 문맥을 다루는 부분을 경계 하나로 나눠, Windows·Linux는 SDL 창을, 브라우저는 페이지의 캔버스를 씁니다.
- 실행: 엔진은 한 스레드에서 돌며 프레임마다 브라우저에 잠깐 차례를 넘깁니다.
- 실행 방법:
./scripts/build_web_wasm.sh --build-dir ~/repiu-web --target repiu_browser로 빌드하고 그 디렉터리를python3 -m http.server로 연 뒤 브라우저에서 엽니다.
확인한 것
구성 확인 브라우저(Chrome) 타이틀·데모 플레이 화면, Worker 오류 없음 Win32 Debug·Release 렌더 장면 probe 화소 일치, 후처리·레터박스·전체 화면, core probe Win32 emulated, Linux x64·i386, 웹 Node빌드, core probe, 실행 CI Win32 Debug 빌드와 probe 알려진 것
- 소리·입력이 없습니다(다음 단위). 매번 게임 파일을 다시 풉니다(그다음 단위에서 영구 저장).
- 실시간보다 많이 느립니다. 제대로 플레이하려면 CPU 에뮬레이터의 번역 확대가 따로 필요합니다.
-
2026-10-15
v0.0.222
이전 릴리스 v0.0.221 이후의 변경입니다(issue #69). 브라우저 호스트의 세 번째 단계입니다. Glide 렌더러를 OpenGL 3.3 core와 WebGL2(OpenGL ES 3.0)가 함께 가진 기능만으로 다시 썼습니다. Windows·Linux 화면은 예전과 같고, 이제 OpenGL 3.3을 지원하는 GPU 드라이버가 필요합니다(2010년 이후의 거의 모든 GPU).
전체 릴리스 노트
이전 릴리스 v0.0.221 이후의 변경입니다(issue #69). 브라우저 호스트의 세 번째 단계입니다. Glide 렌더러를 OpenGL 3.3 core와 WebGL2(OpenGL ES 3.0)가 함께 가진 기능만으로 다시 썼습니다. Windows·Linux 화면은 예전과 같고, 이제 OpenGL 3.3을 지원하는 GPU 드라이버가 필요합니다(2010년 이후의 거의 모든 GPU).
해결된 issue
issue 내용 커밋 #69 웹 이식 2B 단계 3: Glide 렌더러를 GLES 3 / WebGL2 부분집합으로, 네이티브 GL 3.3 core로 확인 85071ca바뀐 것
렌더러가 브라우저에서 돌 수 있는 모양이 되었습니다. 지금까지 렌더러는 옛 OpenGL의 고정 기능(즉시 모드, 행렬 스택, 알파 테스트 등)을 썼고, 이것은 WebGL에 없습니다.
- 그리기: 즉시 모드를 버텍스 버퍼로, 투영·점 크기·알파 테스트를 셰이더로 옮겼습니다. 색 결합·안개 계산은 그대로입니다.
- 화면 버퍼: 게임은 화면 밖의 front/back 버퍼 두 장에 그리고, 버퍼 교체 때 완성된 그림만 창으로 옮깁니다. 브라우저가 표시 뒤 화면을 비우거나 그리다 만 프레임을 내보내도 게임 그림에 영향이 없습니다.
- 후처리(crt·scanline·사용자 셰이더): 화면을 복사하지 않고 바로 읽습니다. 사용자 셰이더 파일은 새 GLSL 문법으로 자동 변환되어 그대로 쓸 수 있습니다.
- OpenGL 문맥: Windows·Linux는 OpenGL 3.3 core를 요청합니다. 지원하지 않으면 화면 없이 실행되고 이유가 표준 오류에 찍힙니다.
확인한 것
구성 확인 렌더 probe 포팅 전 렌더러로 뜬 기준 장면 4개와 차이 0화소(Debug·Release) Win32 Release pumpitea·pumpit8 화면 전후 비교, 사용자 플레이 확인. vsync OFF 처리량 예전과 같음(약 1,100fps) Linux x64·i386 (WSL) 빌드, core probe, 4.6 / 4.5 Core Profile로 타이틀 화면 웹(Node) 고정 기능 stub 없이 링크, core probe, pumpitea 30초 CI Win32 Debug 빌드와 probe 알려진 것
- 브라우저에서는 아직 돌지 않습니다. 다음은 브라우저 페이지를 만드는 단계 4입니다.
-
2026-10-11
v0.0.218
이전 릴리스 v0.0.217 이후의 변경입니다(issue #58). PIU10 게임에서 화살표가 플레이 중 뒤로 튀거나 음악보다 늦어지던 증상을 고쳤습니다.
전체 릴리스 노트
이전 릴리스 v0.0.217 이후의 변경입니다(issue #58). PIU10 게임에서 화살표가 플레이 중 뒤로 튀거나 음악보다 늦어지던 증상을 고쳤습니다.
해결된 issue
issue 내용 커밋 #58 플레이 중 화살표가 멈추거나 뒤로 가거나 앞으로 튐: 곡 시계의 MP3 위치 보정 27677cb바뀐 것
MP3 위치를 tick 시각으로 답합니다. PIU10 게임(pumpipx3 등)은 타이머 tick으로 곡 시계를 올리고, MP3 칩의 frame-sync 비트가 바뀐 횟수로 곡 위치를 세어 256프레임(약 6.7초)마다 곡 시계를 그 위치로 맞춥니다. 화살표는 이 곡 시계를 따릅니다. 실제 기계의 타이머는 4 ms마다 고르게 돌지만, rePIU에서는 tick이 늦게 몰려 들어갑니다(Windows vsync에서는 프레임당 4개를 한꺼번에). 그러면 게임이 같은 순간의 비트를 여러 번 읽어 변화 두 개가 상쇄되고, 곡 위치를 52 ms씩 잃어 다음 보정 때 화살표가 뒤로 튀고 그 곡 내내 늦었습니다.
이제 게임이 타이머 처리 안에서 읽으면 그 tick이 원래 왔어야 할 시각의 비트로, 밖에서 읽으면 마지막으로 받은 tick 시각의 비트로 답합니다. 입력 키를 tick 시각으로 답하던 방식과 같습니다.
측정(Windows, pumpipx3, 300초) 보정 때 화살표 이동 이전, 화면 캡처를 같이 돌림 −194.6 ~ +5.4 ms 이번, 같은 조건 모두 +1.1 / −2.9 ms(tick 하나 안) 이번, 직접 플레이 8곡 47회 중 46회 +1.1 / −2.9 ms(나머지 1회의 원인이던 race도 수정) REPIU_PIU10_FRAME_SYNC_REPLAY=0이면 이전 동작입니다.진단.
REPIU_PIU10_SONG_CLOCK_LOG=1이면 보정 때마다 화살표가 몇 ms 움직였는지 찍고, 종료 보고에PIU10 song clock …와 프레임당 tick 분포PIU10 song frame ticks …가 나옵니다(꺼져 있어도 8 ms 이상은 찍힘).REPIU_TIMER_LATENCY_TRACE=<경로>는 tick이 어디서 늦는지 기록합니다.확인한 것
구성 확인 Win32 Release 빌드, core probe(새 piu10_song_clock_census,piu10_frame_sync_history포함), JAMMA timeline probe, 자동 플레이와 직접 플레이Linux x64 Release(WSL) 빌드, core probe 알려진 것
- Linux 실기와 스팀덱에서는 플레이로 확인하지 않았습니다(WSL에서는 MP3 재생이 진행되지 않음).
- tick이 프레임당 한 번 몰려 들어가는 구조 자체, 게임이 수백 ms 멈출 때 tick이 버려져 화살표가 앞으로 튀는 경우는 #59에서 이어집니다.
-
2026-10-10
v0.0.217
이전 릴리스 v0.0.216 이후의 변경입니다(issue #55). 게임패드로 OSD를 열고 조작합니다. 키보드 없는 스팀덱에서도 전체 화면, 비율, 셰이더, 텍스처 같은 게임 중 설정을 바꿀 수 있습니다.
전체 릴리스 노트
이전 릴리스 v0.0.216 이후의 변경입니다(issue #55). 게임패드로 OSD를 열고 조작합니다. 키보드 없는 스팀덱에서도 전체 화면, 비율, 셰이더, 텍스처 같은 게임 중 설정을 바꿀 수 있습니다.
해결된 issue
issue 내용 커밋 #55 게임패드로 OSD 열기와 조작(LT+RT+Y) 33eba35바뀐 것
게임패드 OSD. 한 패드에서 LT + RT + Y를 함께 누르면
Tab처럼 OSD가 열리고 닫힙니다. 열려 있는 동안은 패드로 조작합니다.패드 동작 D-pad 항목 이동 A체크박스 바꾸기, 메뉴(셰이더 등) 열기·고르기 B열린 메뉴가 있으면 메뉴만 닫고, 없으면 OSD를 닫음 LT + RT + Y OSD 닫기 OSD가 열려 있는 동안 패드 입력은 게임에 가지 않고, 열 때 눌려 있던 게임 입력은 떼어진 것으로 처리합니다. 닫을 때 누르고 있던 버튼(예: 닫으려고 누른
B= 기본 우하 발판)은 뗄 때까지 게임에 가지 않습니다. 키보드는 지금처럼 OSD가 열려 있어도 게임으로 갑니다. 스팀덱 사용 순서는 런처 업데이트 가이드에 있습니다.확인한 것
구성 확인 Linux x64·i386 Release 빌드, core probe(새 host_pad_input_osd_chord포함)Win32 CI Debug 빌드와 probe 실제 패드(Xbox Series X, Linux x64) LT+RT+Y로 두 번 열고 닫음, 패드로 비율 유지와 셰이더를 바꿈, 열린 동안 게임에 간 패드 입력 0건, 닫은 B가 게임에 새지 않음알려진 것
- 스팀덱 실기에서는 확인하지 않았습니다.
Tab으로 연 OSD도 같은 규칙이라, 열려 있는 동안에는 패드로 게임을 할 수 없습니다(키보드는 됨).
-
2026-10-10
v0.0.216
이전 릴리스 v0.0.215 이후의 변경입니다(issue #52). 게임패드만으로 게임과 런처를 끝낼 수 있습니다. 키보드도 창 닫기 버튼도 없는 스팀덱을 위한 조합입니다. v0.0.215를 쓰고 있다면 이 버전은 런처의 Update 버튼으로 받을 수 있는 첫 릴리스입니다.
전체 릴리스 노트
이전 릴리스 v0.0.215 이후의 변경입니다(issue #52). 게임패드만으로 게임과 런처를 끝낼 수 있습니다. 키보드도 창 닫기 버튼도 없는 스팀덱을 위한 조합입니다. v0.0.215를 쓰고 있다면 이 버전은 런처의 Update 버튼으로 받을 수 있는 첫 릴리스입니다.
해결된 issue
issue 내용 커밋 #52 게임패드 종료 조합 LT+RT+L3+R3 1초 8d0b378바뀐 것
게임패드 종료 조합. 한 패드에서 LT + RT + 왼쪽 스틱 누르기 + 오른쪽 스틱 누르기를 1초 동안 함께 누르고 있으면 끝냅니다.
어디서 결과 게임 중 창을 닫은 것과 같이 게임이 끝나고 런처로 돌아감(인자 실행이면 프로세스가 끝남) 런처 런처가 닫히고 rePIU가 끝남(스팀덱에서는 Steam으로 돌아감) 1초가 되기 전에 떼면 아무 일도 없습니다. 오른쪽 스틱 누르기는 기본으로
CLEAR에 묶여 있어, 조합을 누르는 동안 게임에 CLEAR가 들어갑니다. 런처 안내 문구에도 조합을 적었고, 스팀덱 설치 순서는 런처 업데이트 가이드에 있습니다.확인한 것
구성 확인 Linux x64·i386 Release 빌드, core probe(새 host_pad_input_exit_chord포함)Win32 CI Debug 빌드와 probe 실제 패드(Xbox Series X, Linux x64) 게임 중 1초 유지 → 정상 종료 후 런처 복귀, 짧게 누르기와 런처 종료는 사용자가 화면으로 확인 v0.0.215 릴리스 실행 첫 시도에 모든 잡 통과, Node.js 20 경고 없음, Linux x64 host_thread_interrupt주장 모두 참(#50, #12)알려진 것
- 스팀덱 실기에서는 확인하지 않았습니다.
- 조합을 누르는 동안 게임에 CLEAR가 들어갑니다(결정에 따른 동작).
-
2026-10-10
v0.0.215
이전 릴리스 v0.0.214 이후의 변경입니다(issue #48, #50, #12 후속). 런처가 새 릴리스를 알리고 버튼 하나로 업데이트합니다. 스팀덱처럼 릴리스를 손으로 받아 푸는 일이 번거로운 환경을 위한 기능이며, 이 버전부터 다음 릴리스를 런처에서 받을 수 있습니다.
전체 릴리스 노트
이전 릴리스 v0.0.214 이후의 변경입니다(issue #48, #50, #12 후속). 런처가 새 릴리스를 알리고 버튼 하나로 업데이트합니다. 스팀덱처럼 릴리스를 손으로 받아 푸는 일이 번거로운 환경을 위한 기능이며, 이 버전부터 다음 릴리스를 런처에서 받을 수 있습니다.
해결된 issue
issue 내용 커밋 #48 런처의 업데이트 확인과 제자리 설치 8a1cc84#50 GitHub Actions의 Node.js 20 런타임 교체 8a1cc84#12 (후속, 열린 상태) Linux x64 릴리스 CI의 간헐적 host_thread_interrupt실패 진단8a1cc84바뀐 것
런처 업데이트. 런처(인자 없이 실행한
repiu)가 열리면 GitHub의 최신 릴리스를 백그라운드에서 확인합니다. 새 버전이면 맨 위에 "rePIU v… is available."과 Update 버튼이 나오고, 게임패드로도 누를 수 있습니다. 누르면 이 빌드에 맞는 아카이브를 받아 GitHub가 알려 준 SHA-256과 같을 때만 풀고, 아카이브에 든 파일만 바꾼 뒤 런처를 다시 시작합니다.roms\,cfg\,nvram\은 그대로이고, 실패하면 기존 파일로 되돌립니다.항목 내용 받는 방법 Win32는 WinHTTP, Linux는 시스템 curl다시 시작 Linux는 같은 프로세스가 새 실행 파일로 바뀜(Steam이 계속 실행 중으로 봄), Win32는 새 런처를 자식으로 실행 설치 버튼이 나오는 곳 릴리스 아카이브를 푼 폴더(실행 파일 옆 VERSION이 실행 중인 버전과 같음)만. 빌드 트리는 알림만끄기 cfg\repiu.ini의[Launcher] check_updates = 0, 또는REPIU_UPDATE_CHECK=0스팀덱 설치, 문제 해결, 시험 절차는 런처 업데이트 가이드에 있습니다.
CI. 워크플로의 action을 Node 24 판으로 올렸습니다(
checkout@v7,cache@v6,upload-artifact@v7,download-artifact@v8). Linux x64 릴리스 잡을 간헐적으로 멈추던host_thread_interruptprobe는 일곱 가지 주장을 따로 출력하고, 대상 스레드가 두 함수를 번갈아 돌게 해 인터럽트 위치 편향만으로는 실패하지 않게 했습니다.확인한 것
구성 확인 Linux x64·i386 Release 빌드, core probe( launcher_update13개 항목 포함), 로컬과 GitHub runnerWin32 CI Debug 빌드와 probe(새 파일에서 경고 없음) 실제 업데이트(Linux x64) v0.0.212인 척한 시험 폴더에서 v0.0.213으로: 받기·검증·10개 교체·다시 시작, 다음 시작 때 옛 파일 정리, 빌드 트리에서는 설치 버튼 없음 실제 아카이브 v0.0.213의 tar.gz와 zip을 새 코드로 풀어 Python 결과와 바이트 단위로 같음 알려진 것
- 업데이트 기능은 이 버전부터 들어 있으므로, 실제로 런처에서 업데이트되는 것은 다음 릴리스부터입니다.
- Win32의 실제 교체·다시 시작과 스팀덱 실기는 확인하지 않았습니다.
- #12는 한 번의 통과로 해결을 단정하지 않고 열어 둡니다. 다음에 실패하면 새 출력이 어느 주장인지 알려 줍니다.
-
2026-10-10
v0.0.214
이전 릴리스 v0.0.213 이후의 변경입니다(issue #45, #34 후속). 게임 중 OSD에서 전체 화면과 화면 비율을 바꾸고, 그 선택이 다음 실행까지 남습니다. 런처에서는 게임패드
A로 게임을 시작할 수 있습니다.전체 릴리스 노트
이전 릴리스 v0.0.213 이후의 변경입니다(issue #45, #34 후속). 게임 중 OSD에서 전체 화면과 화면 비율을 바꾸고, 그 선택이 다음 실행까지 남습니다. 런처에서는 게임패드
A로 게임을 시작할 수 있습니다.해결된 issue
issue 내용 커밋 #45 OSD·런처의 전체 화면·비율 유지 옵션, cfg\repiu.ini에 저장f21ac2e#34 (후속) 런처 롬셋 목록에서 게임패드 A로 시작f21ac2e바뀐 것
전체 화면과 비율 유지.
TabOSD 맨 위에 "Fullscreen"과 "Keep aspect ratio"가 생겼습니다. 비율 유지는 기본으로 켜져 있어 지금처럼 4:3 그림 둘레에 검은 띠를 두고, 끄면 비율을 무시하고 창이나 화면을 꽉 채웁니다. 창 모드에도 같은 규칙이 적용됩니다.방법 지속 게임 중 TabOSD,Alt+Enter, 더블 클릭바꾸는 즉시 cfg\repiu.ini의[Video] fullscreen·keep_aspect에 저장런처 Options 맨 위의 두 체크박스 같은 키 REPIU_GLIDE_FULLSCREEN,REPIU_GLIDE_KEEP_ASPECT그 실행 (저장값보다 우선) 다음 실행은 저장한 상태로 시작합니다. 런처 창도 두 값을 따릅니다. 전체 화면으로 열리고, 런처에서는
Alt+Enter로 바꾸며, 비율 유지가 켜져 있으면 런처 화면을 가운데에 두고 둘레를 검게 둡니다. 런처 Options 순서는 Fullscreen, Keep aspect ratio, vsync, Full-precision textures, Screen shader, Sound gain입니다.런처에서 게임패드로 시작. 롬셋 목록에서 D-pad로 고른 뒤
A를 누르면 Enter처럼 시작합니다.확인한 것
구성 확인 Linux x64·i386 Release 빌드, core probe(letterbox의 늘림 사각형, 런처 설정의 두 키 포함) Win32 CI Debug 빌드(모든 타깃), repiu_glide_issue_probe,repiu_aot_probe --timer-safe-point. core·런처 probe와 실행은 하지 않음실제 실행(Linux x64, x11) OSD 전환마다 한 번씩 저장(14/14), 저장값으로 시작, 창을 닫을 때 전체 화면 해제는 저장 안 함, 런처 체크박스·창·Options 순서, 패드 A시작 — 사용자가 화면으로 확인알려진 것
- i386 빌드에서는 게임을 실행해 보지 않았습니다(빌드와 core probe만). 바뀐 코드는 아키텍처 공용입니다.
- Wayland 비디오 드라이버에서의 전환은 확인하지 않았습니다(실행은 모두 x11).
- 런처로 시작한 게임에서 OSD로 바꾼 값이 돌아온 런처 화면에 반영되는지는 따로 확인하지 않았습니다.
-
2026-10-10
v0.0.213
이전 릴리스 v0.0.212 이후의 변경입니다(issue #37). 텍스처가 게임 원본의 8bit 색으로 그려집니다. 그라데이션이 띠로 끊기던 얼굴·옷·배경이 매끄러워집니다.
전체 릴리스 노트
이전 릴리스 v0.0.212 이후의 변경입니다(issue #37). 텍스처가 게임 원본의 8bit 색으로 그려집니다. 그라데이션이 띠로 끊기던 얼굴·옷·배경이 매끄러워집니다.
해결된 issue
issue 내용 커밋 #37 게임 내장 Mesa 드라이버의 4444 절단을 우회하는 원본 정밀도 텍스처 f31b7cc바뀐 것
텍스처 원본 정밀도(기본 켬). PIU 실행 파일 안에는 OpenGL을 Glide로 옮기는 Mesa 3.x 드라이버가 들어 있고, 이 드라이버가 모든 컬러 텍스처를 채널당 4bit(ARGB4444)나 5/6bit(RGB565)로 잘라 하드웨어에 넘깁니다. rePIU는 드라이버가 들고 있는 원본 8bit 이미지를 찾아, 다시 잘랐을 때 게임이 넘긴 데이터와 바이트 단위로 같은 경우에만 대신 씁니다. 확인되지 않은 텍스처는 예전과 같이 그립니다. 게임이 보는 메모리와 동작은 바뀌지 않습니다.
실제 아케이드 기판도 4444 화면이었으므로, 이 옵션은 원본보다 나은 화질입니다. 끄면 실기와 같은 화면입니다.
방법 지속 게임 중 TabOSD의 "Full-precision textures (8-bit)"그 실행만, 바로 적용 런처 Options의 "Full-precision textures" cfg\repiu.ini의[Video] texture_full_precisionREPIU_GLIDE_TEXTURE_FULL_PRECISION=0그 실행 (런처 값보다 우선) 비교 화면과 동작 원리는 개발 기록에 있습니다.
확인한 것
구성 확인 Win32 Release 빌드, 새 probe mesa_fx_texture_source, 런처 probe, core probeLinux x64 Release 빌드, core probe 실제 게임 pumpitea 업로드 61/61, pumpit8 130/130이 원본으로 대체(실패 0), OSD 토글 정상 19개 롬셋 모두 같은 Mesa 빌드임을 실행 파일의 바이트 서명으로 확인했습니다.
알려진 것
- pumpitea·pumpit8 외 롬셋은 실행해 보지 않았습니다. 검증에 실패하는 텍스처는 예전과 같이 그려집니다.
- 원본 정밀도 텍스처마다 호스트 메모리에 사본 두 벌을 더 둡니다(최대 약 32MB). GPU 메모리는 그대로입니다.
- Linux i386 빌드는 확인하지 않았습니다.
-
2026-10-09
v0.0.212
이전 릴리스 v0.0.211 이후의 변경입니다(issue #34). 게임패드와 조이스틱(USB 발판 포함)으로 게임과 런처를 조작할 수 있습니다.
전체 릴리스 노트
이전 릴리스 v0.0.211 이후의 변경입니다(issue #34). 게임패드와 조이스틱(USB 발판 포함)으로 게임과 런처를 조작할 수 있습니다.
해결된 issue
issue 내용 커밋 #34 게임과 런처의 게임패드·조이스틱 입력 6e9cba3바뀐 것
표준 게임패드는 설정 없이 동작합니다. 먼저 연결한 패드가 1P, 두 번째가 2P입니다.
입력 게임패드 좌상 / 우상 발판 LB / RB 좌하 발판 D-pad 아래, D-pad 왼쪽 우하 발판 B, D-pad 오른쪽 가운데 발판 A, Start 코인(COIN1) Back CLEAR 오른쪽 스틱 클릭 TEST와 SERVICE는 게임 중 실수로 운영자 메뉴에 들어가지 않도록 게임패드 기본값이 없습니다(키보드 F1·F2).
배정을 바꾸거나 다른 장치를 쓰려면
cfg/<롬셋>.ini의[Input]값에 키 이름과 섞어 씁니다. 표준 매핑이 없는 장치(USB 발판 등)는 버튼·hat 번호로 겁니다.[Input] P1_CENTER = S, Pad1_A, Joy1_Button5 P1_UP_LEFT = Q, Joy1_Hat1Up장치는 연결한 순서대로 번호(
Pad1~Pad4,Joy1~Joy8)를 받고, 연결되면 stderr에[repiu-pad] Pad1 connected: <이름>이 찍힙니다. 키와 게임패드로 같은 입력을 누르면 둘 다 뗄 때까지 눌린 상태가 유지되고, 창이 포커스를 잃으면 게임패드 입력도 뗀 것으로 처리합니다. 전체 이름 목록은 설정 파일 가이드 §6.1에 있습니다.런처도 게임패드로 조작합니다. D-pad로 옮기고 A로 고릅니다.
기존 설정 파일: 입력 줄을 직접 적어 두었다면 그 줄이 게임패드 기본값까지 대신합니다. 주석 상태로 둔 줄은 새 기본값을 따릅니다. 새로 생성되는 설정 파일의 주석에는 게임패드 이름 목록이 들어갑니다.
확인한 것
구성 확인 Win32 Release 빌드, 새 probe host_pad_input, 설정 probe 94개,repiu_aot_probe전체 체인, core probe 전체Linux x64 Release 빌드, core probe 전체 실제 장치 사용자가 표준 게임패드로 게임·런처와 기본 배치를 확인 알려진 것
- 아날로그 스틱 방향과 조이스틱 축은 지원하지 않습니다.
- 발판형 조이스틱(
Joy<N>_이름)은 실제 장치로 확인하지 않았습니다. - Linux i386 빌드는 확인하지 않았습니다.
-
2026-10-09
v0.0.211
이전 릴리스 v0.0.210 이후의 변경입니다(issue #30, #8, #31). 보류해 두었던 guarded segment 끄기 스위치를 지우고, 머지되지 않은 채 사라졌던 두 수정 — Win32 Release probe 수정과
getchHLE — 을 다시 들였습니다.전체 릴리스 노트
이전 릴리스 v0.0.210 이후의 변경입니다(issue #30, #8, #31). 보류해 두었던 guarded segment 끄기 스위치를 지우고, 머지되지 않은 채 사라졌던 두 수정 — Win32 Release probe 수정과
getchHLE — 을 다시 들였습니다.해결된 issue
issue 내용 커밋 #30 guarded segment load·pop·read 끄기 스위치 삭제(환경 변수 토글 정리 보류 항목) cf3c73b#8 Win32 Release repiu_core_probe의stack_bridge_contract실패cf3c73b#31 INT 21h AH=07h/08h 에코 없는 콘솔 입력( getch)cf3c73b바뀐 것
getch로 키를 기다리는 fatal 경로가 이제 키를 받습니다. 게임의 fatal 처리기는 Watcomgetch(INT 21h AH=08h)로 키를 기다리는데, 이 서비스가 없어 그 경로에 닿으면 이름 없는 폴트로 죽었습니다.AH=07h/08h가 BIOS 키보드 버퍼를 읽고, 키가 없으면 다시 묻게 합니다. 2026-10-01 Task 764로 만들어졌지만 그 브랜치가 머지되지 않아 빠져 있던 것입니다.더 이상 읽지 않는 환경 변수 3개.
REPIU_AOT_GUARDED_SEGMENT_LOAD,_POP,_READ. 이 경로들은 dynamic 백엔드에서 항상 켜져 있습니다. v0.0.206에서 슬롯 내부를 바꾼 뒤 비상 수단으로 남겨 두었는데, v0.0.207~v0.0.210 동안 회귀가 없었습니다.Win32 Release core probe가 전부 통과합니다.
stack_bridge_contract의 거절 검사가 컴파일러가 EAX에 남긴 값에 기대고 있어 Release에서만 실패했습니다. 엔진 결함은 아니었습니다(Task 765의 수정, 같은 이유로 빠져 있던 것).새 probe.
repiu_aot_probe --dos-console-input(core probe에도 포함).개발 규칙. AGENTS.md에 "main이 아니면 새 브랜치를 만들지 않고 이어서 작업한다"와 여러 issue를 닫는 PR의 작성법을 적었습니다.
확인한 것
구성 확인 Win32 Release 빌드, core probe 전체 통과( stack_bridge포함),repiu_aot_probe전체 체인(종료 코드 0), Glide probe 2종Linux x64 Release 빌드, core probe 전체 통과 Linux i386 Release core probe 전체 통과 알려진 것
- 게임 실행은 하지 않았습니다. guarded segment는 기본값이 그대로이고,
getch는 fatal 경로에서만 닿습니다(Task 764 당시 pumpipx3에서 확인).
- 게임 실행은 하지 않았습니다. guarded segment는 기본값이 그대로이고,
-
2026-10-09
v0.0.210
이전 릴리스 v0.0.209 이후의 변경입니다(issue #25). 이미 답이 나온 조사를 위해 넣었던 일회성 진단 9개를 지웠습니다. 지운 코드는 모두 해당 변수를 켰을 때만 돌던 것이라, 아무 변수도 주지 않은 실행의 동작은 v0.0.209와 같습니다.
전체 릴리스 노트
이전 릴리스 v0.0.209 이후의 변경입니다(issue #25). 이미 답이 나온 조사를 위해 넣었던 일회성 진단 9개를 지웠습니다. 지운 코드는 모두 해당 변수를 켰을 때만 돌던 것이라, 아무 변수도 주지 않은 실행의 동작은 v0.0.209와 같습니다.
해결된 issue
issue 내용 커밋 #25 이미 답이 나온 질문을 위한 일회성 진단 삭제(환경 변수 토글 정리 후속) b24ca84바뀐 것
더 이상 읽지 않는 환경 변수 9개. 설정해도 아무 효과가 없습니다.
- Glide:
REPIU_GLIDE_CALL_AUDIT,REPIU_GLIDE_TEX_CENSUS,REPIU_GLIDE_DRAW_CENSUS,REPIU_GLIDE_TRI_CENSUS,REPIU_GLIDE_FRAME_DUMP,REPIU_DUMP_LFB_BMP,REPIU_GLIDE_VERTEX_DEPTH_CENSUS - 그 밖:
REPIU_LOWMEM_TRACE,REPIU_AOT_PROBE_GUEST
ordinal별 호출 수와 첫 인자는 종료 로그의
Glide call trace줄에, 텍스처 형식·크기 집계는 기본 텍스처 census에, 텍스처 덤프는REPIU_GLIDE_TEX_DUMP(TGA)에 그대로 있습니다.종료 로그. 위 변수를 켰을 때만 찍히던
Glide vertex depth census …와AOT runtime cache probe …줄이 없어졌습니다. 기본 실행의 출력은 바뀌지 않습니다.성능.
REPIU_DUMP_LFB_BMP는 LFB 화면을 내보낼 때마다getenv를 불렀는데, 그 호출이 없어졌습니다.남긴 것. 후보였던
REPIU_AOT_DBT_CALL_TRACE,REPIU_AOT_RETIRED_TRAP_PROFILE,REPIU_GLIDE_SETTER_CENSUS,REPIU_GLIDE_SETTER_PHASE는 살아 있는 기구를 재거나 가이드· 스크립트가 쓰므로 그대로 동작합니다.확인한 것
구성 확인 Win32 Release 빌드, repiu_aot_probe전체 체인(종료 코드 0), Glide probe 2종Linux x64 Release 빌드, core probe 전체 통과 알려진 것
- 게임 실행은 하지 않았습니다. 지운 코드는 기본 실행 경로에 닿지 않습니다.
- Linux i386 빌드는 확인하지 않았습니다.
- Win32 core probe의
stack_bridge실패는 이전부터 있던 것입니다(#8).
- Glide:
-
2026-10-09
v0.0.209
이전 릴리스 v0.0.208 이후의 변경입니다(issue #24). 진단처럼 보이는 이름 뒤에 숨어 있던 끄기 스위치 3개와, 기본으로 꺼져 있던 DOS/DPMI 실험 2개를 지웠습니다. 아무 변수도 주지 않은 실행의 동작은 v0.0.208과 같습니다.
전체 릴리스 노트
이전 릴리스 v0.0.208 이후의 변경입니다(issue #24). 진단처럼 보이는 이름 뒤에 숨어 있던 끄기 스위치 3개와, 기본으로 꺼져 있던 DOS/DPMI 실험 2개를 지웠습니다. 아무 변수도 주지 않은 실행의 동작은 v0.0.208과 같습니다.
해결된 issue
issue 내용 커밋 #24 진단 이름 뒤에 숨은 끄기 스위치와 DOS/DPMI 실험 두 개 삭제(환경 변수 토글 정리 3번 묶음) 5444d28바뀐 것
더 이상 읽지 않는 환경 변수 6개. 설정해도 아무 효과가 없습니다.
- 기능은 항상 켜져 있고 끄는 길만 없어진 것:
REPIU_GLIDE_DRAW_ENTRY_POINTS(point·AA· polygon 진입점 그리기),REPIU_TIMER_TICK_BACKLOG(밀린 timer tick 보존),REPIU_JAMMA_SNAPSHOT(입력 스냅샷). 스냅샷 주기REPIU_JAMMA_SNAPSHOT_US는 그대로 동작하며0이면 매 읽기 조회가 됩니다. - 기능째 없어진 실험:
REPIU_DOS4GW_MEMORY_PATH_PROBE(DOS 버전 응답에 PharLap 표지를 붙이던 실험),REPIU_DPMI_1E7F_PROBE_SUCCESS·REPIU_DPMI_1E7F_TRACE(INT 31h AX=1E7Fh에 가짜 성공을 돌려주던 진단 — 이 호출은 이미 고친 스택 손상에서 나온 것이었습니다).
진단 출력 형식이 바뀌었습니다. 지운 값은 기본 실행에서 항상 0이었습니다.
- 종료 로그의 tick 줄이
timer tick delivery due/injected/dropped/deferred/max-backlog/remaining으로 바뀌고(backlog-enabled·coalesced열 삭제), 게이트 줄은timer tick in-gate due: N이 됩니다. - CD 오디오 위치 census 파일에서
ticks_coalesced·ticks_coalesced_in_gate열이 빠집니다. — 이 형식을 읽는 자체 스크립트가 있다면 함께 고쳐야 합니다.
probe. v0.0.208에서 실행 시간 버킷 하나를 지운 뒤 맞지 않던
repiu_aot_probe의 기대값을 고쳐, Win32에서 probe 체인이 끝까지 돕니다.스크립트·문서. backlog 스위치 A/B 스크립트
task366을 지우고task414는 새 로그 줄에 맞췄습니다. frontier 문서의 토글 표를 현재 상태로 고쳤고, README의 텍스처 덤프 항목을 실제 변수REPIU_GLIDE_TEX_DUMP로 바로잡았습니다.확인한 것
구성 확인 Win32 Release 빌드, repiu_aot_probe전체 체인(종료 코드 0), timer tick probe, Glide probe 2종, pumpit2a 60초를 v0.0.207 릴리스와 두 쌍 교대 실행(fault 0, 둘째 쌍 프레임 3,202 / 3,203)Linux x64 Release 빌드, core probe 39개 통과 알려진 것
- Linux i386 빌드는 확인하지 않았습니다.
- Win32 core probe의
stack_bridge실패는 이전부터 있던 것입니다(#8). - 첫 번째 pumpit2a 쌍에서는 새 빌드가 순간 끊김 두 번으로 약 1초 뒤처졌지만, 둘째 쌍에서 초당 fps까지 일치해 실행 편차로 판정했습니다.
- 기능은 항상 켜져 있고 끄는 길만 없어진 것:
-
2026-10-07
v0.0.207
이전 릴리스 v0.0.206 이후의 변경입니다(issue #20). 오래전에 기본 켜기로 승격된 기능 15개의 끄기용 환경 변수를 없앴습니다. 기능은 모두 켜진 그대로이고, 아무 변수도 주지 않은 실행의 동작은 v0.0.206과 같습니다.
전체 릴리스 노트
이전 릴리스 v0.0.206 이후의 변경입니다(issue #20). 오래전에 기본 켜기로 승격된 기능 15개의 끄기용 환경 변수를 없앴습니다. 기능은 모두 켜진 그대로이고, 아무 변수도 주지 않은 실행의 동작은 v0.0.206과 같습니다.
해결된 issue
issue 내용 커밋 #20 오래 승격된 기능의 끄기 스위치 제거(환경 변수 토글 정리 1번 묶음) 8f84450바뀐 것
더 이상 읽지 않는 환경 변수 15개. 아래 변수를 설정해도 아무 효과가 없습니다. 해당 기능은 항상 켜져 있습니다.
- AOT-DBT:
REPIU_AOT_DBT_PORT_IO_DISPATCH,REPIU_AOT_DBT_RETURN_MISS_DISPATCH,REPIU_AOT_DBT_DIRECT_EDGE_DISPATCH,REPIU_AOT_DBT_TIMER_SAFE_POINTS,REPIU_AOT_INLINE_CACHE_PATCH_INLINE,REPIU_AOT_DIRECT_RETURN_TABLE(REPIU_AOT_DIRECT_RETURN_TABLE_BITS는 그대로 표 크기를 정합니다) - Glide:
REPIU_GLIDE_SETTER_ELIDE,REPIU_GLIDE_SETTER_ELIDE_TEXTURE,REPIU_GLIDE_SETTER_ELIDE_BATCH3,REPIU_GLIDE_SETTER_ELIDE_BATCH4,REPIU_GLIDE_DRAW_BATCH,REPIU_GLIDE_HOST_WAIT,REPIU_GLIDE_GATE_PUMP - 기타:
REPIU_PORT_IO_DELAY_LOOP,REPIU_NATIVE_LINEAR_SPAN_REJECT_CACHE
종료 요약 줄 두 개의 형식이 바뀌었습니다.
AOT inline cache patch direct/worker: N/0→AOT inline cache patches: N(worker 왕복 패치 경로를 지웠습니다).Glide setter elision enabled/texture-state/batch-three/batch-four/…에서 항상 참이 된 묶음 플래그 셋을 뺐습니다.
동작 변화 하나. legacy 백엔드(
REPIU_EXECUTION_BACKEND=legacy, 진단용)에서REPIU_NATIVE_LINEAR_SPAN_REJECT_CACHE=1로 reject cache를 켜던 방법이 사라졌습니다. 기본 백엔드(dynamic)는 변화가 없습니다.문서. 환경 변수 전수 조사 결과를
docs/analysis/environment-toggle-inventory.md에 두고, 보류한 항목(버려진 옵트인 실험 정리,REPIU_AOT_GUARDED_SEGMENT_LOAD/POP/READ스위치 제거)을 재개 조건과 함께docs/TODO.md에 올렸습니다.glide-setter-elision-testing.md등 가이드 3종의 A/B 절차는 종료된 기록으로 표시했습니다 — setter 생략 때문에 화면 이상이 생겨도 이제 환경 변수로 끌 수 없으니, 로그와 함께 issue로 알려 주십시오.진단 도구.
repiu_aot_probe에--inline-cache,--native-linear-span,--glide-draw-batch,--glide-setter-state-cache단독 실행 플래그를 더했습니다.확인한 것
구성 확인 Win32 x86 Release 빌드, probe 6종(위 4종과 --selector-guard·--segment-restore), 16개 롬셋 30초 스모크를 v0.0.206 빌드와 게임마다 교대(양쪽 예외 0, 15개 롬셋 프레임 일치), pumpitea 90초 한 쌍(로고 다음 공백 2.78초 / 2.79초, port I/O delay loop 통계 동일)알려진 것
- pumpitp2가 30초 스모크에서 프레임 0으로 끝난 실행이 한 번 있었습니다(예외 없음, 게스트는 대기 루프). 교대 재검에서 새 빌드 3회·이전 빌드 2회가 모두 정상이었고 v0.0.206 검증 때도 같은 현상이 한 번 있었으므로, 이번 변경과 무관한 간헐 현상으로 봅니다.
- Linux 빌드는 확인하지 않았습니다. 바뀐 코드는 플랫폼 공용 엔진 코드뿐입니다.
- AOT-DBT:
-
2026-10-07
v0.0.206
이전 릴리스 v0.0.205 이후의 변경입니다(issue #18). pumpitea의 ANDAMIRO 로고 다음 로딩 공백이 약 29초에서 약 1.5~2.7초로 줄었습니다 — 회귀 전 기준(v0.0.180, 5.0초)보다도 짧습니다. 로딩 뒤 47초 부근의 두 번째 정지(2.5~6.5초)도 1.4~2.1초로 줄었습니다.
전체 릴리스 노트
이전 릴리스 v0.0.205 이후의 변경입니다(issue #18). pumpitea의 ANDAMIRO 로고 다음 로딩 공백이 약 29초에서 약 1.5~2.7초로 줄었습니다 — 회귀 전 기준(v0.0.180, 5.0초)보다도 짧습니다. 로딩 뒤 47초 부근의 두 번째 정지(2.5~6.5초)도 1.4~2.1초로 줄었습니다.
해결된 issue
issue 내용 커밋 #18 pumpitea 로고 다음 로딩이 5초 대신 29초(ES 변경마다 캐시 전량 세그먼트 재패치) 612251c804df407a5c88cc6426c239c0620새로 들어온 것
shadow를 진실원으로 하는 세그먼트 슬롯 (#18). Win32에서 물리 세그먼트 레지스터는 게스트 selector를 절대 담지 않는다는 사실을 전용 probe(
repiu_aot_probe --segment-restore)로 확정하고, i386 guarded 세그먼트 슬롯들이 shadow selector 블록을 가상 상태의 진실원으로 쓰게 했습니다.- 로드 슬롯(
612251c): 물리 비교를 버리고 shadow·레지스터별 수용 쌍(flat + 마지막 수용된 base 0 selector, 메모리 워드 비교라 쌍 변경에 재패치 불필요)과 비교해, 쌍 일치 시 shadow를 네이티브로 씁니다. 게스트 memcpy의 ES flip(0x0024↔0x002B)이 트랩·재해석·전량 재패치 없이 돕니다. - pop·메모리-소스 로드 슬롯(
804df40): 같은 세 비교를 pop에 적용하고,mov Sreg, r/m16메모리-소스를 guarded load로 분류합니다. - INT8 캐시 진입과 무가드 read(
7a5c88c): 타이머 주입이 핸들러를 캐시 블록으로 진입시키고(REPIU_TIMER_HANDLER_CACHE_ENTRY=0으로 복원), read 슬롯은 가드 없이 shadow 워드를 읽습니다.
뜨거운 INT 21h 서비스의 호스트 직접 디스패치와 승격 (#18). 두 번째 정지의 원인은 데모 데이터 파일 I/O(~45,000호출)의 VEH 왕복이었습니다.
REPIU_AOT_DBT_SUPERBLOCK호스트 디스패치의 INT 일괄 제외에 AH=2Ch (시각)·AH=42h(lseek) 허용을 더하고(c6426c2), 16개 롬셋 매트릭스에서 8개 타이틀을 깨뜨린 AH=3Fh(read)는 제외했습니다 — read가 게스트 메모리를 바꾼 뒤 디스패처의 직접 캐시 점프가 재진입 funnel의 리타이어·격리 검사를 건너뛰기 때문입니다(39c0620). 이 증거로 토글을 Port-I/O(Task 386) 선례대로 기본 켜짐으로 승격했습니다 —REPIU_AOT_DBT_SUPERBLOCK=0으로 INT3 전용 경계를 복원합니다.진단 도구.
repiu_aot_probe에--selector-guard(selector guard probe 단독 실행)와--segment-restore(VEH 재개의 세그먼트 복원 측정) 플래그가 추가됐고, 세그먼트 로드 트레이스가 사전 상태(물리 Seg*, 갱신 전 shadow)를 함께 찍습니다.기각 기록. 재패치 경로를 싸게 만드는 세 변형(레지스터 마스크 v1·v2, 보호 범위 축소 v3)은 교대 측정에서 공백을 2~3배 늘려 기각했고, 전 과정을
docs/analysis/pumpitea-loading-segment-flip.md에 남겼습니다.확인된 것
구성 확인 Win32 x86 Release 빌드, repiu_aot_probe --selector-guard·--segment-restore, pumpitea 90초 교대 측정(공백 1.5~2.7초/정지 1.4~2.1초, 예외 0), 16개 롬셋 30초 스모크(기본 켜짐)알려진 것
- 같은 바이너리의 공백이 런에 따라 빠른/느린 영역으로 갈리는 모드 분기의 선택자는 미확정입니다(EEPROM 내용·순수 워밍업은 배제).
- pumpit3a가 로딩 중 간헐 게스트 AV(4회 중 1회꼴, EBX=0)로 종료한 사례가 있습니다 — 세그먼트 경로 타이밍 변화로 드러나는 기존 경합으로 추정하며 미해결입니다.
- far 문자열 비교 관용구(
0xFCF6D/6F)의 폴백 2,530회/90초는 남아 있습니다(시간 영향 ~0.1초). - 호스트 HLE 디스패치 승격과 새 슬롯들은 Win32에서만 측정했습니다. long-mode(x64) 슬롯은 기존 동작 그대로입니다.
- 로드 슬롯(
-
2026-10-05
v0.0.205
이전 릴리스 v0.0.204 이후의 변경입니다(issue #15). 게임 중
TabOSD가 화면 맨 위에 가로로 펼쳐지고, 이름·버전·빌드 날짜와 Target Profile을 먼저 보여 줍니다. OSD와 런처의 글자는 창을 키우면 함께 커집니다.전체 릴리스 노트
이전 릴리스 v0.0.204 이후의 변경입니다(issue #15). 게임 중
TabOSD가 화면 맨 위에 가로로 펼쳐지고, 이름·버전·빌드 날짜와 Target Profile을 먼저 보여 줍니다. OSD와 런처의 글자는 창을 키우면 함께 커집니다.해결된 issue
issue 내용 커밋 #15 re2DJ 형태의 OSD, OSD와 런처 글자가 창 크기에 비례 d44a1e5새로 들어온 것
OSD 배치 (#15). re2DJ의 OSD와 같은 형태입니다.
- 화면 맨 위에 가로 전체 폭으로 붙고, 높이는 내용만큼입니다.
- 첫 줄:
rePIU v0.0.205 (Win/x86 Release) - Build …(창 제목과 같은 표기). 둘째 줄:Target Profile : pumpit1처럼 지금 실행 중인 롬셋. 그 아래로 v0.0.204의 Renderer 절, LFB 토글, 화면 shader 메뉴가 이어집니다.
창 크기에 비례하는 글자 (#15).
- OSD의 글자와 여백이 창 높이에 맞춰 커집니다. 1배 창(640×480)에서 0.75배, 기본 2배 창에서 1.5배, 4K 전체화면에서 3.375배입니다. 전에는 창을 키워도 글자가 13 px 그대로라 상대적으로 작아졌습니다.
- 인자 없이 실행했을 때의 런처도 이제 창 크기를 바꾸거나 최대화할 수 있고, 글자·표·버튼이 함께 커집니다. 기본 크기에서는 이전과 같습니다. 런처에 항상 보이던 세로 스크롤바도 없어졌습니다.
폰트. OSD와 런처 모두 re2DJ와 같은 ProggyForever(Dear ImGui의 확대에 강한 기본 폰트)로 그립니다. 이를 위해 Dear ImGui를 v1.92.1에서 v1.92.9로 올렸습니다.
확인한 것
구성 확인 Win32 x86 Release 빌드, repiu_core_probe·repiu_aot_probe --imgui-ui-scale, pumpit1 OSD 캡처(1배·2배·4K 전체화면), 런처 캡처(기본 960×640·최대화 3840×2054)Linux x64 Debug (WSL) 빌드, repiu_core_probe전체 통과Win32 x86 Debug (CI) 빌드와 probe 알려진 것
- 새 OSD 배치는 Linux에서 화면으로 확인하지 못했습니다(같은 공용 코드이고, v0.0.204의 OSD는 Linux에서 화면으로 확인했습니다).
- 런처 기본 크기에서 긴 제목이 Status 열 경계까지 닿습니다(이전과 같음). 창을 키우면 풀립니다.
- 이전 릴리스의 "알려진 것"은 그대로입니다: #7 i386 Wayland(NVIDIA) 창 생성 실패,
#8 Win32 Release core probe의
stack_bridge_contract, #12 릴리스 CI의 Linux x64host_thread_interrupt간헐 실패.
-
2026-10-05
v0.0.204
이전 릴리스 v0.0.203 이후의 변경입니다(issue #5). 게임 중
TabOSD 맨 위에 지금 화면을 그리는 OpenGL renderer가 나오고, 소프트웨어 렌더러면 경고합니다.전체 릴리스 노트
이전 릴리스 v0.0.203 이후의 변경입니다(issue #5). 게임 중
TabOSD 맨 위에 지금 화면을 그리는 OpenGL renderer가 나오고, 소프트웨어 렌더러면 경고합니다.해결된 issue
issue 내용 커밋 #5 인게임 OSD에 OpenGL renderer 표시 7edcfa2새로 들어온 것
OSD의 Renderer 절 (#5).
Tab으로 여는 OSD 맨 위에 다음이 나옵니다.- renderer(예:
NVIDIA GeForce RTX 4090/PCIe/SSE2), vendor, OpenGL 버전. - SDL 비디오 드라이버: Windows는
windows, Linux는x11또는wayland. WSL에서 엔진이 Mesa D3D12를 골랐으면 그렇게 덧붙입니다. - llvmpipe, softpipe, swrast, GDI Generic(Windows에서 GPU 드라이버가 없을 때), WARP, SwiftShader 같은 소프트웨어 렌더러면 빨간 글씨와 "Software rendering: no 3D acceleration". 게임이 유난히 느리면 먼저 여기를 보십시오.
표시만 하며 GL 호출을 더하지 않습니다. 로그에는 기존
GL renderer:줄 옆에GL vendor/version/video driver/software: …한 줄이 더해졌습니다.확인한 것
구성 확인 Win32 x86 Release 빌드, repiu_core_probe·repiu_aot_probe --gl-renderer-identity, pumpit1에서 OSD 캡처(RTX 4090,windows)Linux x64 Debug (WSL) 빌드, repiu_core_probe전체 통과,GALLIUM_DRIVER=llvmpipe로 pumpit1을 실행해 경고 캡처(x11)Win32 x86 Debug (CI) 빌드와 probe 알려진 것
- renderer(예:
-
2026-10-05
v0.0.203
이전 릴리스 v0.0.200 이후의 변경입니다(Tasks 771–773, issue #4·#6). v0.0.201은 릴리스 빌드가 실패해 올라가지 않았고, v0.0.202는 문서 규칙만 바뀌었으므로 여기에 함께 적습니다. Linux i386이 크게 빨라졌고(pumpit1 60초 프레임 약 1,990 → 3,190), 창을 최소화해도 게임 시간이 늦어지지 않습니다. 명령줄에서 화면 shader를 고를 수 있습니다.
전체 릴리스 노트
이전 릴리스 v0.0.200 이후의 변경입니다(Tasks 771–773, issue #4·#6). v0.0.201은 릴리스 빌드가 실패해 올라가지 않았고, v0.0.202는 문서 규칙만 바뀌었으므로 여기에 함께 적습니다. Linux i386이 크게 빨라졌고(pumpit1 60초 프레임 약 1,990 → 3,190), 창을 최소화해도 게임 시간이 늦어지지 않습니다. 명령줄에서 화면 shader를 고를 수 있습니다.
해결된 issue
issue 내용 커밋 #6 direct 모델(i386): 숨겨진 창이 vsync swap을 막는 동안 타이머 tick이 버려짐 6574861#4 task를 GitHub issue로 관리하고 main 머지는 squash merge한 PR로 dcc73e5issue로 바꾸기 전의 작업: Task 771
77117bf(v0.0.201), Tasks 772–7736574861(v0.0.203에 함께 들어감).새로 들어온 것
Linux i386 Glide 호출 연결 (Task 773). Linux i386에서는 코드 캐시가
0xE8000000근처에 있어, 게임의 Glide 호출을 gate로 바로 잇는call rel32를 한 번도 쓰지 못했습니다. 그래서 Glide 호출마다 약 160 µs가 드는 경계 처리를 거쳤습니다. 32비트 명령 포인터는 2^32에서 감기므로, 이제 감은 변위를 씁니다. Windows는 캐시가 낮은 주소에 있어 원래 이 문제가 없었습니다.- pumpit1 60초: 약 1,990 → 3,190프레임(vsync 끔: 2,100 → 53,197), 게스트 스레드 CPU 99.4% → 16.3%.
- pumpit8 60초: 439 → 3,288프레임.
최소화한 창에서도 게임 시간 유지 (#6). 창이 숨겨지면 컴포지터가 vsync swap을 약 1초에 한 번으로 늦춥니다. Linux i386은 그동안 밀린 타이머 tick을 버려 게임 시간이 늦어졌습니다(12초 최소화에 4,200개 이상). 이제 swap 대기가 50 ms를 넘으면 기다리는 동안에도 tick을 전달합니다. 창이 보일 때의 동작과 프레임은 그대로입니다.
- 12초 최소화에서 버려진 tick: pumpit1 4,456 → 21, pumpitea 4,451 → 54.
- Windows는 최소화해도 swap이 막히지 않아 원래 tick을 대량으로 버리지 않았습니다. 같은 경로가 켜지지만 동작은 같습니다.
명령줄에서 화면 shader 고르기 (v0.0.201, Task 771).
repiu pumpit1 --post-shader crt(또는--post-shader=crt).REPIU_POST_SHADER와 런처 설정보다 우선합니다. 값이 없으면 오류로 끝납니다.새 스위치 (설정하지 않으면 기본 동작)
--post-shader <id>— 그 실행의 화면 shader.REPIU_GLIDE_SWAP_WAIT_TICKS=0— swap 대기 중 tick 전달을 끕니다(이전 동작).REPIU_GLIDE_SWAP_WAIT_TICK_HOLD_MS— 전달을 시작하기까지 기다리는 시간. 기본은 direct 모델(Win32, Linux i386) 50 ms, Linux x64 0 ms.
확인한 것
구성 확인 Linux i386 Release (Ubuntu 26.04, RTX 4090, x11) core probe, pumpit1·pumpit8·pumpitea 정상 실행과 12초 최소화, 켜고 끈 설정 교대 비교 Linux x64 Debug (같은 머신) 빌드, core probe, pumpit1 회귀 없음, --post-shader세 경우, 전체화면 왕복(Wayland·x11), v0.0.200 아카이브Win32 x86 Release (Windows 11) 빌드, 새 probe 두 개(core·AOT), pumpit1·pumpit8·pumpitea 정상 실행과 12초 최소화, 보류 0 ms 부하 시험(주입 3,804회, 프레임 같음) Win32 x86 Debug (CI) 빌드와 probe 알려진 것
- Linux i386 + Wayland(NVIDIA)에서 창을 만들지 못합니다(#7).
32비트 Wayland 라이브러리가 설치되어 있으면 SDL이 Wayland를 고르고 EGL surface 생성에 실패합니다. 지금은
SDL_VIDEO_DRIVER=x11로 실행하십시오. - Win32 Release core probe의
stack_bridge_contract=false는 probe 쪽 문제로 보고 #8에서 추적합니다. - 게임의 Glide 호출 중 간접 호출(약 1.5%)은 여전히 경계 처리를 거칩니다.
- v0.0.200의 "알려진 것"(Linux 창 크기 변경 직후 한 프레임, WSLg Wayland 전체화면 해제)은 그대로입니다.
-
2026-10-04
v0.0.200
이전 릴리스 v0.0.197 이후의 변경입니다(Tasks 764–770). v0.0.198과 v0.0.199는 릴리스 노트 없이 태그만 붙었으므로 여기에 함께 적습니다. 화면 후처리 shader(
crt,scanline)와 전체화면 전환이 들어왔고, 창을 어떤 모양으로 바꿔도 원래 4:3 화면을 유지합니다. vsync가 기본으로 켜지고, GitHub 릴리스에 Linux i386·x64 압축 파일이 함께 올라갑니다.전체 릴리스 노트
이전 릴리스 v0.0.197 이후의 변경입니다(Tasks 764–770). v0.0.198과 v0.0.199는 릴리스 노트 없이 태그만 붙었으므로 여기에 함께 적습니다. 화면 후처리 shader(
crt,scanline)와 전체화면 전환이 들어왔고, 창을 어떤 모양으로 바꿔도 원래 4:3 화면을 유지합니다. vsync가 기본으로 켜지고, GitHub 릴리스에 Linux i386·x64 압축 파일이 함께 올라갑니다.새로 들어온 것
화면 후처리 shader (Task 768). 버퍼 swap 직전에 표시 단계 전용 pass가 하나 돕니다. 게스트가 읽는 framebuffer는 바뀌지 않습니다.
- 내장
crt(곡률, 빔 모양 주사선, 마스크, 비네트)와scanline. libretro 단일 pass GLSL 형식의.glsl파일을shaders\에 넣으면 목록에 함께 나옵니다. - 고르는 곳: 런처 Options의 "Screen shader"(
cfg\repiu.ini에 저장),REPIU_POST_SHADER=crt(그 실행, 런처 값보다 우선), 게임 중TabOSD(그 실행만; Reload와 매개변수 슬라이더). - 기본은
none이며 이때는 GL 호출을 하나도 더하지 않습니다. - 기본 2배 창에서 주사선이 보이도록 줄의 밝기 곡선을 출력 픽셀에 맞췄습니다.
crt곡률 기본값은 0.01입니다. - 형식과 확인 절차: 후처리 shader 가이드. 개발 기록: WIP 글.
전체화면 전환과 비율 유지 (Task 769). 게임 창을 더블클릭하거나
Alt+Enter를 누르면 테두리 없는 전체화면과 창 모드를 오갑니다. 디스플레이 해상도는 바꾸지 않습니다. 창 크기를 바꾸든 전체화면이든 그림은 원래 비율을 유지하고 남는 부분은 검은 띠가 됩니다. 게스트가 읽는 값에는 띠가 섞이지 않습니다.Alt+1~Alt+4는 창 모드에서 1~4배를 고릅니다. 전에는 전체화면이 없었고, 창을 늘리면 그림이 비율 없이 늘어났습니다.vsync 기본 켜짐 (v0.0.198, Task 766).
REPIU_GLIDE_SWAP_INTERVAL이 없으면 엔진이 swap interval을 요청하지 않아, SDL의 Wayland 백엔드에서는 vsync 없이 그려졌습니다. 이제 GL 창이 생기면 항상 요청합니다 (변수 값이 유효하면 그 값, 아니면 1).REPIU_GLIDE_SWAP_INTERVAL=0또는 런처의 "Wait for vertical sync" 해제로 끕니다.Linux 릴리스 압축 파일 (v0.0.199, Task 767). 릴리스 워크플로가 Win32와 함께 Linux i386·x64 Release를 ubuntu-22.04에서 빌드해
rePIU-v<version>-linux-<arch>.tar.gz로 올립니다. C++ 런타임은 정적 링크라 libGL과 glibc 2.35 이상만 있으면 됩니다.스크린샷 (Task 770). 16개 롬셋의 화면을
docs/screenshots/에 두고 README와 프로젝트 사이트 소개에 넣었습니다. shader 비교 화면은docs/screenshots/shaders/에 있습니다.사이트와 크레딧 (Tasks 764–765). 사이트에 Pump It Up Exceed 아케이드 장식을 더했고,
LICENSE(BSD 3-Clause),CREDITS.md, 사이트 Credits 페이지가 들어왔습니다.새 스위치 (설정하지 않으면 기본 동작)
REPIU_POST_SHADER—none(기본),crt,scanline, 또는shaders\안 파일 이름.cfg\repiu.ini의[Video] post_shader— 런처가 저장합니다.
확인한 것
구성 확인 Win32 x86 Debug 빌드, repiu_aot_probe --post-shader·--glide-letterbox·--launcher,repiu_glide_render_probe --opengl-post-shader·--opengl-lfb(Intel Arc), pumpit1 15초none·crt·scanlineWin32 x86 Release 빌드, 16개 롬셋 90초씩 화면 캡처(스크린샷), pumpit8 입력 스크립트로 none·crt·scanline캡처Linux x64 Debug (WSL) 빌드, 위 probe 세 개(임시 빌드), GL probe를 x11·Wayland × d3d12·llvmpipe로, pumpit1 30초 x11에서 실제 X 입력으로 전체화면 왕복 4회 알려진 것
- Linux에서 창 크기를 바꾼 직후 한 프레임은 옛 크기 버퍼에 새 viewport로 그려질 수 있습니다. Mesa는 다음
swap에서야 버퍼 크기를 바꾸기 때문입니다. 다음 프레임부터는 맞습니다. 같은 이유로
repiu_glide_render_probe --opengl-post-shader의 letterbox 검사는 Linux에서 실패합니다(검사 쪽 문제). - WSLg Wayland에서 전체화면 해제가 느리고 일정하지 않습니다(swap 13~368번, 한 번은 600번 안에 풀리지 않음). 엔진은 해제를 정상으로 요청합니다. x11과 Windows에서는 바로 풀립니다. 실제 Wayland 데스크톱은 확인하지 못했습니다.
repiu_aot_probe와repiu_glide_render_probe는 Linux에서 빌드 대상이 아닙니다.- Linux i386 빌드와 웹 빌드는 이번에 확인하지 않았습니다.
- v0.0.197의 "알려진 것"(Win32 Release core probe의
stack_bridge_contract=false등)은 그대로입니다.
- 내장
-
2026-09-30
v0.0.197
이전 릴리스 v0.0.195 이후의 변경입니다(Tasks 761–762). v0.0.196은 릴리스 노트 없이 태그만 붙었으므로 여기에 함께 적습니다. Win32에서 pumpit8이 첫 곡을 로딩한 뒤 멈추던 것과 pumpipx3가 시작하자마자 죽던 것을 고쳤고, Glide LFB 고정밀 표시와 첫 인게임 OSD가 들어왔습니다.
전체 릴리스 노트
이전 릴리스 v0.0.195 이후의 변경입니다(Tasks 761–762). v0.0.196은 릴리스 노트 없이 태그만 붙었으므로 여기에 함께 적습니다. Win32에서 pumpit8이 첫 곡을 로딩한 뒤 멈추던 것과 pumpipx3가 시작하자마자 죽던 것을 고쳤고, Glide LFB 고정밀 표시와 첫 인게임 OSD가 들어왔습니다.
고친 것 (v0.0.197, Task 762)
Win32에서 pumpit8이 첫 곡을 로딩한 뒤 멈추던 것. v0.0.191부터 v0.0.196까지 같은 자리에서 멈췄습니다. 게임은 곡을 다 읽은 뒤 타이머 tick 카운터가 1200(5초)이 되길 기다리는데, 그 대기 루프에 tick이 들어가지 않았습니다.
- 원인. Win32(와 Linux i386)는 게스트의
iret을 그대로 실행하므로 엔진이 타이머 핸들러의 복귀를 보지 못했습니다. 핸들러가 돌아간 코드는 제자리에서 감독 없이 돌았고, 트랩이 없는 루프에 들어가면 다음 tick을 넣을 자리가 없었습니다. v0.0.190까지는 주입 프레임에 남아 있던 추적 플래그(TF)가 우연히 감독을 이어 줬고, v0.0.191(Task 736)이 그 플래그를 빼면서 끊겼습니다. 그 방식을 되살려 보니 다음 실행에서 핸들러가 끝없이 중첩돼 스택이 320 KiB 내려갔으므로, 되돌리지 않았습니다. - 수정. 주입한 타이머 프레임이 return pad(실행하면 폴트가 나는 예약 페이지)로 복귀합니다. 엔진이 그 폴트에서 원래 복귀 주소와 실행 상태를 되돌리고, 제자리 코드는 코드 캐시로 다시 들여보내며, 복귀를 본 김에 Linux x64에서 검증된 중첩 금지·차례·연쇄 규칙(Task 751)을 Win32에서도 켭니다.
- 결과 (Win32 Release, 입력 스크립트).
실행 v0.0.196 v0.0.197 pumpit8 100초 2,086프레임, 곡 로딩 뒤 멈춤, tick 9,573 / 22,771 (42%) 5,004프레임, 곡 재생 60 fps, tick 22,655 / 22,757 (99.6%) pumpit8 곡 파일 열기 시점 55초 44초 pumpitea 60초 — 2,500프레임, tick 99.5%, 51.9 kHz → 240 Hz 도달 pumpit2a 60초 — 3,125프레임, tick 99.7% pumpitpc 60초 — 3,085프레임, tick 99.5% Win32에서 pumpipx3가
Fatal error: unable to find entry point in DLL.로 죽던 것. 같은 수정으로 사라졌습니다: 30초 실행 3회 모두 완주, 스위치를 끄면 2회 모두 이전과 같이 죽습니다. 이 수정이 겨냥한 것은 아니어서, 타이머 주입 경로가 왜 그 실패를 좌우하는지는 설명하지 못했습니다.로딩이 길어 보이던 것. 버려지던 tick이 전달되면서 게임의 시계가 제 속도로 갑니다. pumpit8의 곡 파일 열기가 같은 입력에서 11초 당겨졌습니다.
새로 들어온 것 (v0.0.196, Task 761)
- Glide LFB 고정밀 표시.
grLfbWriteRegion의 24/32-bit 소스가 565로 양자화되지 않고 표시까지 원본 정밀도를 유지합니다. 게스트가 읽는 565 버퍼는 그대로입니다. 기본 켜짐,REPIU_GLIDE_LFB_HIGH_PRECISION=0|off|false로 끕니다. - 인게임 OSD.
Tab으로 열고 닫습니다(게임 입력에는 전달되지 않음). 항목은 고정밀 토글 하나이며, 바꾸면 다음 LFB 쓰기부터 적용됩니다. - PTX 헤더 조사와 버전별 자산 세대를 기록했습니다(pumpitea는 2세대, PNG/PTX 겸용).
새 스위치와 진단 (설정하지 않으면 기본 동작)
REPIU_TIMER_RETURN_PAD—0이면 주입 프레임에 실제 복귀 주소를 넣는 v0.0.196까지의 동작입니다(pumpit8이 다시 멈춥니다). 최종 보고의timer return pad pushed/returned/supervised/overflow/unmatched/abandoned/depth-max/depth-at-end줄이 복귀를 몇 번 받았는지 말합니다.REPIU_GLIDE_LFB_HIGH_PRECISION— 위 참조.- core probe
timer_return_pad,repiu_aot_probe --timer-return-pad.
확인한 것
구성 확인 Win32 x86 Release pumpit8 100초 ×2, pumpitea·pumpit2a·pumpitpc 60초, pumpipx3 30초 ×3, 스위치 끈 대조 실행 Win32 x86 Debug 빌드, core probe 전부 통과, aot probe( --timer-return-pad,--pic-timer-in-service,--jamma-input-timeline,--shutdown-recovery-policy)Linux x64 Release·Debug 빌드, core probe 전부 통과, pumpit8 50초(동작 변화 없음) Linux i386 (WSL) 빌드, core probe 전부 통과, pumpit8 70초 폴트 없음 화면과 소리는 수치로만 확인했습니다. 전 롬셋 조사는 하지 않았습니다.
알려진 것
- Win32 Release 빌드의 core probe가
stack_bridge_contract=false로 실패합니다. v0.0.195의 Release 빌드도 같아서 이 릴리스에서 생긴 것은 아닙니다. Debug 빌드는 통과합니다. - pumpipx3가 살아난 기전은 설명하지 못했습니다(위).
- Linux i386은 WSL에서 4~13 fps입니다(이전과 같음).
- 웹 빌드는 확인하지 못했습니다.
- v0.0.194의 "알려진 것"(WSL에서 음악이 느려지는 현상 등)은 그대로입니다.
- 원인. Win32(와 Linux i386)는 게스트의
-
2026-09-28
v0.0.195
이전 릴리스 v0.0.194 이후의 변경입니다(Tasks 756–760). 소스 디렉터리의 규칙을 정리한 릴리스로, 게임의 동작은 바뀌지 않습니다. OS에 의존하는 코드는 플랫폼 계층으로, 엔진의 CPU 아키텍처 차이는 실행 모델로 모았습니다. 검증하다 찾은 결함 둘(WSL에서 Linux i386 빌드가 창을 열지 못함, Win32 core probe가 중간에 abort함)을 함께 고쳤습니다.
전체 릴리스 노트
이전 릴리스 v0.0.194 이후의 변경입니다(Tasks 756–760). 소스 디렉터리의 규칙을 정리한 릴리스로, 게임의 동작은 바뀌지 않습니다. OS에 의존하는 코드는 플랫폼 계층으로, 엔진의 CPU 아키텍처 차이는 실행 모델로 모았습니다. 검증하다 찾은 결함 둘(WSL에서 Linux i386 빌드가 창을 열지 못함, Win32 core probe가 중간에 abort함)을 함께 고쳤습니다.
구조
v0.0.194 v0.0.195 플랫폼 계층 밖의 플랫폼·아키텍처 분기( src,include)162 102 그 가운데 엔진·런타임·헤더 93 33 엔진에서 아키텍처를 묻는 #if60여 개 모델·진단 파일의 #error확인 9개규칙 (Tasks 757–759)
- 엔진·런타임·HLE·도구 디렉터리에는 플랫폼·아키텍처 하위 디렉터리를 두지 않습니다.
- OS에 의존하는 코드는
src/platform/<OS>/[<아키텍처>/]에 두고, 플랫폼 계층은 엔진의 타입을 쓰지 않습니다. 플랫폼 계층 안에서는 아키텍처에 따라x86/,x64/로 나눕니다. - 엔진의 아키텍처 차이는 실행 모델입니다.
direct는 게스트의 바이트를 그대로 실행하고(Win32, Linux i386),cache는 번역한 long mode 코드 캐시를 실행하며(Linux x64),none은 게스트를 실행하지 않습니다(웹 빌드). 공용 코드는include/repiu/runtime/execution_model.h로 묻고, 모델에 따라 다른 구현은 공용 파일 옆에<이름>_direct.cpp,<이름>_cache.cpp로 둡니다. - 한 호스트에서만 만든 진단과 probe 전체를 감싼 가드는 디렉터리로 나누지 않습니다.
옮긴 것
- Linux 플랫폼 계층을 공용·
x86/·x64/로 나누고, Linux x64 공개 헤더를include/repiu/platform/linux/x64/로 옮겼습니다(Task 757). - 플랫폼 계층의
host_thread,host_time,host_error_stream,build_identity와 컴파일러 shim 헤더를 OS별로 나눴습니다(Task 758 단계 1). - MSVC 인라인 어셈블리 thunk 다섯 개와 스택 전환을 엔진에서
src/platform/win32/로 옮겼습니다. - 충돌 보고, 폴트 보고의 호스트 고유 정보, WSL 드라이버 선택, 심볼 이름, 추적 덤프, 텔레메트리의 호스트 읽기, 스레드 강제 종료를 플랫폼 계층의 함수로 떼어 냈습니다.
- 실행 트램펄린의 x86-64 전용 742줄(반환 resolver, 캐시 진입, 반환 추적)을
execution_trampoline_cache.cpp로, x86 전용 진입을execution_trampoline_direct.cpp로 옮겼습니다.
고친 것
WSL에서 Linux i386 빌드가 창을 열지 못하던 것 (Task 760) — v0.0.194의 D3D12 드라이버 선택이 드라이버 파일을 아키텍처와 무관한 목록에서 찾아, i386 프로세스가 x86-64용 드라이버를 보고
GALLIUM_DRIVER=d3d12를 설정했습니다. 이제 프로세스의 아키텍처에 맞는 드라이버가 있을 때만 고릅니다.Win32 core probe가 중간에 끝나던 것 (Task 758) —
dos_console_deviceprobe가CON:경로에서filesystem_error를 던져 core probe가 종료 코드 3으로 끝나고 그 뒤의 probe가 돌지 않았습니다.그 밖
- GitHub Pages 프로젝트 사이트(한국어·영어)를 더했습니다(Task 756).
확인한 것
구성 확인 Linux x64 Release·Debug, Linux i386 빌드. core probe 결과 줄이 디렉터리 정리 전과 같음 Win32 x86 Debug 빌드. core probe, aot probe 18개 모드, glide issue probe의 결과가 디렉터리 정리 전과 같음 실행(짧게) Linux x64 pumpit8·pumpitpc·pumpit1, Linux i386 pumpit1, Win32 pumpit8: 폴트 없음 알려진 것
- 웹 빌드는 확인하지 못했습니다. 이 릴리스를 만든 머신에 빌드 도구가 없습니다.
- 엔진에 Win32 코드가 남아 있습니다. 실행 트램펄린의 Win32 스레드 프로시저(SEH), 예외 진입, 종료 복구의 vectored handler입니다. 옮기려면 Win32가 폴트를 받는 방식을 바꿔야 하므로 별도 작업으로 두었습니다.
- 도구와 probe의 플랫폼 분기 69개는 그대로입니다. 약 30개는 probe 전체가 Win32에서만 도는 가드입니다.
- 호출하는 곳이 없는 함수 둘(
PushX87Float,CaptureSuspendedThreadSnapshot)을 남겨 두었습니다. - v0.0.194의 "알려진 것"(WSL에서 음악이 느려지는 현상 등)은 그대로입니다.
-
2026-09-27
v0.0.194
이전 릴리스 v0.0.193 이후의 변경입니다(Tasks 751–754). Linux x64에서 디스크 이미지가 있는 16개 롬셋이 모두 실행되고, pumpit8의 노트가 음악과 어긋나 뒤로 돌아가던 증상이 사라졌습니다. 엔진이 게스트의
cli를 지키고, WSL에서 GPU로 그리며, 입력 기록을 핸들러의 복귀에서 끝내고, 타이머 tick이 오디오와 같은 시계를 따릅니다. 노트 증상은 사용자가 WSLg(wayland)에서 플레이로 확인했습니다.전체 릴리스 노트
이전 릴리스 v0.0.193 이후의 변경입니다(Tasks 751–754). Linux x64에서 디스크 이미지가 있는 16개 롬셋이 모두 실행되고, pumpit8의 노트가 음악과 어긋나 뒤로 돌아가던 증상이 사라졌습니다. 엔진이 게스트의
cli를 지키고, WSL에서 GPU로 그리며, 입력 기록을 핸들러의 복귀에서 끝내고, 타이머 tick이 오디오와 같은 시계를 따릅니다. 노트 증상은 사용자가 WSLg(wayland)에서 플레이로 확인했습니다.숫자로 (WSL Linux x64 Release)
v0.0.193 v0.0.194 60초를 완주하는 롬셋(디스크 이미지가 있는 16개 중) 15 (pumpitpc가 5초에 죽음) 16 시작 직후 tick 폭주(초당 18,000 tick, 프레임 0장) 6회 중 2회 32회 중 0회 pumpit8 선택 화면 fps 36~42 60 pumpit8·pumpitp2 마지막 20초 fps 중앙값 39 60 pumpit8에서 게스트가 읽은 누름(입력 스크립트는 139회) 1,389~2,406 139 pumpit8 플레이의 tick 속도(오디오의 시계로, 240이어야 함) 241~264 239.5~240.7 고친 것
게스트의
cli를 지킵니다 (Task 751) — user mode에서는 IF를 내릴 수 없어, 게임이cli…sti로 감싼 구간 한가운데에도 타이머 tick이 주입됐습니다. pumpitpc는 보안 칩(CAT702) 통신 도중 핸들러가 보드의 레지스터를 바꿔 부팅 검사가 실패했습니다. 이제cli가 세운 hold가sti나 주입 프레임의iret까지 주입을 미룹니다(100 ms 밸브). 주입의 규칙도 정리했습니다: 핸들러 반환 직후의 연쇄는 3개·주기 1 ms 이상, 중첩은 한 단계, 직전 핸들러가 쓴 시간만큼 인터럽트된 코드에 차례를 줍니다. 시작 직후의 tick 폭주가 이 규칙으로 사라졌습니다.aam·aad에뮬레이션 (Task 751) — long mode에 없는 명령입니다. pumpitpc가 첫 점수 표시에서 죽던 원인이었습니다.페이서가 늦은 프레임을 한 주기 더 붙잡지 않습니다 (Task 752) — 재동기 조건이 이 프레임 자신의 마감과 비교하고 있어, 1 ms 늦은 프레임이 33 ms 프레임이 됐습니다. 이제 한 주기 넘게 늦었을 때만 재동기하고 늦은 프레임은 자지 않고 지나갑니다.
WSL에서 GPU로 그립니다 (Task 752) — WSL은 GPU를 Mesa의 D3D12 드라이버로만 내주는데 Mesa는 스스로 llvmpipe를 고릅니다.
/dev/dxg와d3d12_dri.so가 있고 사용자가 드라이버를 직접 고르지 않았으면 엔진이GALLIUM_DRIVER=d3d12를 설정합니다.입력 기록을 핸들러의 복귀에서 끝냅니다 (Task 753) — tick을 주입할 때 쌓는 입력 replay 프레임이 스택 비교로만 회수돼, 게스트가 다시 올라오지 않는 스택 높이에서 주입된 tick의 프레임이 끝까지 남았습니다. 그 뒤로 핸들러 밖의 읽기는 옛 시각의 키로 답을 받았습니다(SERVICE 누름을 초당 60회 읽음). 이제 핸들러의
iret에서 끝내고, 복귀를 볼 수 없는 Win32는 이후 64회 넘는 주입으로 회수합니다.타이머 tick이 오디오의 시계를 따릅니다 (Task 754) — tick 스케줄만 SDL의 시계(Linux의
CLOCK_MONOTONIC_RAW)를 쓰고 오디오와 나머지는CLOCK_MONOTONIC을 썼습니다. WSL2가CLOCK_MONOTONIC을 1~7% 늦추는 동안 게스트의 시간이 음악보다 최대 9% 빨리 가서, 노트가 앞서 나갔다가 뒤로 돌아왔습니다. 이제 tick 스케줄과 입력 타임라인이steady_clock을 따르고, 두 시계가 어긋나면 엔진이 알립니다.새 스위치와 진단 (설정하지 않으면 기본 동작)
변수 / 출력 내용 REPIU_GUEST_CLI_HOLD=0게스트 clihold를 끔guest cli hold cli/sti/blocked/expired/max-hold-us/held-at-end(최종 보고)hold의 횟수와 가장 긴 hold timer IRQ0 after-return …,timer IRQ0 nesting …,timer IRQ0 turn …(최종 보고)주입 규칙별로 미룬 횟수 REPIU_PIU10_CAT702_TRACE=1CAT702 통신마다 [repiu-cat702] #N bits= in= out=[repiu-linexe] get-proc miss name=…Glide 이름 조회 실패 REPIU_WSL_D3D12=0WSL에서 Mesa의 선택을 그대로 둠 [repiu-glide] GL renderer: …,Glide GL renderer/wsl-d3d12-chosen(최종 보고)무엇이 그리는지 REPIU_GLIDE_LONG_FRAME_LOG=2모든 프레임을 sleep/guest/present로 찍음 REPIU_JAMMA_REPLAY_FRAME_END=0입력 프레임을 복귀로 끝내지 않음(스택 비교와 나이만) REPIU_JAMMA_TIMELINE_TRACE=1나이로 회수되는 입력 프레임마다 ESP와 주입 당시 EIP JAMMA timeline frames ended-by-return/retired-stale/latest-state-reads(최종 보고)입력 프레임이 회수된 경로 REPIU_EVENT_CLOCK=sdltick 스케줄이 이전처럼 SDL의 시계를 따름 [repiu-clock] …,host clock raw-against-steady …(최종 보고)두 시계의 어긋남(1초 창, 0.5% 기준) scripts/survey_romsets.sh <binary> <tag> [seconds] [profile…]롬셋별 60초 실행 요약( build/survey/<tag>/summary.txt)scripts/input_scripts/pumpit8_play.txt,survey_generic.txtpumpit8을 곡까지 모는 입력, 조사용 공통 입력 알려진 것
- WSL에서 음악이 느려졌다 돌아옵니다. 이 머신의 WSL은
CLOCK_MONOTONIC을 실제 시간보다 1~7% 늦추고 있고(Windows 성능 카운터와 대조, 누가 늦추는지는 미확인), 오디오는 그 시계를 따라 소비됩니다. WSL을 다시 시작해도 남았습니다. 엔진 밖의 일로 보고 있으며 실기(실제 Linux 머신)에서의 확인이 후속 작업입니다. 노트와 음악은 서로 맞습니다. - WSL에서 잰 fps와 tick 속도는
CLOCK_MONOTONIC으로 잰 값이라, 시계가 늦춰진 동안에는 1~9% 높게 나옵니다. 최종 보고의host clock …줄을 먼저 보십시오. - Win32의 pumpitp3·pumpipx3가 간헐적으로
Fatal error: unable to find entry point in DLL.로 죽습니다. CAT702 통신은 정상이고 원인은 미확정입니다. - 6개 프로필(pumpit2, pumpit3, pumpite, pumpitp3a, pumpipx3a, pumpipx3b)은 디스크 이미지(CHD)가 없어 실행할 수 없습니다.
- INT 21h AH=08h(
getch)가 없습니다. 게임의 오류 화면 뒤에서만 닿습니다. - Win32는 게스트의
iret을 볼 수 없어 중첩·차례 규칙이 꺼져 있고, 입력 프레임은 나이로 회수됩니다(약 0.27초까지 옛 시각의 읽기가 남을 수 있음). Tasks 752~754는 Win32에서 빌드와 probe만 확인했습니다. - 예산 만료 뒤 teardown segfault는 그대로입니다. pumpit8 곡 선택에서 미리듣기 로드마다 게스트가 0.5초 멈춥니다. 플레이 중 present가 23~43 ms인 프레임이 가끔 있습니다(호스트 표시 쪽, 미조사).
- Tasks 748~752의 입력 스크립트 실행은 Task 753의 결함 아래에서 돌았습니다(게스트가 받은 입력이 스크립트보다 많았음).
- WSL에서 음악이 느려졌다 돌아옵니다. 이 머신의 WSL은
-
2026-09-27
v0.0.193
이전 릴리스 v0.0.192 이후의 변경입니다(Tasks 745–750). Linux x64에서 vsync를 켜도 노트와 BGA가 멈칫하지 않습니다. 드라이버가 vsync를 거부하면 엔진이 주사율에 맞춰 swap을 직접 맞추고, MP3 재생 시계는 장치의 pull 지터에 흔들리지 않으며, swap을 기다리는 동안에도 타이머 ISR이 돕니다. 인자를 주고 실행할 때도
cfg/repiu.ini가 적용됩니다. 사용자가 WSLg(wayland, 진짜 vsync)에서 플레이로 확인했습니다.전체 릴리스 노트
이전 릴리스 v0.0.192 이후의 변경입니다(Tasks 745–750). Linux x64에서 vsync를 켜도 노트와 BGA가 멈칫하지 않습니다. 드라이버가 vsync를 거부하면 엔진이 주사율에 맞춰 swap을 직접 맞추고, MP3 재생 시계는 장치의 pull 지터에 흔들리지 않으며, swap을 기다리는 동안에도 타이머 ISR이 돕니다. 인자를 주고 실행할 때도
cfg/repiu.ini가 적용됩니다. 사용자가 WSLg(wayland, 진짜 vsync)에서 플레이로 확인했습니다.숫자로 (pumpitea 곡 구간, 곡 중 입력 있는 62초 플레이, WSL Linux x64 Release)
v0.0.192 v0.0.193 swap_interval = 1의 fps (llvmpipe가 vsync 거부)58~98 59.9~60.1 MP3 frame-sync 토글 간격(26.1 ms여야 함) 16~35 ms 24~27 ms (98%) 게스트가 토글을 보는 지연, 60 Hz 대기에서 0~16 ms 균등 0~4 ms 게스트의 곡 위치 걸음, 60 Hz 대기에서 16 ms / 33 ms 쌍봉 25 ms 중심 (21~31) 페이싱 attract 14초의 tick 폐기 936 62 25 ms 초과 프레임(곡 구간) — 0 vsync off의 자유 실행은 이전에도 관측 지연 0~7 ms였고 이번에도 같습니다. 달라진 것은 vsync를 켠 실행이 그와 같아졌다는 점입니다.
고친 것
드라이버가 vsync를 거부할 때의 swap 페이싱 (Task 745) — WSLg의 llvmpipe GL은
SDL_GL_SetSwapInterval(1)을 거부합니다("That operation is not supported"). 거부되면 엔진이grBufferSwap뒤에서 디스플레이 주사율 기준 마감까지 기다립니다. 마감은 누적되어 평균 속도가 정확하고, 한 주기 이상 밀리면 다시 맞춥니다. 최종 보고의Glide swap pacing …두 줄이 활성 여부·주기·거부 사유와 지터(late, resyncs, oversleep)를 말합니다.인자 실행도
cfg/repiu.ini를 읽습니다 (Task 746) —repiu pumpitea처럼 런처를 거치지 않는 실행도 같은 파일을 먼저 읽어 적용합니다. 같은 뜻의 환경 변수가 있으면 환경 변수가 이깁니다. 로그의Launcher settings read from …줄이 값의 출처를 말합니다.HLE 재진입 뒤의 tick 주입 (Task 747) — Linux x64는 ISR의
iret·out·cli를 처리한 뒤 밀린 tick을 주입하지 않아, 긴 게이트 뒤에 프레임당 두 개만 빠지고 backlog(64)가 넘쳤습니다. legacy 체인과 같은 주입 호출을 넣어iret직후 중첩 없이 빠집니다.MP3 재생 시계가 자유 진행합니다 (Task 749) — 이전 시계는 장치가 768프레임을 가져갈 때마다 선을 직전 계단에서 다시 그려, pull 시각의 몇 ms 지터가 12 ms 점프와 정지가 됐습니다. 이제 자기 이전 값에서 PCM 속도로 진행하고, 가져간 count에서 장치 버퍼 절반을 뺀 목표까지 4초 시정수(±2%)로 맞춥니다. 가져간 count를 넘지 않고 뒤로 가지 않는 불변식은 그대로입니다.
swap을 기다리는 동안에도 타이머 tick (Task 750, Linux x64) —
grBufferSwap게이트 안에서는 게스트 코드가 돌지 않아 타이머 ISR도 멈췄습니다. 이제 게이트가 present를 호스트 스레드에 넘기고 기다리면서, 밀린 tick을 "게이트에 도달한call직전에 온 인터럽트"로 주입합니다. ISR의iret가call을 다시 실행해 게이트로 돌아오고, present가 끝났을 때만 게이트가 반환합니다. 실제 기계가 vblank 대기 중에도 IRQ0을 받는 것과 같습니다. tick을 무장하는 코드도 호스트 폴 루프 전용에서 게스트 대기 루프가 함께 부르는 호출로 바뀌었습니다.새 스위치와 진단 (설정하지 않으면 기본 동작)
변수 / 출력 내용 Glide swap pacing active/refresh-hz/period-us/paced-swaps/slept-ms/refusal(최종 보고)페이싱 활성 여부와 거부 사유 Glide swap pacing late-swaps/late-max-us/resyncs/oversleep>1ms/oversleep-max-us(최종 보고)페이서 지터 Glide swap wait ticks swaps/injections(최종 보고)대기 중 tick을 전달한 swap 수와 주입 수 REPIU_GLIDE_SWAP_WAIT_TICKS=0대기 중 tick 전달을 끔(Linux x64 전용 기능, 기본 켜짐) REPIU_GLIDE_SWAP_WAIT_LOG=1tick을 전달하지 못한 대기를 처음 몇 번 찍음 REPIU_PIU10_MP3_POSITION_TRACE=1토글마다, 게스트가 새 값을 본 읽기마다 [repiu-mp3-pos] toggle|seen …REPIU_GLIDE_LONG_FRAME_LOG=1(큰 값은 µs 문턱)주기 1.5배를 넘긴 프레임을 sleep/guest/present로 분해 REPIU_INPUT_SCRIPT=<파일><ms> <키 이름> [hold ms]줄을 SDL 키 이벤트로 넣는 스크립트 키보드. 예:scripts/input_scripts/pumpitea_play.txt,pumpitea_play_steps.txtREPIU_DOS_ASSET_TRACE=all자산 트레이스의 상한(열기 200·읽기 120·seek 120)을 없앰 알려진 것
- wayland 드라이버가 진짜 vsync를 받습니다. WSLg는 소켓이
/mnt/wslg/runtime-dir에 있으므로XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir SDL_VIDEO_DRIVER=wayland로 실행합니다. 이전 기록의 "wayland 드라이버는 창을 못 연다"는 이 경로 문제였습니다. - wayland로 시작할 때 6회 중 2회, 4초부터 tick 주입이 초당 18,000으로 치솟다가 13초에 게스트 스택이 넘쳐 죽었습니다. 최근 실행에서는 나오지 않았고 원인은 조사하지 않았습니다.
- 대기 중 tick 전달은 Linux x64 전용입니다. Win32에서 켜면 첫 주입 뒤 폴트 없이 멈춰 제공하지 않습니다. Win32의 vsync도 같은 게이트 대기를 하므로 같은 관측 지연이 있을 것입니다.
- Win32 pumpitea는 15초에 tick 380개를 버립니다(vsync on/off 같음). 이번 변경과 무관하며 미조사입니다.
- 입력 없는 플레이는 곡 14초쯤 게이지가 비어 실패 연출로 들어가고, 그때의 BGA 로드(
HEYMAN.DAT등)가 80~340 ms 프레임을 만듭니다. 계측할 때 곡 중 멈칫으로 오인하지 않도록 입력 스크립트를 쓰십시오. - 호출부가
call rel32가 아닌 게임은 대기 중 tick을 받지 못하고 이전처럼 기다립니다.
- wayland 드라이버가 진짜 vsync를 받습니다. WSLg는 소켓이
-
2026-09-27
v0.0.192
이전 릴리스 v0.0.191 이후의 변경입니다(Tasks 741–744). Linux x64에서 trap 한 번의 비용을 41% 줄이고, pumpitea의 breakpoint 예외를 초당 38,000회에서 약 30,000회로 줄였습니다. 어느 guest 지점이 trap을 내는지 세는 census가 항상 켜져 있고, 번역되지 않은 블록으로 돌아오는
ret이 이름 없는SIGTRAP으로 죽던 잠복 결함을 고쳤습니다. 이 릴리스는 Linux x64 엔진 내부의 변경이며 게임 동작은 바뀌지 않습니다(Windows 빌드는 이번 변경에 영향받는 코드가 없거나 원본 바이트 복사로 동일).전체 릴리스 노트
이전 릴리스 v0.0.191 이후의 변경입니다(Tasks 741–744). Linux x64에서 trap 한 번의 비용을 41% 줄이고, pumpitea의 breakpoint 예외를 초당 38,000회에서 약 30,000회로 줄였습니다. 어느 guest 지점이 trap을 내는지 세는 census가 항상 켜져 있고, 번역되지 않은 블록으로 돌아오는
ret이 이름 없는SIGTRAP으로 죽던 잠복 결함을 고쳤습니다. 이 릴리스는 Linux x64 엔진 내부의 변경이며 게임 동작은 바뀌지 않습니다(Windows 빌드는 이번 변경에 영향받는 코드가 없거나 원본 바이트 복사로 동일).숫자로 (pumpitea attract 27~30초, WSL Linux x64 Release)
v0.0.191 v0.0.192 breakpoint 예외 1회의 VEH 비용 15.2k cycle 8.9k cycle VEH의 벽시계 비중 14.3% 6.7~7.3% breakpoint 예외 수 1.02M~1.22M 0.88M~0.97M memcpy helper push es·mov eax,ds53,914 × 2 0 jmp cs:[table](0x010F659E)27,594~36,659 0 pumpit2a 25초 breakpoint 149,911 108,557 fps는 그대로입니다. 남은 trap은 게임 자신의 대기 루프(Watcom
delay()의 INT 21h AH=2Ch 보정 spin 과 폴링,lseek) 안에서 일어나므로 비용이 줄어든 만큼 같은 대기 안에서 더 돌 뿐이고, 얻은 것은 CPU 점유입니다.고친 것
"ISR 입력 스캔의 trap 부하"는 오진이었습니다 (Task 741) — 새 breakpoint 지점 census(항상 켜짐, 최종 보고의
breakpoint site #N host/guest/cache/count/exits상위 32개)가 실제 상위를 밝혔습니다. Watcomdelay()의 INT 21h AH=2Ch 호출 세 곳이 61%(시작 2초의 보정 spin 45만 회 + 초당 6,600회),lseek이 10%, memcpy helper의 segment write 쌍이 10%, ISR은 1%대입니다. trap 수는 게임 대기 루프가 정하므로 trap 한 번의 비용을 줄였습니다: HLE 재진입마다 같은 guest 주소를 Zydis로 세 번씩(segment write 여부, 앞 64 명령의 span 안전, long mode 동일성) 다시 디코드하던 것을 주소별 메모(1,024칸, guest 코드 쓰기마다 오르는 세대·8바이트 지문·code mode로 무효화)로 바꿨습니다.REPIU_AOT_REENTRY_MEMO=0으로 끕니다. DOS 서비스 안의 호출마다getenv와localtime변환도 캐시합니다.segment 읽기·push가 trap 없이 (Task 742) —
mov r16/r32,sreg와 32비트push es/ds/fs/gs가 long-mode 슬롯으로 방출됩니다. 값은 guest가 보는 selector이고 엔진이 shadow에 유지하므로 슬롯은 guard 없이 shadow를 읽습니다(새 kindkGuardedSegmentPush; i386 이미지는 원본 복사). 이전 코드는 long-mode 이미지에 i386 read 슬롯을 방출해 host 스택에 pushfq하고 host DS=0을 RIP 상대 주소와 비교해 항상 fallback INT3로 가고 있었습니다. 이제 long mode에서는 방출하지 않습니다.번역되지 않은 대상으로의 return (Task 743) — 동적 번역이 거절된 블록으로
ret이 돌아오면 return thunk의 resolver가 "첫 명령이 long mode에서 byte-identical"일 때만 legacy resume을 허용해, 그 밖의 대상(pop edx가 첫 명령인 흔한 경우)에서 thunk의int3로 죽었습니다. 간접 call과 같은 조건(읽을 수 있는 guest 코드)으로 넓혔습니다. 남는 막다른 곳(번역도 없고 동일하지도 않은 명령)은[repiu-x64-untranslatable] stage= eip= bytes= …로 주소와 바이트를 남기고 끝나며,[repiu-fault] unhandled …에 마지막 VEH exit site(last_exit_site=/last_exit_eip=)가 붙습니다.xor edx,edx; mov dl,bl로 정규화하는 jump table (Task 744) — planner의 low-byte guard(cmp bl,imm; ja)가 정규화로and r32,0xFF만 알아, 이 Watcomswitch형식은CS:접두어 때문에 매번 trap했습니다. guard가xor/sub r32,r32를 지나mov r8,r8'(또는movzx r32,r8)에서 완성됩니다.새 진단 (설정하지 않으면 동작 없음)
변수 / 출력 내용 breakpoint site #N host/guest/cache/count/exits(최종 보고, 항상)breakpoint 예외를 낸 host 주소·guest 주소·횟수·끝낸 VEH exit site 상위 32개 AOT reentry memo hit/miss segment/span/long-mode(최종 보고, 항상)재진입 메모 적중률 REPIU_AOT_REENTRY_MEMO=0재진입 메모를 끄고 매번 디코드(A/B용) REPIU_AOT_DYNAMIC_REJECT=<guest 주소>또는readworker가 그 동적 이미지를 거절해 번역 실패 상황을 재현 [repiu-x64-untranslatable] …번역 없고 long mode 비동일한 명령에서 멈출 때 주소·바이트 (8회까지) [repiu-fault] unhandled … last_exit_site= last_exit_eip=거절한 VEH exit site 코드( VehExitSite번호)와 EIP[repiu-aot-unsafe] line=N동적 이미지 append가 unsafe 실패로 끝난 검사 지점 알려진 것
- 번역도 없고 long mode 동일하지도 않은 명령(예: 절대 주소
cmp)에 이르면 Linux x64는 여전히 실행할 수 없습니다(인터프리터 없음). 동적 번역이 정상이면 도달하지 않으며, 이제 주소를 남기고 끝납니다. - Linux x64의 남은 trap은 전부 게임 자신의 것입니다:
delay()AH=2Ch,lseek, ISR의 batch된in·EOI·iret. 곡 재생 중 tick이 초당 234~244로 흔들리는 것과 장면 전환 중 40~160 ms의 tick 공백은 조사하지 않았습니다. REPIU_GUEST_POSITION_CENSUS는 Linux에서 표본을 잡지 못합니다.REPIU_DOS_INT_TRACE는 stderr 출력이 대기 루프를 늦춰 호출 수를 왜곡하므로 trap을 셀 때 쓰지 마십시오.- v0.0.191의 알려진 것(Windows legacy backend pumpitea 크래시, VS 2026 v145
dos4gw_hello크래시, CD 미리듣기 무음 보고)은 그대로입니다. - Linux 측정은 WSLg에서 했습니다.
검증: Linux x64 core probe 전체 통과(새 case: 재진입 메모, long-mode segment read/push 슬롯 3개, jump-table guard 3개), Windows x86 core probe 전체 통과, WSL 합성 입력으로 pumpitea 코인 → 곡 선택 → 플레이(60초, 폴트 0), pumpit2a 25초 회귀, 수정 빌드 4/4 실행 폴트 0.
- 번역도 없고 long mode 동일하지도 않은 명령(예: 절대 주소
-
2026-09-26
v0.0.191
이전 릴리스 v0.0.190 이후의 변경입니다(Tasks 735–740). pumpitea가 Linux x64와 Windows 양쪽에서 시작해 플레이까지 갑니다. Linux x64 플레이에서 화살표 노트가 멈췄다 뛰어넘던 증상과 음악 노이즈가 사라졌습니다(사용자 확인). 창 제목에 플랫폼·아키텍처·빌드 구성이 표시되고, Linux 빌드 스크립트가
--target없이 그대로 동작합니다.전체 릴리스 노트
이전 릴리스 v0.0.190 이후의 변경입니다(Tasks 735–740). pumpitea가 Linux x64와 Windows 양쪽에서 시작해 플레이까지 갑니다. Linux x64 플레이에서 화살표 노트가 멈췄다 뛰어넘던 증상과 음악 노이즈가 사라졌습니다(사용자 확인). 창 제목에 플랫폼·아키텍처·빌드 구성이 표시되고, Linux 빌드 스크립트가
--target없이 그대로 동작합니다.pumpitea: 어디까지 왔나
v0.0.190 v0.0.191 Linux x64 시작 AOT 이미지 decode 검증 거절로 시작 못 함 시작 → 타이틀 → 곡 선택 → 플레이 Linux x64 23초 타이머 ISR 중첩으로 segfault 90초 이상 정상 Windows 시작 8초에 0xC000000590초 타이틀 도달 플레이 중 노트 멈췄다 뛰어넘음 부드러움 플레이 중 음악 간헐 노이즈 없음 고친 것
Linux x64에서 pumpitea 시작 실패 (Task 735) — AOT 이미지 decode 검증이 guest
82 68 10 01(sub byte [eax+0x10],1) 하나를 거절했습니다. opcode82는80group-1 연산의 별칭이고 long mode에서는 #UD입니다. long-mode 경로가82를80표기로 판단·방출합니다. 빌드 실패 메시지는 이제 단계와 첫 실패 항목, 방출 바이트를 담습니다.pumpitea 타이머 ISR 중첩 (Task 736) — PIT를 51.9 kHz로 프로그램한 단계에서 타이머 ISR의
cli뒤 back-edge에 tick이 다시 주입되어 ISR이 중첩되고 스택이 넘쳤습니다(user mode에서 IF는cli를 반영하지 않습니다). PIC의 IRQ0 in-service 비트를 모델링해 EOI(0x20/0x60→ 포트0x20) 전에는 주입하지 않고, EOI 뒤 gueststi에서 밀린 tick을 전달합니다(연속sti전달은 금지, 주입 frame에서 TF 제거).REPIU_PIC_TIMER_IN_SERVICE=0으로 이전 동작.Windows pumpitea 시작 크래시 (Task 737) — 세 겹이었습니다. (1) i386 간접 CALL fallback이 x64 규칙(피호출에서 계속)대로 push를 유지해 반환 주소를 두 번 push → i386에서는 legacy가 CALL을 재실행하므로 되돌립니다. miss tail의 CALL도
+8. (2) i386 kCopy가 16-bit code object의 3 stub 바이트를 그대로 복사해 dynamic 이미지 전부가 거절됨 → 16-bit 기록은 INT3 HLE 경계. (3) ISR 입력 스캔의cmp; jge exit; jmp back형식을 지연 루프 batcher가 거절 → 뒤집은 형식 수용(Windows 19,001 batch/90초).Linux x64 플레이의 노트 끊김과 노이즈 (Task 740) — MP3 frame-sync 토글과 decoder의 refill이 모두 SDL 장치가 PCM을 가져가는 계단(장치 buffer 단위)에 매여 있었습니다. 한 계단 안의 frame 경계 여러 개가 한 호출에서 함께 토글되고(240 Hz로 폴링하는 guest는 짝수 개를 변화 없음으로 봄), decoder도 계단 단위로 몰아 채워 guest의 demand와 frame 카운터가 같은 계단으로 움직였습니다. 장치 계단 사이를 PCM 속도로 보간하는 재생 시계가 토글과 decode 게이트를 움직입니다. 장치 buffer 2048 frame 강제 시 초당 4~6회이던 토글 뭉침이 0이 되었고, 사용자 환경에서 노트 끊김과 노이즈가 모두 사라졌습니다.
바뀐 것
- 창 제목(Task 738):
rePIU v0.0.191 (Win/x86 Debug) - Build … - Glide 2 OpenGL [dynamic] - FPS : ….Linux/x64 Release처럼 플랫폼/아키텍처 빌드 구성이 붙습니다.repiu::platform::BuildIdentityLabel(), CMakeREPIU_BUILD_CONFIG. - Linux 빌드 스크립트(Task 739):
scripts/build_linux_x64.sh·build_linux_i386.sh가--target없이 전체를 빌드합니다.repiu_exe가 Linux에서GL을PUBLIC으로 링크해repiu_instruction_census와 i386 core probe의 링크 오류가 없어졌습니다. i386 스크립트에--build-dir와 구성 불일치 안내가 생겼고,--headless는 X11/Wayland 개발 패키지가 없을 때 configure 오류를 막는 스위치일 뿐 데스크톱 드라이버는 그대로 빌드됩니다. - 최종 보고에
PIU10 MP3 received/dropped/decoded/starvation/toggles/multi/pcm-empty/ pcm-low-water-bytes/device-buffer-frames와timer IRQ0 in-service …줄이 추가됐습니다.
새 진단 (설정하지 않으면 동작 없음)
변수 / 도구 내용 REPIU_PIU10_MP3_CENSUS_MS=<ms>MP3 파이프라인 census 한 줄(수신·decode·큐·시계 지연·demand·토글·뭉침·기아·빈 큐·tick) REPIU_PIC_TIMER_IN_SERVICE=0IRQ0 in-service 모델을 끄고 이전 주입 동작으로 SDL_AUDIO_DEVICE_SAMPLE_FRAMES=<frames>(SDL 힌트) 장치 buffer를 강제해 큰 buffer의 host를 재현 scripts/task740_mp3_census_capture.sh위 census로 한 번 실행하고 초당 요약을 냄 알려진 것
- Windows legacy backend의 pumpitea 크래시(
0x040FD010)는 재확인하지 않았습니다. Windows pumpitea의 곡 선택·플레이 진행도 확인하지 않았습니다. - pumpitea의 ISR 입력 스캔은 tick마다 200회
in을 trap으로 실행합니다(Linux 88초에 405만 회). 결과는 맞지만 비용이 큽니다. - 곡 재생 중 tick이 초당 234~244로 흔들리고, 장면 전환 중 40~160 ms의 tick 공백이 있습니다. 조사하지 않았습니다.
- v0.0.190의 알려진 것 중 VS 2026(v145) 빌드의
dos4gw_hello크래시, Linux x64 로딩 구간 정지, CD 미리듣기 무음 보고는 그대로입니다. - Linux 측정은 WSLg에서 했습니다.
검증: Linux x64 core probe 33/33, Windows x86 core probe 31/31, Linux i386 core probe 31/31, WSL 합성 입력으로 pumpitea 코인 → 곡 선택 → 플레이 재현(60~90초, 폴트 0), Windows pumpitea 90초, pumpit2a 회귀 실행, 사용자 플레이 확인.
- 창 제목(Task 738):
-
2026-09-26
v0.0.190
이전 릴리스 v0.0.189 이후의 변경입니다(Tasks 718–734). Linux x64에서 pumpit2a가 곡 선택 화면을 지나 곡을 고르고 플레이에 들어갑니다. 렌더 단계 게임 시계가 기본으로 흐르고, Glide 호출이 trap 없이 직접 디스패치됩니다. Windows에서는 pumpit1 legacy 실행의 간헐 종료 크래시를 고쳤습니다.
전체 릴리스 노트
이전 릴리스 v0.0.189 이후의 변경입니다(Tasks 718–734). Linux x64에서 pumpit2a가 곡 선택 화면을 지나 곡을 고르고 플레이에 들어갑니다. 렌더 단계 게임 시계가 기본으로 흐르고, Glide 호출이 trap 없이 직접 디스패치됩니다. Windows에서는 pumpit1 legacy 실행의 간헐 종료 크래시를 고쳤습니다.
Linux x64: 어디까지 왔나
v0.0.189 v0.0.190 렌더 단계 게임 시계 옵션을 켜야 흐름 기본값으로 흐름 Glide 호출 전부 ud2trap (180초에 242만 회)직접 디스패치 (trap 0) 180초 실행 swap 수 10,344 15,832 곡 선택 화면 정지 통과 → 곡 선택 → step 데이터 로드 → 곡 재생 고친 것
pumpit2a 곡 선택 정지 (Task 734) — Linux x64 return thunk가 guest의
ret과 간접 call/jump 뒤 EFLAGS를 복원하지 않아, 이 경로를 지나는 모든ret이 CF=0으로 돌아왔습니다. Watcom C 런타임의sin/coshelper는 결과를 CF로 알려 주므로 게임이fsin을 끝없이 재시도했습니다. thunk가 저장한 flags를jmp직전에 복원합니다. 이 결함은ret뒤에 flags를 읽는 모든 guest 코드에 영향을 줬을 수 있습니다.Linux x64 렌더 단계 시계 (Task 718) — safe point tick 주입이 기본값입니다. 끄려면
REPIU_LINUX_X64_SAFE_POINT_INJECTION=0을 씁니다. 이전에는 변수가 있기만 하면 켜져서=0도 켜짐이었습니다.Linux x64 Glide 직접 디스패치 (Task 720) — direct-dispatch thunk가 i386 assembly에만 있어서 Linux x64의 모든 Glide 호출이
ud2signal을 거쳤습니다.RepiuLinuxX64GlideGateThunk가 guest x87 상태를 저장·복원하며 export를 직접 부릅니다. 로딩 구간의 1~3.6초 정지는 그대로이며, Glide trap이 원인이 아니라는 것도 이것으로 확인했습니다.종료 회수
- x64 종료 회수 판정이 RIP 전체를 봅니다(Task 730). 전에는 하위 32비트만 봐서, host 라이브러리 주소의 하위 절반이 guest 범위에 들면 host frame에서 "회수"할 수 있었습니다.
- Windows: pumpit1 legacy 실행이 약 절반 확률로 종료 시
0xC0000005로 죽던 문제를 고쳤습니다(Task 733). 원인은SetThreadContext시점에 커널이 이미 전달 중이던 예외였고, 종료 구간 전용 redirect guard로 막습니다. 수정 뒤 40회 연속 정상 종료했습니다.
바뀐 것 — 로그와 소스 위치
- 공용 로더가
src/host/win32/main.cpp에서 **src/host/loader/main.cpp**로 옮겨졌습니다 (Task 731, 내용 동일). - 로더 로그 줄의
Win32접두어가 없어졌습니다(Task 732).[loader] Win32 minimal execution timed out은 이제[loader] minimal execution timed out입니다. 저장소의 스크립트·가이드 230곳은 함께 고쳤습니다. 외부에서 로그를 파싱한다면 패턴을 고쳐야 합니다. scripts/test_all.ps1의 낡은 pumpit1 단정을 현재 동작에 맞췄습니다(Task 733).
성능 실험 (기본값 꺼짐)
Glide LFB write lock마다 하던 framebuffer 역읽기를, 3dfx page flip처럼 color buffer별 사본을 swap에서 교환하는 방식으로 대신할 수 있습니다(Tasks 728–729). 30초 실행에서 seed가 304회에서 2회로, 역읽기 비용이 약 65억 cycle에서 4,690만 cycle로 줄었지만 이 구간 fps는 거의 그대로 (27.7 → 28.0)여서 기본값은 끈 채로 둡니다.
REPIU_GLIDE_LFB_STAGING_REUSE로 켭니다.새 진단 (설정하지 않으면 동작 없음)
변수 / 도구 내용 REPIU_GLIDE_PIXEL_DIAG_INTERVAL_MS시간 간격으로 back buffer를 표본(4×4 밝기 격자). 두 host의 장면을 맞춰 볼 때 REPIU_LINUX_X64_NATIVE_SAMPLE,REPIU_LINUX_X64_NATIVE_SAMPLE_TRACELinux x64 guest thread 위치 표본과 표본별 RIP·guest EIP 로그 REPIU_GLIDE_LFB_TIME_PROFILE,REPIU_GLIDE_LFB_WRITE_CENSUSLFB lock 단계별 비용과 write 범위 REPIU_LINUX_X64_LFB_STORE_CENSUS,..._SOURCE_CENSUSLFB에 쓰는 guest 명령과 그 출처 REPIU_GLIDE_LFB_STAGING_SHADOW_CENSUS,REPIU_GLIDE_LFB_LOCK_INTERVAL_CENSUS역읽기를 건너뛸 수 있는 lock 수와 lock 사이 구간 분류 REPIU_LINUX_X64_MEMORY_WRITE_TRACELinux x64 guest 메모리 쓰기 추적 REPIU_SHUTDOWN_RECOVERY_TRACEWindows 종료 회수 직후 도착한 예외 기록 [repiu-shutdown]줄host_ip=,decision=,aliased=와 Windows redirect guard 개입 횟수scripts/task734_songselect_stall_capture.sh곡 선택 정지 같은 대화형 상태를 프레임률·위치 표본·cache map과 함께 기록 알려진 것
build_win32_x86.ps1은 설치된 가장 새 Visual Studio를 고르는데, VS 2026(toolset v145)으로 빌드한 binary는dos4gw_hello시작에서 크래시합니다(3/3). VS 2022(v143) binary는 정상입니다. 그래서test_all.ps1을 end-to-end로 끝까지 돌리지 못했습니다.- 사용자 환경에서 CD 미리듣기 음악이 들리지 않았다는 보고는 개발 머신에서 재현되지 않았습니다. WSLg 출력에는 CD 재생 구간에 소리가 있습니다.
- Linux x64 로딩 구간의 1~3.6초 정지는 남아 있습니다(Glide가 원인은 아님).
- Linux x64에서
repiu_instruction_census도구가 GL 심볼 링크 오류로 빌드되지 않습니다. - Linux 측정은 WSLg에서 했습니다.
검증: Linux x64 core probe 31/31(새
guest_return_preserves_flags포함), Windows x86 core probe 29/29, WSL 자동 입력으로 pumpit2a 곡 선택 → 곡 재생 재현, Windows pumpit1 legacy 종료 40회 연속 정상. -
2026-09-18
v0.0.189
이전 릴리스 v0.0.188 이후의 변경입니다. Linux x64에서 pumpit2a가 텍스처가 입혀진 화면을 그리게 됐고, 선택 옵션을 켜면 게임 시계가 흐르며 장면이 진행합니다. Windows 동작은 그대로입니다(두 곳의 공용 수정은 아래에 따로 적었습니다).
전체 릴리스 노트
이전 릴리스 v0.0.188 이후의 변경입니다. Linux x64에서 pumpit2a가 텍스처가 입혀진 화면을 그리게 됐고, 선택 옵션을 켜면 게임 시계가 흐르며 장면이 진행합니다. Windows 동작은 그대로입니다(두 곳의 공용 수정은 아래에 따로 적었습니다).
Linux x64: 어디까지 왔나
v0.0.188 v0.0.189 Glide 초기화 → 렌더 루프 도달 도달 텍스처 업로드 건너뜀 Win32와 같은 gate 96개 부동소수점 signal 복귀 뒤 전부 NaN 정상 렌더 단계 게임 시계 멈춤 기본값: 멈춤 / 옵션 켜면 흐름 옵션 켠 90초 실행 — 폴트 0, 3,913프레임, 장면 진행 렌더 단계 시계를 살리는 옵션은
REPIU_LINUX_X64_SAFE_POINT_INJECTION입니다. 값과 관계없이 변수가 있으면 켜집니다(=0도 켜짐). 다음 릴리스에서 기본값으로 켤 예정입니다.고친 것
실행 재진입과 인터럽트 (Tasks 702–705)
- Glide gate와 타이머 체인 경계 뒤 AOT 코드로 다시 들어갑니다. 전에는 경계 뒤
PUSH·CALL이 host 스택에서 실행돼 게스트 스택에서 사라졌습니다. - INT 8 주입 frame에 게스트 선택자를 넣고, ISR의
IRETD를 HLE로 처리합니다. - 아무것도 바꾸지 않은 스레드 인터럽트가 host
R15(게스트 ESP)를 32비트 값으로 덮던 문제를 고쳤습니다.
CPU 상태와 코드 방출 (Tasks 707, 710, 713, 716)
- x87 tag word — signal 복귀마다 모든 FPU 레지스터가 "비어 있지 않음"이 되어 이후 연산이 전부 NaN이었습니다.
- 상위 바이트 lowering —
mov dh,[esp]같은 명령의 첫 바이트가 DL을 저장하는 대신 덮어써 EDX가 깨졌고, 그 결과glEnable(GL_TEXTURE_2D)가 실패해 텍스처가 통째로 생략됐습니다. - long-mode 경로에 timer safe point(1,067곳)를 심습니다.
CS:override 데이터 접근(mov al, cs:[edx+table])을 복사로 방출합니다. 전에는 x64에 처리기가 없는 HLE boundary였습니다.
세그먼트 fold (Tasks 711, 712, 717)
- loader의 초기 스택 선택자(
0x0034)와 데이터 선택자(0x0024)를 명시적으로 쓰는 override는 어느 세그먼트 레지스터에서든 base 0으로 계산합니다. 게스트의 암묵적 접근은 원래 flat이기 때문입니다.- SS: Watcom
lseek가 스택 밖에 써서 PIU.BIN을 110만 번 읽던 문제 - ES:
sprintf("%d")가 텍스처 픽셀을 숫자로 복사해 스택과 힙을 덮던 문제
- SS: Watcom
Glide LFB (Task 708) — LFB 버퍼 주소를 host 포인터에서 32비트로 잘라 넘기던 것을, 게스트가 가리킬 수 있는 4GiB 아래 공용 예약(
ReserveLowAddressMemory)으로 옮겼습니다.Windows에도 적용되는 공용 수정
- SS fold(Task 712) — Windows에서도 PIU.BIN을 처음으로 바르게 읽게 됐습니다.
- 데이터 선택자 fold(Task 717) — 30초 실행의 시계·safe point·세그먼트 수치가 잡음 범위 안이었습니다.
- Windows x86 빌드가 Linux 전용 telemetry 소스를 더 이상 컴파일하지 않습니다(Task 706).
새 진단 (설정하지 않으면 동작 없음)
변수 / 출력 내용 REPIU_LIVE_GUEST_PEEK=<offset>[,n]매초 게스트 dword 최대 16개 기록 REPIU_LIVE_GUEST_SCAN=<offset>,<bytes>,<dword>범위 안 dword 개수와 연속 구간 REPIU_LINUX_X64_DATA_WATCH=<address>Linux 하드웨어 쓰기 감시점(기록 후 계속 실행) [repiu-fault-stack]Linux 처리되지 않은 폴트 뒤 게스트 스택 64 dword 최종 실행 보고 회수가 거절된 종료 경로에서도 요약 출력(Task 709) 알려진 것
- 주입이 꺼진 기본값에서는 렌더 단계부터 게임 시계가 멈춥니다(17초 무렵).
- 게스트가 세그먼트 레지스터를 읽을 때 두 host가 다른 값을 돌려줍니다(Windows
0x2B, Linux0x0024). fold에서는 같게 취급하지만 값 자체는 다릅니다. - Linux 측정은 WSLg에서 했습니다. 실제 ext4 Ubuntu 검증은 하지 않았습니다.
검증: Linux x64 core probe 30/30, Windows x86 core probe 28/28, 두 host의 30초 실행 비교, Linux 90초 실행.
- Glide gate와 타이머 체인 경계 뒤 AOT 코드로 다시 들어갑니다. 전에는 경계 뒤
-
2026-08-28
v0.0.171
이전 릴리스 v0.0.170 이후의 변경입니다. 소스 구조만 바뀌었고 동작은 그대로입니다.
전체 릴리스 노트
이전 릴리스 v0.0.170 이후의 변경입니다. 소스 구조만 바뀌었고 동작은 그대로입니다.
실행 엔진이
src/engine/으로 나왔습니다AGENTS.md는 플랫폼 종속 코드를src/platform/{win32,linux,web}/에 두라고 정합니다. 그런데 플랫폼 공용 실행 엔진이src/platform/win32/안에 있었고 Linux가 그것을 컴파일하고 있었습니다. 세어 보니 그 트리 119개 중 102개가 Win32 의존이 전혀 없었습니다.전 후 src/platform/win32/119 6 (Linux 대응물이 있는 백엔드) src/engine/— 113 include/repiu/platform/win32/46 1 include/repiu/engine/— 46 헤더 경로와 네임스페이스가 바뀝니다 — 호환성 주의
include "repiu/platform/win32/foo.h" -> include "repiu/engine/foo.h" repiu::platform::win32::Bar -> repiu::engine::Bar이 헤더를 쓰는 코드가 있으면 고쳐야 합니다. 백엔드(
repiu::platform)는 바뀌지 않았습니다.의존이 한 방향이 됐습니다
이전에는
execution_trampoline.cpp가#include "win32_thread_api.h"로 백엔드의 소스 디렉터리를 직접 들여다봤습니다. 그 헤더를 공개 헤더로 올려, 이제 엔진은include/repiu/platform/win32/를 통해서만 백엔드를 봅니다.알려진 것
- 동작 변경은 없습니다. Linux·Windows 빌드와 probe·샘플 회귀로 확인했습니다.
- wasm 구성은 검증하지 못했습니다 — 이 환경에 Emscripten이 없습니다.
- 파일·심볼 이름의
Win32는 그대로입니다(aot_code_cache_win32.cpp에는 Win32 코드가 없습니다).src/host/win32/main.cpp도 그대로입니다. 둘 다 별도 작업입니다.
-
2026-08-28
v0.0.170
이전 릴리스 v0.0.169 이후의 변경입니다. Linux 성능 축을 **"재진입마다 트랩 하나"**까지 좁혔습니다. 아직 고치지 않았습니다.
전체 릴리스 노트
이전 릴리스 v0.0.169 이후의 변경입니다. Linux 성능 축을 **"재진입마다 트랩 하나"**까지 좁혔습니다. 아직 고치지 않았습니다.
좁혀진 사슬
Windows Linux 프레임당 재진입 160.6 ~154 (0.90배) 재진입당 breakpoint 0.0432 1.010 재진입 비율은 Linux가 오히려 적습니다. 다른 것은 나올 때마다 트랩을 내는가이고, 이 23배가 앞서 잰 breakpoint 24.2배와 맞습니다. Windows가 트랩을 피하는 경로는 Glide direct dispatch(재진입의 95.1%)이고, Linux에서도 87%가 그 경로로 갑니다 — 그런데 성공해도 트랩이 붙습니다.
실행 중 계측 네 줄
REPIU_EXECUTION_TIME_PROFILE=1과REPIU_LIVE_PROFILE_INTERVAL_MS로 켭니다.[repiu-live-profile] veh/glide/port-io/dos/unaccounted 몫 [repiu-live-veh] 배달 수·배달당 cycle·커널 gap·클래스 셋 [repiu-live-aot] 캐시 entry/boundary/재진입, 경계 사유 다섯 [repiu-live-gdd] direct dispatch patched/resolved/relinked/entry/successLinux는 로더 요약에 닿지 못하므로 이 줄들이 아니면 아무것도 읽을 수 없습니다.
알려진 것
- 성능은 아직 고치지 않았습니다.
- 다음 질문: Windows에서 그 INT3이 왜 다시 밟히지 않는가.
- 이 버전을 만드는 동안 추정이 네 번 틀렸습니다. 모두 카운터의 이름으로 뜻을 짐작하고 증가 지점을 읽지 않은 경우였고, 표로 frontier에 남겼습니다.
-
2026-08-28
v0.0.169
이전 릴리스 v0.0.168 이후의 변경입니다. 웹(WebAssembly) 이식 Stage 1~2가 들어가고, Stage 3~5는 보류로 정리했습니다.
전체 릴리스 노트
이전 릴리스 v0.0.168 이후의 변경입니다. 웹(WebAssembly) 이식 Stage 1~2가 들어가고, Stage 3~5는 보류로 정리했습니다.
플랫폼 공용 코어가 wasm32로 빌드됩니다
코어 소스를 한 줄도 고치지 않았습니다.
항목 값 repiu_exe오브젝트 (wasm32)64 코어 소스 수정 0 엔진 자신의 미해결 심볼 0 repiu_core_probe(wasm32)9 / 9 통과, 제외된 6개는 이름을 찍음 Windows 회귀 15 / 15, 변화 없음 의존성(SDL3·libchdr·miniz·Zydis)도 Emscripten에서 그대로 빌드됩니다.
인터프리터의 크기를 쟀습니다
pumpipx3의PIU.EXE를 진입점에서 재귀 하강으로 훑었습니다.항목 값 명령 74,333 (블록 17,367) 서로 다른 mnemonic 120 서로 다른 (mnemonic, 피연산자 서명) 320 상위 17 mnemonic 누적 87.89% 인터프리터 규모의 기준은 120이 아니라 320입니다 —
MOV하나가 19가지 형태입니다. 다만 상위 17개가 87.89%이므로 점진적으로 세울 수 있습니다.브라우저에서는 아직 실행되지 않습니다
현재 backend 둘(
legacy·dynamic)이 모두 네이티브 x86 위에 서 있어 wasm에 대응 형태가 없습니다. Stage 3(플랫폼 중립 인터프리터)이 그 자리이고, 보류했습니다 — Linux 성능 축이 먼저입니다.재개 조건은 웹 이식 frontier에 있습니다. 요약하면 둘을 먼저 정해야 합니다: x87 표현(80비트인가) 과 Worker 실행 전제. 둘 다 나중에 바꾸면 인터프리터를 다시 쓰는 일이 됩니다.
-
2026-08-27
v0.0.168
이전 릴리스 v0.0.167 이후의 변경입니다. v0.0.167의 결론 하나를 정정합니다.
전체 릴리스 노트
이전 릴리스 v0.0.167 이후의 변경입니다. v0.0.167의 결론 하나를 정정합니다.
시그널 전달은 범인이 아니었습니다
v0.0.167은 Linux 격차의 68%를 "시그널 전달"이라고 적었습니다. 그 68%를 두 인자로 갈랐더니 이름이 틀렸습니다.
Windows Linux 배율 프레임당 배달 18.8 256.3 13.6x 배달당 cycle (핸들러 본문) 104,002 341,557 3.3x 커널 왕복 바닥 21,756 9,632 0.44x 커널 왕복 single-step 평균 28,185 16,466 0.58x Linux의 시그널 전달은 Windows의 예외 디스패치보다 두 배 이상 쌉니다. 비싼 것은 전달이 아니라 경계를 13.6배 더 밟는 것과 밟을 때마다 핸들러 본문이 3.3배인 것입니다.
보고 줄이 하나 늘었습니다
[repiu-live-veh] #8 veh_count=617579 per_frame=263 cycles_per_veh=344198 gap_min=9472 gap_ss_mean=16427 gap_ss_count=22082REPIU_EXECUTION_TIME_PROFILE=1과REPIU_LIVE_PROFILE_INTERVAL_MS로 켭니다. 새로 세는 것은 없습니다 — 이 값들은 이미 쌓이고 있었고 찍히지 않았을 뿐입니다.알려진 것
- 아직 성능을 고치지 않았습니다. 원인을 좁히는 데까지입니다.
- 초과 경계가 무엇인지는 아직 모릅니다. single-step은 아닙니다(Linux 배달의 3.6%, Windows 24.4%).
- 프레임당과 초당 정규화가 서로 다른 말을 합니다. 초당으로는 Linux가 폴트를 40% 적게 냅니다 — 프레임이 24배 드물기 때문입니다.
-
2026-08-27
v0.0.167
이전 릴리스 v0.0.166 이후의 변경입니다. Linux가 왜 느린지 알아냈습니다.
전체 릴리스 노트
이전 릴리스 v0.0.166 이후의 변경입니다. Linux가 왜 느린지 알아냈습니다.
답: 시그널 전달
같은 기계이므로 TSC cycle을 직접 비교했습니다. 프레임당, 100만 cycle 단위입니다.
버킷 Windows Linux 배율 격차 기여 폴트 핸들러 2.065 88.072 42.6x 68.3% Glide 1.618 26.480 16.4x 19.7% 미귀속 2.159 17.214 8.0x 12.0% DOS 서비스 0.006 2.438 433.9x 1.9% Linux 격차의 68%가 폴트 핸들러이고, Linux에서 그것은 시그널 전달입니다. 3회 재현되며 Windows 대조군은 35.35%입니다.
Glide 게이트는 그 전에 배제했습니다 — 호스트 왕복 횟수와 크로싱 횟수를 되돌려 놓아도 프레임률이 움직이지 않았고, 같은 노브가 Windows에서는 27.1%를 깎습니다.
실행이 실행 중에 자기 상태를 말합니다
REPIU_LIVE_PROFILE_INTERVAL_MS=1000을 주면 지정한 간격마다 한 줄이 나옵니다.[repiu-live-profile] #5 frames=1706 window_frames=275 cycles_per_frame=133327684 veh=59.82% glide=22.86% dos=2.90% unaccounted=17.30%REPIU_EXECUTION_TIME_PROFILE=1과 함께 씁니다. 이것이 필요했던 이유는 Linux에서 렌더까지 간 실행이 로더 요약에 닿지 못하기 때문입니다 — 게스트 스레드가 멈추지 않아 종료 갈래가_Exit으로 끝납니다.누적 평균으로는 안 보이는 것이 바로 나왔습니다: Linux 프레임당 비용이 실행 중 세 배가 됩니다(83M → 272M cycle). 일정하게 느린 것이 아니라 장면이 진행될수록 나빠집니다.
알려진 것
- 아직 성능을 고치지 않았습니다. 축을 찾는 데까지입니다.
- 시그널 전달이 횟수가 많은 것인지 회당 단가가 큰 것인지 가르지 않았습니다. 다음 작업입니다.
- probe 모음이 Release에서 검증된 적이 없습니다(v0.0.166에서 기록).
-
2026-08-27
v0.0.165
이전 릴리스 v0.0.164 이후의 변경입니다. Linux 이식 3단계로, 게임이 Linux에서 실행되고 화면이 나옵니다. v0.0.163이 런처까지였고 그때는 롬셋을 고르면 "실행 엔진이 아직 Linux에 없다"고 알리고 끝났습니다.
전체 릴리스 노트
이전 릴리스 v0.0.164 이후의 변경입니다. Linux 이식 3단계로, 게임이 Linux에서 실행되고 화면이 나옵니다. v0.0.163이 런처까지였고 그때는 롬셋을 고르면 "실행 엔진이 아직 Linux에 없다"고 알리고 끝났습니다.
Linux에서 게임이 돕니다
- 기본
dynamicAOT backend가 Linux에서 동작합니다. Win32 메모리 호출 62곳을 3b 가상 메모리 계층으로 옮기고, 없던 것 하나 — 명령 캐시 flush — 를 만들었습니다. 캐시 배치, 동적 실행, 인라인 패치, 페이지 retirement가 모두 Linux에서 돕니다. legacybackend는 명령 단위로 단일 스텝하므로 기동 자산 디코드를 끝내지 못했습니다. 화면이 기다리고 있던 것이 이 항목이었습니다.- WSLg
pumpit1기준 약 45.1초에 첫 버퍼 스왑, 약 51.7초에 69,263/307,200 non-black 픽셀, 이후 스왑이 계속됩니다. 실제 게임 화면이 나오는 것은 사람이 확인했습니다.
종료가 됩니다
- 종료를 요청받은 실행이 스스로 끝납니다. 이전에는 SDL이 SIGTERM을 종료 이벤트로 바꾼
뒤
pthread_join이 영원히 기다려, 종료를 요청받은 실행 자체가 매달렸습니다. - 종료 블록의 Win32 전용 호출 넷이
InterruptHostThread한 번으로 수렴했습니다. 회수에 성공하면 정상 종료하고, 거절되면 기다리지 않고 내려갑니다. - 코어 덤프 없이 끝납니다. 회수를 거절당한 갈래가 게스트 스레드가 아직 필요로 하는 폴트 핸들러를 떼고 있었습니다. 이제 그 갈래는 정리를 하지 않고 곧바로 끝냅니다. 60초 예산 6회 기준 SIGTRAP 2회 → 0회.
알려진 것
- 속도가 아주 느립니다. Linux 프레임률은 아직 측정된 적이 없고, 다음 작업이 그것입니다.
- 무엇이 그려지는지를 Windows와 프레임 단위로 대조한 적은 없습니다.
- 기본
-
2026-08-24
v0.0.163
이전 릴리스 v0.0.162 이후의 변경입니다. Linux 이식 2단계로, 런처가 Linux에서 32비트 바이너리로 실행됩니다. 실행 엔진은 여전히 Windows 전용이라 게임은 실행되지 않습니다.
전체 릴리스 노트
이전 릴리스 v0.0.162 이후의 변경입니다. Linux 이식 2단계로, 런처가 Linux에서 32비트 바이너리로 실행됩니다. 실행 엔진은 여전히 Windows 전용이라 게임은 실행되지 않습니다.
Linux 런처
repiu_launcher가 i386 ELF로 빌드되어 WSLg에서 동작합니다. 롬셋 목록과 옵션 화면은 Windows와 같은 코드입니다.- 실제
roms/를 읽어 22개 중 실행 가능한 것을 가려냅니다. - 롬셋을 고르면 "실행 엔진이 아직 Linux에 없다"고 알리고 종료합니다. 없는 것을 실행하는 척하다 "파일 없음"으로 실패하게 두지 않습니다.
sudo dpkg --add-architecture i386 && sudo apt update sudo apt install -y libx11-dev:i386 libxext-dev:i386 libxrandr-dev:i386 \ libxi-dev:i386 libxcursor-dev:i386 libxfixes-dev:i386 libxkbcommon-dev:i386 \ libgl1-mesa-dev:i386 libasound2-dev:i386 scripts/build_linux_i386.sh --config Debug --target repiu_launcher build/linux_i386/repiu_launcher데스크톱 패키지 없이 코어와 probe만 빌드하려면
--headless를 주십시오.이번 단계에서 확인된 것
32비트 SDL3 데스크톱 스택이 섭니다 — X11 비디오, OpenGL 렌더, ALSA 오디오. 이는 런처만이 아니라 앞으로 게임 창과 사운드에 필요한 구성 전부입니다. 실행 엔진도 SDL을 쓰기 때문에(게임 창과 오디오 세 경로), 이 확인이 없었다면 포팅 전체가 막힐 수 있었습니다.
가는 길에 두 가지를 해결했습니다.
- SDL configure가
XSCRNSAVER·XTEST확장에서 멈췄습니다. 화면보호기 억제와 입력 시뮬레이션용이고 이 프로젝트는 쓰지 않으므로, 패키지를 더 요구하는 대신 껐습니다. 켜 둔 확장 하나가 곧 운영자가 찾아야 할 32비트 패키지 하나입니다. - OpenGL 링크가 필요했습니다.
find_package(OpenGL)의 imported target 대신 평범한GL을 씁니다 — 전자는 64비트libGL.so를 잡아-m32링크를 아키텍처 불일치로 깨뜨릴 수 있습니다.
알려진 제한
- Linux에서 게임은 실행되지 않습니다. 실행 엔진 이식은 다음 단계입니다.
- Linux 런처는 Windows의 복귀 루프와 자식 프로세스 구조를 가져오지 않았습니다. 그 구조는 GPU 드라이버가 게스트 주소 공간을 선점하는 Win32 고유 사정에서 나온 것이라, Linux에도 같은 제약이 있는지는 실행 엔진이 생긴 뒤에 판단합니다.
- Wayland 백엔드는 잡히지 않았습니다. WSLg가 X 서버를 함께 제공하므로 문제되지 않습니다.
-
2026-08-24
v0.0.162
이전 릴리스 v0.0.161 이후의 변경입니다. Linux 이식의 첫 단계로, 플랫폼 공용 코어와 그 probe가 Linux에서 i386으로 빌드되고 통과합니다. Windows 동작에는 변화가 없고, Linux에서 게임이 실행되지는 않습니다.
전체 릴리스 노트
이전 릴리스 v0.0.161 이후의 변경입니다. Linux 이식의 첫 단계로, 플랫폼 공용 코어와 그 probe가 Linux에서 i386으로 빌드되고 통과합니다. Windows 동작에는 변화가 없고, Linux에서 게임이 실행되지는 않습니다.
측정이 계획을 결정했습니다
이식 범위를 추정하지 않고 먼저 쟀습니다. 전체 192개 소스 중 공용 코어가 53개,
src/platform/win32가 76개인데, 공용 코어는 Win32 헤더를 하나도 include하지 않고, MSVC 전용 구문이 없으며, 32비트 포인터를 가정하지 않고, GCC 13 + C++20에서 오류 없이 컴파일됩니다. 한 줄도 고치지 않고 Linux로 넘어갔습니다.반대쪽은 규모가 다릅니다 —
CONTEXT270곳,VirtualProtect47곳,fs:[...]어셈블리 28곳. 실행 엔진 이식(Stage 3)의 크기입니다.빌드 구조
src/platform/win3276개와 Windows 전용 실행 파일이if(WIN32)안으로 들어갔습니다. 새 라이브러리 타깃을 만들지 않았으므로 Windows 결과물과 링크 구성은 그대로입니다.repiu_core_probe신설. 플랫폼에 의존하지 않는 probe 9개를 담고 양쪽 OS에서 모두 빌드됩니다. 두 실행의 출력 46줄이 완전히 동일합니다 — "통과했다"가 아니라 "같은 코드가 두 환경에서 같은 결과를 낸다"를 확인할 수 있습니다.scripts/build_linux_i386.sh추가. 32비트 툴체인을 먼저 확인하고 없으면 설치 명령을 안내합니다.
Linux 빌드
sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386 scripts/build_linux_i386.sh --config Debug --target repiu_exe --target repiu_core_probe build/linux_i386/repiu_core_probe아키텍처는 i386입니다. 게스트의 32비트 코드를 같은 프로세스에서 네이티브 실행하므로 호스트도 32비트여야 하고, 이는 현재 Win32 호스트와 같은 이유입니다.
고친 것
nvram_path_probe가 MSVC 전용_putenv를 쓰고 있었습니다. POSIX는setenv/unsetenv이고 변수를 지우는 방식도 다릅니다. 조건부로 나눴습니다. 공용 코어 53개에서는 이런 결함이 하나도 나오지 않았습니다.알려진 제한
- 실행 엔진·로더·런처는 Windows 전용입니다. Linux에서 게임은 실행되지 않습니다.
- SDL3가 X11/Wayland 개발 패키지를 요구해, 이 단계에서는 창을 열지 않으므로 SDL이 문서화한 헤드리스 옵션을 씁니다. 런처를 올리는 다음 단계에서는 실제 데스크톱 패키지가 필요합니다.
repiu_chd_cd_probe와repiu_glide_issue_probe는 Win32 참조가 없어 가드하지 않았지만 Linux 빌드는 아직 확인하지 않았습니다.
-
2026-08-22
v0.0.161
이전 릴리스 v0.0.160 이후의 변경입니다. 이번 릴리스는 실행 방법을 바꿉니다. 인자 없이 실행하면 런처가 열려 롬셋을 고르고, 게임이 끝나면 다시 목록으로 돌아옵니다.
전체 릴리스 노트
이전 릴리스 v0.0.160 이후의 변경입니다. 이번 릴리스는 실행 방법을 바꿉니다. 인자 없이 실행하면 런처가 열려 롬셋을 고르고, 게임이 끝나면 다시 목록으로 돌아옵니다.
런처
repiu.exe를 인자 없이 실행하면 롬셋 목록이 열립니다. 고른 롬셋을 실행하고, 게임이 끝나면 런처로 복귀하므로 다른 타이틀을 이어서 고를 수 있습니다. 종료는 Quit입니다.- 내장 카탈로그의 롬셋이 전부 나옵니다. 실행할 수 없는 것도 감추지 않고 흐리게 표시하며
사유를 그대로 씁니다 —
roms\<id>.zip없음, ZIP에 필수 PIU10 엔트리 없음,roms\<id>\없음, CHD 없음, CHD가 둘 이상. 어떤 디스크가 왜 안 되는지 목록에서 바로 보입니다. - 판정은 마운트와 같은 규칙을 쓰되 추출은 하지 않습니다. 22개 디스크를 전부 무결성 검사하면 기다릴 수 없고, 실제 판단은 실행 시점의 마운트가 이미 합니다.
- 방향키로 이동, Enter로 실행, Esc로 종료합니다. 실행 불가 항목은 방향키가 건너뜁니다. 창이 뜨면 마지막에 실행한 롬셋에 커서가 놓입니다.
- vsync와 사운드 게인을 런처에서 바꿔
cfg\repiu.ini에 저장합니다. 같은 의미의 환경 변수가 설정돼 있으면 환경 변수가 이깁니다 — 측정 스크립트와 진단 절차의 우선권이 유지됩니다.
기존 실행 방법은 그대로입니다
repiu.exe <롬셋>과repiu.exe <PIU.EXE 경로>는 한 줄도 바뀌지 않았고, 게임이 끝나면 프로세스도 완전히 종료됩니다. 복귀 루프는 인자 없는 단독 실행에만 있습니다.- 인자 없이 실행하는 자동화를 위해
REPIU_LAUNCHER=0을 주면 런처를 건너뛰고 기존 기본값(pumpit1)으로 갑니다. - 창이나 GL 컨텍스트를 만들 수 없는 환경에서는 런처를 포기하고 기존 기본값으로 계속 진행합니다.
왜 네이티브 GUI가 아닌가
Dear ImGui(MIT)로 호스트가 이미 만드는 SDL3 창에 직접 그립니다. 리눅스에서 쓸 만한 크로스플랫폼 네이티브 툴킷은 Qt(LGPLv3/상용), GTK(LGPL), wxWidgets·FLTK(LGPL 파생)로 전부 프로젝트의 카피레프트 금지 정책과 충돌하고, 피하려면 Win32와 X11/Wayland를 각각 구현해야 합니다. 여기에 캐비닛에는 키보드·마우스가 없어 결국 게임 창 안에서 조작할 수 있어야 한다는 점과, 같은 레이어가 나중에 인게임 OSD가 된다는 점이 같은 방향을 가리켰습니다.
구조는 플랫폼 공용을 먼저 둡니다. 롬셋 가용성 판정과 설정 저장은
std::filesystem과 기존 INI 파서만 쓰므로 probe로 검증되고, 화면은 SDL3와 OpenGL만 씁니다.검증
- Win32 x86 Debug/Release
repiu,repiu_aot_probe빌드 성공. repiu_aot_probe --launcher: 5항목 통과 — 가용성 6개 상태, 카탈로그 커버리지, 설정 왕복과 잘못된 값 처리, 환경 변수 우선순위 전 조합, 자식 명령줄 인용(공백 포함 경로).- 회귀:
repiu.exe pumpit8이 런처를 거치지 않고 exit 0으로 완전 종료하며 런처 관련 로그가 0건입니다. - 사용자가 목록 표시, 선택 실행, 게임 종료 후 런처 복귀, 키보드 조작을 확인했습니다.
알려진 제한
- 창 크기와 전체화면은 아직 없습니다. 현재 창 생성이 게스트의 논리 해상도를 따르고 이를 덮어쓸 소비자가 없어, Glide backend의 창 생성 경로를 함께 손대야 합니다.
- 캐비닛 발판으로 런처를 조작하는 것은 아직 안 됩니다. 발판 입력은 게스트 실행 중 PIUIO 포트로 들어오는 것이라 경로가 다릅니다. 지금은 키보드·마우스·게임패드로 조작합니다.
- 실행 backend나 direct-return table 같은 진단 토글은 런처에 노출하지 않았습니다. 환경 변수로만 다룹니다.
-
2026-08-22
v0.0.160
이전 릴리스 v0.0.159 이후의 변경입니다. 이번 릴리스는 실행 성능에 집중합니다. 번역된
RET이 대부분의 경우 host로 넘어가지 않고 스스로 목적지를 찾도록 바꿔, 같은 장면에서 프레임이 +59.4% 늘었습니다.전체 릴리스 노트
이전 릴리스 v0.0.159 이후의 변경입니다. 이번 릴리스는 실행 성능에 집중합니다. 번역된
RET이 대부분의 경우 host로 넘어가지 않고 스스로 목적지를 찾도록 바꿔, 같은 장면에서 프레임이 +59.4% 늘었습니다.return 처리 비용을 먼저 재고, 다음에 없앴습니다
- Task 482: return handler를 서로 겹치지 않는 다섯 단계(entry 검증, 게스트 스택
target read와 bookkeeping, target 분류·resolution, megamorphic 정책·patch, guest
continuation)와 residual로 나누는 opt-in 계측
REPIU_AOT_RETURN_STAGE_PROFILE을 추가했습니다. - 그 계측이 기존 수치의 오류를 먼저 잡아냈습니다. 기존
kAotReturnbucket은HandleAotReturnTransfer안만 재고 있어 adapter 구간이 통째로 빠져 있었습니다. 실제 return 처리 비용은guest-run의 16.5~17.5%가 아니라 **약 27%**였습니다(3회 재현, 비 1.39). - 지배적인 단계는 없었습니다 —
resolve31.7%,patch27.5%,entry25.1%,read8.3%,continuation7.3%. 그리고 정책 관측의 99.4%가 megamorphic bypass로, 거의 모든 return이 4-entry PIC로 수렴하지 못하는 site에서 발생하고 있었습니다.
Megamorphic direct-return table (기본 켜짐)
- Task 499: 번역된
RET의 miss 경로 앞에서 공용 memo table을 조회해, 적중하면 host 전환 없이 원본 RET과 같은 스택 효과로 돌아갑니다. 실패하면 기존 경로가 한 바이트도 바뀌지 않은 채 이어집니다. - pumpit8 A/B(60초 × 3쌍, 계측 없이 토글만 변경)에서 프레임 37,385 → 59,586,
+59.38%(623 → 993 fps)였고 두 집단의 범위가 겹치지 않았습니다. 프레임당 삼각형은
+2.84%로 장면 동일성 기준(3%) 안이고
grDrawPoint는 여섯 실행 모두 정확히 같았습니다. - host return 왕복이 실행당 약 5,000만 회에서 약 2,900회로, return 처리 비용이
guest-run의 25.63%에서 **0.13%**로 내려갔습니다. REPIU_AOT_DIRECT_RETURN_TABLE=0으로 끄면 이 기능이 없던 때와 캐시 바이트가 정확히 같아집니다(473,078 B 대 526,083 B). 표 크기는REPIU_AOT_DIRECT_RETURN_TABLE_BITS로 8~18(기본 15)을 줍니다.- 정확성은 새 불변식을 만들지 않습니다. table 적중은 4-entry PIC 적중과 의미가 같으므로, 항목이 PIC 항목과 같은 조건에서만 살아 있으면 됩니다. 삽입은 host가 검증한 active hit만 받고, 무효화는 PIC guard를 되돌리는 바로 그 자리에서 table 전체를 지웁니다. 그 지점은 게스트 스레드가 이미 정지해 있어 lock도 generation stamp도 필요 없습니다.
Glide gate 비용의 실제 구성
같은 계측 작업에서 Glide gate(
guest-run의 23.7~24.7%)도 3회 재현으로 분해했습니다. 최대 구성은 렌더링이 아니라 호스트에 닿지 않는 crossing으로, bucket의 45~47% (guest-run의 10.7~11.6%)입니다. 게이트 진입의 **70.8~71.1%**가 setter elision으로 걸러지는 호출인데, elision은 호스트 왕복만 없앴을 뿐 crossing은 회당 약 1,790 cycle을 그대로 씁니다. 이 비용은 지금까지 측정된 적이 없습니다.검증
- Win32 x86 Debug/Release
repiu_exe,repiu,repiu_aot_probe빌드 성공. repiu_aot_probe --return-stage-profile8항목,--direct-return-table5항목 통과. 후자에는 emit한 바이트열을 실제로 실행해 적중 시 EIP·ESP가 원본 RET과 같고eax/ecx가 보존되는지 확인하는 항목이 포함됩니다(C3와C2 iw양쪽).- pumpit8 전체 probe Debug/Release 모두 exit 0.
- 실행 중 page retirement 36회 동안 table 무효화가 정상 동작했고, 모든 실행에서 return fallback 0, index scan 0이었습니다.
알려진 제한
- direct-return table의 승격 근거는 pumpit8 무인 attract 장면입니다. 게임플레이 장면에서
한 번 더 확인해 두는 것이 좋습니다. 회귀가 보이면
REPIU_AOT_DIRECT_RETURN_TABLE=0이 즉시 이전 동작으로 되돌립니다. - pumpitea 첫 구동에서 4.2초 만에 널 역참조 크래시 1건이 있었고 재현되지 않았습니다. 마운트가 external-extent 파일 89개를 조용히 건너뛰는 것이 유력한 원인이며, pumpite CHD 마운트 분석에 재개 절차와 함께 기록했습니다.
- 다음 성능 축은 Glide입니다 — crossing 앞으로 elision 판정 옮기기(약 11%)와 호스트 wake 지연(약 6%).
- Task 482: return handler를 서로 겹치지 않는 다섯 단계(entry 검증, 게스트 스택
target read와 bookkeeping, target 분류·resolution, megamorphic 정책·patch, guest
continuation)와 residual로 나누는 opt-in 계측
-
2026-08-20
v0.0.159
이전 릴리스 v0.0.156 이후의 변경입니다. 이번 릴리스는 호스트 키보드를 게임에 전달하는 경로를 정리합니다. 게임 창의 키 입력이 BIOS
INT 16h로 전달되고, PIUIO 발판과 캐비닛 버튼의 키 매핑을 롬셋별 설정 파일에서 바꿀 수 있으며, EEPROM이 롬셋마다 분리 저장됩니다.전체 릴리스 노트
이전 릴리스 v0.0.156 이후의 변경입니다. 이번 릴리스는 호스트 키보드를 게임에 전달하는 경로를 정리합니다. 게임 창의 키 입력이 BIOS
INT 16h로 전달되고, PIUIO 발판과 캐비닛 버튼의 키 매핑을 롬셋별 설정 파일에서 바꿀 수 있으며, EEPROM이 롬셋마다 분리 저장됩니다.창 키보드 BIOS 입력
- 게임 창의 SDL key down/up event를 원본 게임이 프레임마다 조회하는
INT 16hBIOS 경로에 연결했습니다. 기존 JAMMA 입력 경로는 그대로 병행 동작합니다. - 플랫폼 공용
hle::BiosKeyboard가 실기 BIOS data area와 같은 15-entry FIFO, legacy/enhanced AX 쌍, shift/lock 상태를 소유합니다. SDL host thread가 producer이고 guest thread가 consumer이므로 짧은 mutex 임계 구역으로 보호합니다. - Win32 SDL adapter가 US 101-key scan code, ASCII, Shift/Caps/Ctrl/Alt 조합, function/navigation/numeric keypad를 변환합니다. key repeat는 DOS typematic keystroke로 enqueue하고, 포커스를 잃으면 눌린 modifier를 해제하되 lock toggle은 유지합니다.
AH=00h/10h가 FIFO head를 소비하고AH=01h/11h는 소비 없이 ZF로 유무를 알리며,AH=02h/12h가 실제 shift flags를 반환합니다.
롬셋별 설정 파일
cfg/<롬셋 ID>.ini의[Input]섹션에서 PIUIO 발판과 캐비닛 버튼의 호스트 키를 롬셋마다 바꿀 수 있습니다. 키 이름은 실제 발판 위치를 따릅니다:P1_UP_LEFT,P1_DOWN_RIGHT,TEST,SERVICE,CLEAR,COIN1등 14개.- 설정 파일이 없으면 첫 실행에 만들어집니다. 모든 항목이 주석 처리된 템플릿이고 주석의
값은 현재 적용 중인 설정이므로, 바꾸려는 줄의
;만 지우면 됩니다. 기존 파일은 절대 덮어쓰지 않습니다. - 쉼표로 여러 키를 걸 수 있고(
P2_UP_LEFT = Keypad7, Home),Ctrl+F1같은 조합키를 지원합니다. 수식키를 붙인 설정은 정확히 일치해야 하고, 수식키가 없는 설정은 같은 키를 두고 겹치지 않는 한 수식키를 따지지 않습니다. NumLock/CapsLock/ScrollLock은 어떤 설정에도 영향을 주지 않습니다. - 값을 비우면 그 입력이 꺼집니다.
TEST =로 적으면 어떤 키로도 TEST가 동작하지 않습니다. - 롬셋 파일은 부모 롬셋 파일 위에 키 단위로 얹힙니다. 공용 설정은
cfg/pumpitup.ini에 두면 모든 타이틀에 적용됩니다. - 설정 파일 오타는 경고만 남기고 실행을 막지 않습니다. 로더 로그가 파일 이름과 줄 번호를 알려줍니다.
- 사용법은 docs/guides/romset-config-files.md에 있습니다.
입력 매핑 구조 정리
- 같은 키 매핑이 세 곳에 서로 다른 표현으로 중복되어 있던 것을 바인딩 표 하나로 합쳤습니다.
ScanJammaPort8와CaptureCurrentJammaPressedMask는 Win32 가상키로, SDL event 경로는 SDL keycode로 각자 정의하고 있어 한 곳만 고치면 조용히 어긋나는 구조였습니다. - 정식 키 타입은
SDL_Keycode, 수식키 마스크는SDL_Keymod입니다. SDL event 경로는 변환 없이 직접 비교하고, 폴링 경로만 가상키 변환 하나를 씁니다. - 포트·비트·논리 입력·로그 이름을 한 표로 합쳤습니다. 로그가 비트
0x02를P1-UpRight로 부르면서 코드 열거는kP1Down이던 불일치를 정정했고, 열거 순서는 타임라인 비트 위치이므로 바꾸지 않았습니다. - 기본 설정에서 호스트 키 조회 횟수는 이전과 동일합니다. 조합키를 쓰지 않으면 수식키를 아예 읽지 않으므로, Task 403이 측정한 포트 I/O 비용이 그대로 유지됩니다.
롬셋별 NVRAM
- 93C46 EEPROM 이미지를 MAME처럼
nvram/<롬셋 ID>/eeprom.dat에 저장합니다. 이전에는 22개 롬셋이 작업 디렉터리의eeprom.dat하나를 공유해, 한 타이틀에서 저장한 캐비닛 설정이 다른 타이틀에도 그대로 보였습니다. - 기존
eeprom.dat가 있으면 롬셋 디렉터리로 한 번 복사해 승계합니다. 지금까지 맞춰 둔 캐비닛 설정이 유지되며, 원본은 지우지 않습니다. REPIU_EEPROM_PATH전체 경로 오버라이드는 의미가 그대로입니다. 벤치마크 스크립트와 측정 가이드의 실행별 EEPROM 격리가 계속 동작합니다.cfg/와nvram/을.gitignore에 추가했습니다.
검증
- Win32 x86 Debug의
repiu_exe,repiu,repiu_aot_probe빌드가 성공했습니다. repiu_aot_probe --romset-config: checks=94 failures=0. INI 파싱, 별칭과 입력 끄기, 이름 표 건전성, 조합키 파싱과 거부 형태, 수식키 판정과 경쟁 규칙과 lock 비트 배제, 레이어링, 생성 파일 왕복과 미덮어쓰기를 덮습니다. 회귀 가드로 구현 이전의 하드코딩 매핑을 직접 전사해 가상키 단위로 대조합니다.repiu_aot_probe --nvram-path: checks=14 failures=0. 오버라이드 우선순위, 디렉터리 실제 생성, 롬셋 간 경로 분리, 기존 파일 승계와 내용 일치와 원본 보존을 덮습니다.repiu_aot_probe --bios-keyboard-input: 성공.translated=true,fifo=true,modifiers=true,overflow=1.repiu_aot_probe build/runtime_mounts/pumpit1/PIU/PIU.EXE전체 단정이 exit 0으로 통과했습니다.- 사용자가 실제
pumpipx3실행에서 설정 파일 생성과 키 매핑 동작을 확인했습니다. - 기존 C4819 코드 페이지 경고만 남았습니다.
알려진 제한
COIN2와 캐비닛 입력 포트0x02AB는 지원하지 않습니다. 해당 I/O 포트 비트가 확정되지 않았습니다.- 조합키는 캐비닛 버튼용입니다. 발판 입력에 걸면 창 이벤트 경로에서 수식키 판정이 기본 키를 누른 순간으로 고정되므로 권장하지 않습니다.
[Input]외의 섹션은 아직 없습니다. 인식하지 않는 섹션은 조용히 무시되므로, 나중 버전용 파일이 이전 빌드를 막지 않습니다.- 롬셋별 NVRAM의 런타임 확인(실행 후 파일 생성과 설정 승계)은 아직 사용자 환경에서 검증되지 않았습니다.
- 게임 창의 SDL key down/up event를 원본 게임이 프레임마다 조회하는
-
2026-08-17
v0.0.156
이전 릴리스 v0.0.155 이후의 변경입니다. 이번 릴리스는
pumpitp3타이틀 화면에서 확인된 paletted texture 갱신 병목을 제거하면서, palette가 기존 P_8/AP_88 texture에 반영되는 Glide 의미를 유지합니다.전체 릴리스 노트
이전 릴리스 v0.0.155 이후의 변경입니다. 이번 릴리스는
pumpitp3타이틀 화면에서 확인된 paletted texture 갱신 병목을 제거하면서, palette가 기존 P_8/AP_88 texture에 반영되는 Glide 의미를 유지합니다.Paletted texture 지연 갱신
- palette download마다 보존된 모든 P_8/AP_88 texture를 CPU에서 RGBA로 다시 디코드하고
glTexImage2D로 전량 재업로드하던 eager refresh를 제거했습니다. - Win32 OpenGL backend는 실제 palette byte가 바뀔 때만 generation을 증가시킵니다. byte-identical palette download는 generation 변경과 texture 작업을 모두 생략합니다.
- 각 indexed texture는 마지막 적용 palette generation을 기록합니다. textured draw에서
실제로 sampling되는 현재 texture가 오래된 경우에만 보존 index source를 다시 디코드하고
기존 GPU allocation을
glTexSubImage2D로 갱신합니다. - palette 변경은 다음 sampling 전에 반영되므로 Task 483의 texture/palette 독립 수명과 P_8 불투명 alpha, AP_88 texel alpha 계약을 유지합니다.
성능 계측
- 종료 texture census에 palette download의 전체/변경/동일 횟수를 추가했습니다.
- 지연 갱신 성공/실패 수, source/RGBA byte, CPU decode 시간과 OpenGL upload 호출 시간을 출력합니다.
- 기존 Task 483 palette 성능 TODO를 이번 구현과 문서로 완료했습니다.
검증
- Win32 x86 Debug/Release의
repiu_aot_probe와repiu빌드가 성공했습니다. pumpitp3/PIU/PIU.EXE전체 AOT probe가 두 구성에서 exit 0으로 통과했습니다.glide_texture_table_stack_probe=pass,glide_texture_census_palette_refresh_profiled=true,glide_texture_census_all=true를 확인했습니다.- 사용자가 실제
pumpitp3타이틀 화면에서 성능 개선을 확인했습니다. - 기존 C4819 코드 페이지 경고와 LNK4217 경고만 남았습니다.
- palette download마다 보존된 모든 P_8/AP_88 texture를 CPU에서 RGBA로 다시 디코드하고
-
2026-08-15
v0.0.155
이전 릴리스 v0.0.154 이후의 변경입니다. 이번 릴리스는 dynamic traced 실행 경로의 DOS 파일 생성 디스패치를 복구하고, 640×480 논리 화면이 확대될 때 Glide point 크기도 viewport에 비례하도록 조정합니다.
전체 릴리스 노트
이전 릴리스 v0.0.154 이후의 변경입니다. 이번 릴리스는 dynamic traced 실행 경로의 DOS 파일 생성 디스패치를 복구하고, 640×480 논리 화면이 확대될 때 Glide point 크기도 viewport에 비례하도록 조정합니다.
Dynamic DOS 파일 생성 복구
- dynamic traced
INT 21h/AH=3Ch요청을 공용 DOS HLE 처리기로 전달합니다. 파일 생성 의미, 반환 레지스터와 EIP 진행은 기존 VFS 구현이 계속 단일하게 관리합니다. - 파일 close 시 read stream뿐 아니라 write stream cache도 해제합니다. 생성한 파일을 닫은 뒤 scratch 디렉터리를 정상적으로 정리할 수 있습니다.
- 실제
CD 21instruction과 guest 경로를 사용하는 probe를 추가해 traced dispatcher부터 파일 생성과 정리까지의 통합 경계를 검증합니다.
Viewport 비례 Glide point 크기
- 원본 640×480 논리 화면과 drawable viewport의 가로·세로 배율 중 작은 값을 Glide point 크기로 사용합니다. 1280×960 viewport에서는 기존 1×1 dot가 2×2 physical pixel로 표시됩니다.
- 축별 배율이 다를 때 작은 값을 선택해 dot의 정사각형 형태를 유지하고, 축소 화면에서는 최소 1 pixel을 보장합니다.
- 계산된 크기를 OpenGL이 보고한 aliased point 지원 범위로 제한하고, 직접 point와 batch point 렌더링에 동일하게 적용합니다.
검증
- Win32 x86 Release의
repiu_aot_probe,repiu_glide_render_probe,repiu빌드가 성공했습니다. - AOT probe는
dos_file_create_traced_dispatch=true,dos_file_create_cleanup=true,dos_file_create_all=true로 통과했습니다. - Glide render probe는 1×, 2×, 3×, 비정형 viewport, 축소와 무효 크기의 point 배율 계산을 통과했습니다.
- 기존 C4819 코드 페이지 경고와 optional LibUSB 탐색 경고만 남았습니다.
남은 실기 확인
- 자동 검증은 통과했지만 실제 게임의 다음 실행 frontier와 생성된
ERRLOG.txt내용은 아직 확인하지 않았습니다. pumpit8서비스 화면에서 2× viewport의 dot가 실제로 2×2 pixel로 표시되는지 육안 확인이 필요합니다.
- dynamic traced
-
2026-08-14
v0.0.154
이전 릴리스 v0.0.153 이후의 변경입니다. 이번 릴리스는 DOS 날짜 설정을 실행별 가상 상태로 지원하고, MAME Pump It Up target profile을 22개 전체 카탈로그로 정리하며, clone의 CAT702 자산을 부모 ROM 세트에서 안전하게 상속합니다.
전체 릴리스 노트
이전 릴리스 v0.0.153 이후의 변경입니다. 이번 릴리스는 DOS 날짜 설정을 실행별 가상 상태로 지원하고, MAME Pump It Up target profile을 22개 전체 카탈로그로 정리하며, clone의 CAT702 자산을 부모 ROM 세트에서 안전하게 상속합니다.
가상 DOS 시스템 날짜
INT 21h/AH=2BhSet System Date를 구현했습니다. 유효한 날짜는 host local date 대비 일수 offset으로 실행 context에 저장됩니다.AH=2AhGet System Date는 설정된 가상 날짜와 해당 요일을 반환합니다. 실제 Windows 시스템 날짜는 변경하지 않습니다.- 일반 DOS HLE와 traced/AOT 경로가 같은 계약을 사용하며, 날짜 검증과 set/get round trip을 probe로 고정했습니다.
- 사용자가 실제 실행에서 날짜 설정 이후 진행이 정상임을 확인했습니다.
22개 Pump It Up target profile
- 기존 14개 MAME profile을
xtom3d.cpp의 22개GAME카탈로그 순서로 확장했습니다. 누락됐던pumpit2a,pumpit3a,pumpitpru,pumpitea,pumpipx2p,pumpitp3a,pumpipx3a,pumpipx3b를 추가했습니다. - display name에 버전·연도·날짜를 보존하고 브랜드 표기를
Pump It Up으로 통일했습니다. - 임시
piu_1stprofile을 제거하고 runner, supervisor와 analyzer의 기본 target을pumpit1로 변경했습니다.
부모 ROM 세트 CAT702 fallback
- 모든 PIU profile에 MAME의 직접 parent ROM 세트 ID를 명시했습니다.
- CAT702은 현재 ZIP의 현재 이름, 같은 ZIP의 부모 이름, 형제 부모 ZIP의 부모 이름 순서로 조회합니다. 항목 없음일 때만 다음 단계로 진행하며 archive, 추출과 CRC 오류는 fallback으로 숨기지 않습니다.
- 실제
pumpitpru실행에서pumpitpr.cat7028바이트 항목을 선택해 PIU10을 초기화했습니다. 관찰된 PIU10 port I/O 33회가 모두 처리되고 Glide 초기화까지 진행했습니다.
검증
- Win32 x86 Debug/Release 전체 빌드가 성공했습니다.
- 두 구성의
pumpipx3전체 AOT probe가 exit 0이며piu10_target_profiles=true,dos_date_probe=pass,coherence_all=true를 확인했습니다. - Debug/Release
--piu10probe에서 profile, CAT702 vector, MP3 ring과 frame audit가 모두 통과했습니다. - 기존 C4819와 LNK4217 경고만 남았습니다.
-
2026-08-13
v0.0.153
이전 릴리스 v0.0.152 이후의 변경입니다. 이번 릴리스는
pumpipx3에서 발견된 Glide texture-table 호출 규약을 정정하고, P_8/AP_88 indexed texture가 나중에 내려온 palette를 사용하도록 해 손상된 색상을 복구합니다.전체 릴리스 노트
이전 릴리스 v0.0.152 이후의 변경입니다. 이번 릴리스는
pumpipx3에서 발견된 Glide texture-table 호출 규약을 정정하고, P_8/AP_88 indexed texture가 나중에 내려온 palette를 사용하도록 해 손상된 색상을 복구합니다.Glide texture-table ABI 정정
_GRTEXDOWNLOADTABLE@12의 guest stack frame을 반환 주소, TMU, table type, data pointer의 네 dword로 해석합니다. 기존 구현은 반환 주소를 포함해 세 dword만 읽어 type과 data를 한 칸씩 잘못 해석하고, 복귀 시에도 16바이트가 아닌 12바이트만 정리했습니다.- 성공, 지원하지 않는 table type, 읽기 실패를 포함한 handled path 모두 stdcall 계약대로
ESP += 16을 적용합니다. - 실제 로그 frame과 짧은/null frame을
glide_texture_table_stack_probe로 고정했습니다.
indexed texture 색상 복구
- 표준 Glide palette entry의 상위 8비트는 alpha가 아니므로 무시하고 alpha 255로 변환합니다. P_8은 불투명 palette RGB를 사용하며 AP_88은 texel 상위 8비트의 alpha를 유지합니다.
- texture upload와 palette upload의 수명이 분리된 Glide 의미를 보존합니다. Backend가 P_8/AP_88 원본 index를 보존하고 palette download 시 기존 indexed texture를 새 palette로 다시 디코드·업로드합니다.
- 사용자 실기 확인에서 기존에 손상됐던 palette 색상이 정상으로 복구됐습니다.
검증
- Win32 x86 Debug/Release에서
repiu_aot_probe와repiu빌드가 성공했습니다. - 두 구성의 전체 probe가 exit 0이며
glide_texture_table_stack_probe=pass와glide_texture_census_all=true를 확인했습니다. - 기존 C4819와 LNK4217 경고만 남았습니다.
알려진 성능 제한
- 색상 복구 후 사용자 실기에서 성능이 크게 저하됐습니다.
- 현재 구현은 palette download마다 모든 보존 P_8/AP_88 texture를 CPU에서 RGBA로 변환하고
glTexImage2D로 재업로드합니다. 변경 없는 palette 생략, 실제 사용 texture의 지연 갱신, GPU indexed sampling은 아직 구현하지 않았습니다. - 이번 릴리스는 정확성을 우선하며 이 경로를 유지합니다. 계측과 최적화는
docs/TODO.md의 Task 483 후속 항목으로 문서화했습니다.
-
2026-08-12
v0.0.150
이전 릴리스 v0.0.149 이후의 변경입니다. 이번 릴리스는 실행 probe에 게스트 메모리 구간 dump 진단을 추가하고, 그 진단으로 확정한 원인에 따라 DOS 저메모리 읽기 대행을 문자열 명령까지 확장해
pumpit8의 배경 로딩 중 종료를 제거합니다.전체 릴리스 노트
이전 릴리스 v0.0.149 이후의 변경입니다. 이번 릴리스는 실행 probe에 게스트 메모리 구간 dump 진단을 추가하고, 그 진단으로 확정한 원인에 따라 DOS 저메모리 읽기 대행을 문자열 명령까지 확장해
pumpit8의 배경 로딩 중 종료를 제거합니다.실행 probe 메모리 구간 dump
REPIU_EXECUTION_PROBE_DUMP_*로 probe first-hit 시점의 게스트 메모리 구간을 파일에 보존합니다. 기준은 레지스터 이름 또는 절대 주소이며, 구조체 안의 포인터를 따라가는 간접 역참조와 최대 1 MiB 복사를 지원합니다.- 복사 buffer는 실행 시작 전에 한 번 할당해, first-hit recorder가 예외 처리 중 할당하지 않습니다. 파일 기록은 게스트 스레드가 멈춘 뒤 수행하며 VEH 안에서 파일 I/O를 하지 않습니다.
- 바이트는 telemetry snapshot을 거치지 않고, snapshot에는 포착·기록 상태와 주소만 전달합니다.
- 전체 구간이 게스트 arena 안에서 읽을 수 있을 때만 복사하고, 부분 범위나 host 임의 주소는 읽지 않습니다. 환경 변수가 없으면 기존 실행 경로가 변하지 않습니다.
- 반복 수행 절차는
docs/guides/execution-probe-memory-dump.md에 있습니다.
DOS 저메모리 문자열 명령 대행
HandleGuestLowMemoryReadFault는MOV,MOVZX,MOVSX만 처리하고 나머지 mnemonic을 거부해, 저메모리를 읽는 문자열 명령의 접근 위반이 처리되지 않았습니다.SCAS,LODS,CMPS의 저메모리 읽기를 대행하는 전용 모듈을 추가했습니다.MOVS는 기존 전담 handler가 있어 제외하고,STOS는 쓰기이므로 이 읽기 대행 범위에서 제외했습니다.REP계열이 반복 사이에서 재개 가능하다는 성질을 이용해, 반복이 끝났을 때만EIP를 전진시키고 남았으면 그대로 둡니다. 따라서 handler가 저메모리 경계를 추적하지 않습니다.- 폭은 operand width가 아니라 mnemonic에서 얻습니다.
66hprefix가 mnemonic 자체를 바꾸므로 매핑이 정확합니다. SSEcmpsd와 문자열CMPSD가 mnemonic을 공유하므로ZYDIS_CATEGORY_STRINGOP으로 먼저 거릅니다. SetCompareFlags8을 폭 인자를 받는SetCompareFlags로 일반화하고 기존 함수는 이를 호출하도록 바꿨습니다. 8-bit 결과는 이전과 같습니다.- address size가 32-bit가 아니거나, 접근이 저메모리 경계를 걸치거나,
CMPS반대편이 게스트 범위 검사를 통과하지 못하거나,REP인데ECX가 이미 0이면 대행하지 않고 기존 진단이 원인을 그대로 보고하게 둡니다. - 특정 target이나 실행 파일 주소에 대한 동작 우회는 추가하지 않았습니다.
pumpit8 배경 로딩 종료 해소
BGA/083.DAT의BR01_01.tga(실제 PNG)를 읽던 중 발생하던0xC0000005종료가 사라졌습니다.- 원인은 자산의
iCCPchunk에 있는 zlib datastream이 4바이트 adler32 없이 작성된 것이며, chunk CRC32가 저장값과 계산값 모두0x8FC1BA08로 일치해 원본 그대로 메모리에 도달했음을 확인했습니다. rePIU의 파일 I/O, RES 복호화, PNG 전달에는 결함이 없습니다. - 원본 libpng
1.0.6은png_decompress_chunk의 null 반환을 검사하지 않고 prefix0x17을 더해repne scasb로 역참조합니다. 정적 역어셈블로call과 fault 사이에 조건 분기가 없음을 확인했습니다. - 분석은
docs/analysis/pumpit8-bga-iccp-crash.md에 누적했습니다.
검증
- Win32 x86 Debug 빌드와 Release 빌드가 성공했습니다.
- Debug
pumpit8실행에서 문자열 대행 2,522회, 반복 2,522회, 마지막 EIP0x040E5D0D, 주소0x00000017, mnemonicSCASB, 예외 0건을 기록했습니다. - 같은 실행이
BGA/083.DAT를 지나AUDIO/004.AUD까지 진행했고 약 6.9 FPS로 렌더링했습니다. 종료는 접근 위반이 아니라 관측 종료를 위한 SDL 종료 요청입니다. - OpenWatcom 샘플 819개를 빌드하고 baseline과 같은 Release 구성으로 suite를 재측정해 RunPassed 528, 회귀 0건, 신규 통과 0건을 확인했습니다.
알려진 제한
pumpit8은 이 지점만 통과했을 뿐 완주를 확인한 것이 아니며, 이후 구간은 별도 관측 대상입니다.- DOS/4GW에서 linear
0x17이 실제로 읽힌다는 것은 보호 모드 익스텐더의 표준 동작에서 온 추정이며, 원본 환경에서 직접 관측하지 않았습니다. - 저메모리 대행은 읽기에 한정합니다.
STOS처럼 저메모리에 쓰는 경로는 아직 대행하지 않습니다. 67hprefix가 붙은 16-bit address size 문자열 명령은 관측된 바 없어 구현하지 않았습니다.- 샘플 suite는 저메모리 문자열 읽기 경로에 도달하지 않으므로, 회귀 0건은 영향이 없음을 확인한 것이지 이 경로를 검증한 것이 아닙니다.
-
2026-08-11
v0.0.149
이전 릴리스 v0.0.148 이후의 변경입니다. 이번 릴리스는 PIU10 타이틀별 CAT702 구성을 분리하고, 호출 래퍼를 사용하는 포트 I/O와 MP3 feeder에도 주소·바이너리 독립적인 batching을 확장하며, 후속 PIU10 타깃 프로파일 7종을 추가합니다.
전체 릴리스 노트
이전 릴리스 v0.0.148 이후의 변경입니다. 이번 릴리스는 PIU10 타이틀별 CAT702 구성을 분리하고, 호출 래퍼를 사용하는 포트 I/O와 MP3 feeder에도 주소·바이너리 독립적인 batching을 확장하며, 후속 PIU10 타깃 프로파일 7종을 추가합니다.
TargetProfile과 CAT702
TargetProfile::enable_cat702을 PIU10 ISA 보드 capability와 분리했습니다.- CAT702이 꺼져도 PIU10 flash, MP3와 DAC는 계속 동작하며, CAT702 data-out은 0이고 직렬
입력은 무시됩니다. 해당 profile은
<target>.cat702자산도 요구하지 않습니다. pumpitpc에서 CAT702을 끄면 실제 Lock Error 키 대기 경로로 진입하고, 기본값을 다시 켜면 같은 지점을 지나 계속 실행됨을 확인했습니다. 이는 profile 설정이 실제 보안 검사 결과를 제어한다는 회귀 검증입니다.- CAT702 transform과 challenge-response 알고리즘은 타이틀별로 보정하지 않았습니다.
호출 래퍼형 포트 I/O 지연 루프
- 입력 포트 래퍼의 register 보존, port 전달, 상대
call, guest stack 반환 주소와 caller loop를 검증한 뒤, 결과를 폐기하는 반복만 일괄 처리합니다. - 마지막 반복과 flags 계산은 원본 guest가 수행하며, matcher는 target 이름이나 고정 코드 주소를 사용하지 않습니다.
pumpitpcRelease A/B에서 전체 port I/O는 244,970회에서 14,110회로 약 94.2%, privileged-instruction 예외는 236,962회에서 7,936회로 약 96.7% 감소했습니다.
호출 래퍼형 PIU10 MP3 feeder
- 기존 direct
OUT DX,ALmatcher를 유지하면서 register 보존 byte-output 래퍼와 feeder call/return 관계를 검증하는 후순위 matcher를 추가했습니다. - source cursor, frame count,
ECX, backward loop와 service 조건을 명령 operand에서 추출하므로 profile, 실행 파일 주소나 해시에 의존하지 않습니다. - 감사 계획도 compressed inflight의
0xE00DEMAND경계에서 끊어 scalar status polling과 같은 반환 지점을 보존합니다. - 실제
pumpitpc감사에서 3,700개 segment가 연속 일치했고, 일반 실행은 140,795 byte 중 136,595 byte(약 97.0%)를 batch 처리하면서 drop과 starvation을 기록하지 않았습니다.
추가 타깃 프로파일
- 기존
pumpite뒤에pumpitpr,pumpitpx,pumpit8,pumpitp2,pumpipx2,pumpitp3,pumpipx3을 추가했습니다. - 신규 프로파일은 공용
piu_common, CHD mount, JAMMA/YMZ280B, PIU10 ISA와 CAT702 경로를 사용하며 MP3 시작 지연 기본값은 0 ms입니다. - Debug/Release profile probe가 등록 순서, 단일 등록, 경로, ROM-set id와 capability를 검증합니다.
검증
- Win32 x86 Debug와 Release 빌드가 성공했습니다.
- Debug/Release
repiu_aot_probe --piu10이 통과했습니다. - 신규 7개 로컬 ROM ZIP은 PIU10/CAT702 필수 entry 검증을 통과했습니다.
pumpitpc의 일반 실행, batching A/B와 MP3 감사 실행을 완료했습니다.
알려진 제한
- 저작권이 있는 ROM ZIP과 CHD는 배포하지 않습니다.
- 신규 7개 타깃은 로컬 CHD가 없어 profile과 ZIP까지만 검증했으며, ISO mount와 guest 실행은 아직 검증하지 않았습니다.
- matcher가 wrapper, loop, memory alias 또는 runtime 범위를 완전히 검증하지 못하면 기존 scalar 경로를 유지합니다.
-
2026-08-11
v0.0.148
이전 릴리스 v0.0.147 이후의 변경입니다. 이번 릴리스는
pumpitpc의 CAT702 보안 검사에서 발생하던Lock Error!!!를 수정합니다. CAT702 알고리즘을 바이너리에 맞춰 보정한 것이 아니라, 여러 장치가 공유하는 Watcom 포트 출력 래퍼를 보존하는 공용 포트 I/O 정책으로 해결했습니다.전체 릴리스 노트
이전 릴리스 v0.0.147 이후의 변경입니다. 이번 릴리스는
pumpitpc의 CAT702 보안 검사에서 발생하던Lock Error!!!를 수정합니다. CAT702 알고리즘을 바이너리에 맞춰 보정한 것이 아니라, 여러 장치가 공유하는 Watcom 포트 출력 래퍼를 보존하는 공용 포트 I/O 정책으로 해결했습니다.pumpitpc CAT702 보안 검사
pumpitpc가 호출하는 공용OUT DX,AX래퍼는 시작 과정의0x2AC,0x2A6출력과 이후 PIU10 CAT702 포트0x2D4,0x2D6,0x2DA출력을 모두 담당합니다.- 기존 HLE는 무시하거나 관찰만 하는 첫 출력에서 래퍼의
OUT명령을 NOP로 영구 패치했습니다. 이 때문에 이후 CAT702 직렬 트랜잭션이 실행되지 않아 게임이 Lock Error 경로로 진입했습니다. - 이제 무시·유예 출력은 게스트 코드를 변경하지 않고 현재 EIP만 전진합니다. 해당 트랜잭션만 버리고 공용 래퍼는 다음 DX 포트 호출을 위해 그대로 유지됩니다.
- 이 정책은 target ID, 실행 파일 주소, 특정 포트 번호를 조건으로 사용하지 않으므로 같은 형태의 동적 DX 출력 래퍼에 공통으로 적용됩니다.
CAT702 모델 검증
- 실제 transform
F0 1C FE 03 81 40 38 F8, challengeD0 AA C9 F8 96 4C D0 DE B6 0B, response7D 77 FE EA 8B 7A 55 7D 5F 1E를 회귀 probe에 추가했습니다. - 게스트의 LSB-first 반전 입력, MSB-first 응답 누적, 추가 clock, 인접 바이트 정렬과 최종 bit reversal을 포함하면 기존 CAT702 HLE 모델이 실제 응답과 정확히 일치합니다.
- 따라서 CAT702 transform, bit order 또는 게임 바이너리별 보정은 추가하지 않았습니다.
검증
- Win32 x86 Debug 빌드가 성공했습니다.
- PIU10 실제 CAT702 벡터 probe와 전체
pumpitpcAOT probe가 통과했습니다. - 기본 환경의
pumpitpc가 기존 오류 종료 시점을 지나 50초 이상 계속 실행됨을 확인했습니다.
알려진 제한
- 저작권이 있는 ROM ZIP과 CHD는 배포하지 않습니다.
- 다른 실행 파일이 사용하는 추가 CAT702 명령 형식은 별도의 호환성 검증 대상입니다.
-
2026-08-10
v0.0.147
이전 릴리스 v0.0.146 이후의 변경입니다. 이번 릴리스는 pumpito 로딩 중 수백만 번 예외 경로로 빠지던 zlib DEFLATE 상태 분기를 AOT에서 직접 실행하도록 개선합니다.
전체 릴리스 노트
이전 릴리스 v0.0.146 이후의 변경입니다. 이번 릴리스는 pumpito 로딩 중 수백만 번 예외 경로로 빠지던 zlib DEFLATE 상태 분기를 AOT에서 직접 실행하도록 개선합니다.
pumpito 로딩 성능
- pumpito의 내장 zlib 1.1.3
inflate_codes()가 사용하는 byte 상태 jump table을 AOT가 인식합니다. 해당 분기는 기존 로그에서 약 1,226만 회 HLE dispatcher를 왕복한 로딩 병목이었습니다. - 새 matcher는
cmp low-r8, bound; jnbe; and parent-r32, 0xff; jmp cs:[parent-r32*4+table]의 register 관계와 mask를 검증한 뒤 기존 native jump-table emitter로 연결합니다. - pumpito 전용 주소나 실행 파일 이름, zlib 함수 이름을 조건으로 사용하지 않습니다. 같은 machine-code 계약을 사용하는 다른 바이너리에도 적용되는 공용 AOT 최적화입니다.
- mask나 destination register가 다르거나 high-byte register를 비교하거나 정규화가 생략되면 기존 dispatcher 경로로 안전하게 복귀합니다.
검증
- 실제 pumpito
PIU.EXE의inflate_codes()구간을 직접 entry로 계획했을 때 10개 target의 native jump table 1개가 생성되고 HLE boundary는 0개였습니다. - synthetic probe에서 정상 byte-guard 형태, 기존 32-bit guard 형태와 네 가지 fail-closed 변형을 검증했습니다.
- Win32 x86 Release의
repiu와repiu_aot_probe빌드 및 전체 probe suite가 통과했습니다.
알려진 제한
- 로딩 시간과 exception 감소량은 사용자 게임 자산으로 새 실행 로그를 받아 최종 측정해야 합니다. 정적 계획과 code-cache 생성은 검증됐지만 릴리스 노트에는 아직 실측 개선 배수를 기재하지 않습니다.
- 저작권이 있는 ROM ZIP과 CHD는 배포하지 않습니다.
- pumpito의 내장 zlib 1.1.3
-
2026-08-09
v0.0.146
이전 릴리스 v0.0.143 이후의 변경입니다. 이번 구간은 후기 PIU 타깃 지원과 PIU10 사운드 보드 HLE에 집중했습니다.
pumpito,pumpitc,pumpitpc,pumpite에서 원본 게임 코드를 그대로 실행하면서 MP3 음악과 화면 진행을 함께 처리하는 경로가 추가되었습니다.전체 릴리스 노트
이전 릴리스 v0.0.143 이후의 변경입니다. 이번 구간은 후기 PIU 타깃 지원과 PIU10 사운드 보드 HLE에 집중했습니다.
pumpito,pumpitc,pumpitpc,pumpite에서 원본 게임 코드를 그대로 실행하면서 MP3 음악과 화면 진행을 함께 처리하는 경로가 추가되었습니다.새 타깃과 CHD 지원
- MAME CHD profile에 **
pumpito,pumpitc,pumpitpc,pumpite**를 추가했습니다. 기존pumpit1,pumpit2,pumpit3와 함께 총 7개pumpit*profile을 선택할 수 있습니다. - Mode1 cooked-sector CHD와 data-track LBA/ISO extent 보정을 지원하여
pumpite처럼 data track이 디스크 시작점에 있지 않은 이미지도 ISO9660 mount할 수 있습니다. - mount cache는 CHD identity가 같으면 재사용하며, 실행 파일은 각 profile의
build/runtime_mounts/<target>/PIU/PIU.EXE에서 시작합니다. - JAMMA 입력 보드는 모든
pumpit*profile에, PIU10 ISA flash·CAT702·MP3 capability는 실제 하드웨어를 사용하는pumpito,pumpitc,pumpitpc,pumpite에만 활성화됩니다.
PIU10 ISA 보드 HLE
- PIU10의 20-bit flash address, read-only
piu10.u8, 목적지 선택, status source와 CAT702 PIU 직렬 보안 장치를 플랫폼 공용 HLE로 구현했습니다. - ROM ZIP에서
piu10.u8과<target>.cat702를 CRC 검증 후 추출합니다. 지원하지 않는 profile에는 이 장치를 노출하지 않으므로pumpit1~pumpit3의 기존 경로와 분리됩니다. - 구현은 원본 실행 파일이나 게임 로직을 수정하지 않고 ISA port I/O 경계만 대체합니다.
MAS3507D MP3 streaming HLE
- PIU10이 보내는 MPEG byte stream을 minimp3 + SDL3로 재생합니다. MAME의 MAS3507D 구현은 장치 계약 참고에만 사용했으며 코드는 포함하지 않았습니다.
- 완전한 곡을 받은 뒤 재생하는 방식이 아니라 MPEG frame이 준비되는 대로 decode합니다. persistent decoder와 별도 audio worker를 사용하므로 음악 재생 중에도 원본 게임과 화면이 계속 진행됩니다.
- 세 개의 호환 header로 stream sync를 확정하고 정확히 한 frame만 decoder에 전달하여, FIFO 경계의 불완전한 다음 header가 현재 frame을 손상시키지 않게 했습니다.
- MAS3507D
DEMAND는 4 KiB physical ring뿐 아니라 worker staging까지 포함한 compressed inflight를 기준으로 계산합니다. 이 backpressure는 guest가 HLE 출력보다 수 초 앞서 데이터를 밀어 넣어 곡 중간에 재생이 끊기던 문제를 해결했습니다. - frame-sync status는 decode-ahead 시점이 아니라 SDL queue의 실제 재생 위치에 맞춰 전이합니다.
DAC3350A 출력 제어
- PIU10 destination
0x010의 SDA/SCL transaction에서 DAC3350A AVOL을 복원합니다. - AVOL의 mute와 dB 단계를 SDL audio-stream gain에 반영합니다. 이미 queue된 PCM에도 출력 시점의 gain이 적용되며, 압축 FIFO나 MPEG decoder 상태는 변경하지 않습니다.
- MP3 pause는 구현하지 않았습니다.
MP3 입력 성능과 범용화
- 반복적인
OUT DX,ALfeeder tail을 한 번에 전송하여 byte마다 HLE dispatcher를 왕복하던 병목을 줄였습니다. - batch matcher는 target 이름, 고정 EIP, LE object 번호나 data offset을 사용하지 않습니다. 현재 명령의 load/increment/store alias와 register def-use, branch 경계를 검증하므로 서로 다른 compiler register allocation에도 같은 장치 계약을 적용합니다.
- 실기 로그에서
pumpitc는 전체 MP3 입력의 약 98.6%,pumpite는 첫 checkpoint의 **약 98.7%**를 batch 처리했습니다. 검증에 실패하면 guest 상태를 바꾸지 않고 기존 scalar byte path로 복귀합니다.
MP3 지연과 진단
- 모든 PIU10 profile의 MP3 시작 지연 기본값은 0 ms입니다.
- 필요하면
REPIU_PIU10_MP3_LATENCY_MS=0..500으로 실행별 무음 지연을 지정할 수 있습니다. REPIU_PIU10_DAC_AUDIT=1은 DAC transaction 시점의 PCM queue, device buffer, compressed ring, decoder pending과 inflight를 기록합니다.REPIU_PIU10_MP3_STREAM_AUDIT=1은 producer와 consumer stream을 독립적인 4 KiB hash로 비교합니다. 두 진단 기능은 기본적으로 꺼져 있습니다.
서드파티와 라이선스
- PIU10/CAT702 장치 계약은 BSD 3-Clause인 MAME 소스를 참고했습니다.
- MP3 frame decode에는 CC0 1.0 Universal인 upstream minimp3 고정 commit을 사용합니다.
- MAME emulator, CPU/machine framework 또는 MAS3507D wrapper 코드는 포함하지 않습니다.
알려진 제한
- 저작권이 있는 ROM ZIP과 CHD는 배포하지 않으며 사용자가 합법적으로 보유한 자산을 직접 배치해야 합니다.
- CI에는 게임 자산이 없으므로 실제 음악·화면 동시 진행은 로컬 실기 검증 대상입니다.
- 프로젝트는 여전히 연구·개발 단계이며 완성된 게임 런처가 아닙니다.
포함된 버전
v0.0.144·v0.0.145·v0.0.146 - MAME CHD profile에 **
-
2026-08-07
v0.0.143
이전 릴리스 v0.0.136 이후의 변경입니다. 이번 구간의 중심은 성능입니다 — pumpit2 gameplay 기준 69.3 → 107.2 fps (+54.7%).
전체 릴리스 노트
이전 릴리스 v0.0.136 이후의 변경입니다. 이번 구간의 중심은 성능입니다 — pumpit2 gameplay 기준 69.3 → 107.2 fps (+54.7%).
⚠️ 실행 파일 이름이 바뀌었습니다
repiu_loader_win32.exe→repiu.exe. 스크립트나 바로가기가 옛 이름을 가리키고 있다면 갱신하십시오.성능
항목 결과 inline cache 패치의 스레드 왕복 제거 fps +54.7% (69.3 → 107.2). 간접 call/jump 미스마다 14바이트를 쓰려고 워커 스레드를 깨우고 기다리던 것을 게스트 스레드가 직접 하도록 바꿨습니다. 프레임당 약 350회였고, 게스트 스레드 표본의 30.4%가 그 대기였습니다 Glide setter 상태 생략 같은 값으로 다시 부르는 setter의 host 왕복을 건너뜁니다. 현재 생략률 89.6%. grDitherMode는 호출당 비용이 95% 줄었습니다draw 배칭 연속된 primitive를 모아 한 번에 제출합니다. 평균 배치 2.0 → 32.1 primitive 세 가지 모두 기본으로 켜져 있고, 각각 환경변수로 끌 수 있습니다 (
REPIU_AOT_INLINE_CACHE_PATCH_INLINE=0,REPIU_GLIDE_SETTER_ELIDE=0,REPIU_GLIDE_DRAW_BATCH=0).진단
- 호스트 크래시 리포터. 크래시 시 예외 코드·주소·접근 종류와 심볼과 소스 줄이 붙은 호출 스택을 남깁니다. 도입하자마자 추측으로 5회 실패했던 teardown 크래시를 첫 크래시 실행에서 특정했습니다.
- 비동기 present 축은 측정 오류로 종결했습니다. vsync ON에서
grBufferSwap이 커 보였던 것은 디스플레이 대기였고, vsync OFF에서는 2.9%입니다. 구현은 정확성 근거로만 opt-in으로 남겼습니다.
빌드·CI
- OpenWatcom을 업스트림의 롤링 태그에서 불변 스냅샷(
2026-08-01-Build)으로 고정. 롤링 태그는 같은 URL의 내용물이 교체돼 핀이 조용히 만료됩니다. 툴체인 교체 후에도 샘플 baseline 회귀는 0입니다(819개 빌드, 528개 통과, baseline과 완전 일치). - 원본 자산 없이도 샘플 스위트가 빌드됩니다. 그 스위트는 자체 DOS 프로그램을
컴파일해 로더로 돌리므로
MASTER\트리가 필요 없는데, 환경 점검이 그것을 요구하고 있었습니다.
포함된 버전
v0.0.137·v0.0.138·v0.0.139·v0.0.140·v0.0.141·v0.0.142·v0.0.143 -
2026-08-07
v0.0.136
Full Changelog: https://github.com/nworkers/rePIU/compare/v0.0.81...v0.0.136
전체 릴리스 노트
✨ New Features & Emulation (HLE)
- Emulation & Audio
- Emulate the PIU10 YMZ280B and route its output to SDL audio (
b3dcb5e) - Deliver CD playback position to the guest and start track 2 at its true beginning (
5f93b64)
- Emulate the PIU10 YMZ280B and route its output to SDL audio (
- Graphics & Glide
- HLE: Implement Glide Combine and TMU state for full alpha and texture accuracy (
3047b5c) - Implement Glide line drawing and cull modes (
2a9456a) - Implement safe
GlideGateIdO(1) switch dispatch and complete Glide API family (73a3122)
- HLE: Implement Glide Combine and TMU state for full alpha and texture accuracy (
- Target Bring-up
- Add
pumpit2multisession CHD profile and handle unresolved AOT-DBT direct edges (919ff28) - Bring up the
pumpit3target through five HLE gaps to its render loop (211cbfc)
- Add
- Execution & AOT
- Add AOT-DBT execution and improve SDL host lifecycle (
84797bf) - Implement Port I/O AOT Fast-Path and Opcode-directed HLE Dispatcher (
62c89f8) - Native region execution foundation: diagnose single-step wall, prove the interception mechanism (
0c7a5ba) - Translate AOT segment instructions natively, removing the dominant segmentation exception cost (
61d04ae)
- Add AOT-DBT execution and improve SDL host lifecycle (
⚡ Performance & Optimization
- AOT & Execution Speed
- Translate from the live guest arena and cut append cost 10.5x (
3552db7) - Unblock the post-HLE return path and raise the frame rate 2.19x (
e2f6563) - Move the performance baseline to Release and raise the frame rate 5.8x (
c1087b4) - Harden AOT-DBT dispatch and enable native linear spans (
d790ab7) - Reduce AOT-DBT single-step overhead safely (
fa73415) - Add guarded AOT segment-pop fast path (
18540db) - Improve AOT fallback performance and diagnostics (
e67a9e7) - Improve AOT caching and native execution coverage (
fc7ddba) - Attribute whole-run execution time and remove the AOT lookup bottleneck (
f687886) - Drop the redundant per-gate pump rendezvous (
a2982d9) - Re-baseline the cost profile and cut the Glide rendezvous latency (
6101dbe) - Optimize Music Select execution boundaries (
672e343)
- Translate from the live guest arena and cut append cost 10.5x (
- System & I/O
- Keep DOS file handles open across reads (Task 374) (
4760c19) - Replace Glide GL error polling and find the display limit (Tasks 369-371) (
8bf7d52)
- Keep DOS file handles open across reads (Task 374) (
🐛 Bug Fixes
- Graphics (Glide)
- Fix the corrupted 3D model by connecting Glide vertex depth (
6ab688a) - Fix Glide LFB rendering and stabilize Win32 AOT execution (
ea609ba) - Fix Glide background, fog, and LFB rendering (
bd5a5c4) - Draw Glide sprites at the right scale and color, and implement grHints (
9bcba7b) - Harden Glide gates and report implementation gaps (
7c758b0)
- Fix the corrupted 3D model by connecting Glide vertex depth (
- System & Inputs
- Fix the note and background animation (BGA) jumping by preserving owed timer ticks (
e341c8f) - Fix guest timer delivery and honor PIT cadence (
b18e54a) - Fix the
pumpit3startup stall and add the instruments that found it (280ed28) - Fix JAMMA system and P2 keypad mappings (
dc8852f) - Classify AOT-DBT RET fallbacks by exclusive cause and fix the next DBT target (
f815d2e)
- Fix the note and background animation (BGA) jumping by preserving owed timer ticks (
🔍 Profiling & Analysis
- Bottleneck Identification
- Find
pumpit3's real input cost and bound it with a snapshot (cc21627) - Locate
pumpit3's dominant cost in one port I/O instruction (8d9b365) - Reproduce the
pumpit3startup stall and rule out its two obvious causes (f78b895) - Locate the dynamic translation bottleneck in the full-arena snapshot (
79691a2) - Instrument AOT boundary exits and pin the root cause to segmentation instructions (
c4885c3)
- Find
- Cost Attribution & Profiling
- Attribute
pumpit3arena execution and record the next ten steps (458efdd) - Attribute every VEH exit and settle who consumes the arena-entry single step (
d789786) - Measure the kernel exception round trip and scope the next axis (Tasks 372-373) (
d1155a3) - Attribute and act on the Glide/exception axis (Tasks 364-368) (
3ab6a1b) - Attribute execution cost down to dynamic translation on the worker thread (
47e1b96) - Profile Release execution and Glide bottlenecks (
2e502e3) - Record aot-dynamic being 14.6x slower than legacy as the next task (
c15c362)
- Attribute
- Investigations & Diagnostics
- Instrument the CD audio path and trace the stall to our own watchdog (
7583fde) - Disprove the hidden single-step population and retract 6.5% (Task 376) (
99d7016) - Settle the texture suspicion with a census and pixel dump (Task 375) (
206d414)
- Instrument the CD audio path and trace the stall to our own watchdog (
🛠 Chore, Refactoring & CI/CD
- Refactoring
- Migrate window and CD audio services to SDL3 (
26f28fd) - Consolidate the execution backends to legacy and dynamic (
78366f8)
- Migrate window and CD audio services to SDL3 (
- CI/CD & Testing
- Add tag-triggered build verification and release artifacts on GitHub Actions (
c6e9117) - Require a version tag on every main merge (
35842b6) - Require completion in the OpenWatcom sample pass criterion (
3b5073c) - Refresh the OpenWatcom sample baseline to v0.0.133 (
2038829)
- Add tag-triggered build verification and release artifacts on GitHub Actions (
- Configuration & Documentation
- Default execution to dynamic with no time limit, and make source comments English-only (
d78e328) - Attribute plan build and move the performance baseline to Release (
dcd8937) - Document Glide call-volume performance plan (
609d77d) - Record the GLSL shader review and the Task 377 direction (
0e49182) - Record next optimization frontier: multi-slot indirect inline cache (
b3e7fa2) - Add the post on tracing note jumping to lost timer ticks (
a19360e)
- Default execution to dynamic with no time limit, and make source comments English-only (
Full Changelog: https://github.com/nworkers/rePIU/compare/v0.0.81...v0.0.136
- Emulation & Audio