diff --git a/README.md b/README.md index f13e184..0f62c8c 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,18 @@ $6,660; death/retry preserves the casino bank. See ## Native Entrance And Character Scenes -The graphical startup now fades from Eblan Games into a first-person opening: +The graphical startup runs Eblan Games, a Russian AI-use disclosure and a +side-by-side C++/MASM code fact before the first-person opening: eyes opening beneath a cloudy rainy sky, smoking in an alley, discarding the cigarette, walking and turning through a separate city map, then meeting the guards and choosing a local profile before entering the walkable hall. The sequence resets on every launch; the intro advances automatically. +Enter/Space advances one startup page; Escape skips to the opening scene. +After profile admission and entry into the hall, a centred cyberpunk briefing +unfolds from a point and types version 4.0, game information and controls. +The jukebox starts on entry at 50% volume, with normal positional attenuation. +Enter completes the text, then closes it; Escape closes immediately. Movement +is blocked while the briefing is open. See [startup and briefing](docs/STARTUP_PRESENTATION.md). Sidorovich operates the native card-back shop. New character sheets contain 32 guard poses and 24 merchant poses, with separate actions rather than a single repeated idle animation. diff --git a/bin/poker.exe b/bin/poker.exe index 7749556..9c926c0 100644 Binary files a/bin/poker.exe and b/bin/poker.exe differ diff --git a/build/AssemblerCasinoLauncher.cs b/build/AssemblerCasinoLauncher.cs index 6d5178e..d9641d9 100644 --- a/build/AssemblerCasinoLauncher.cs +++ b/build/AssemblerCasinoLauncher.cs @@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher { static class Program { - const string ExpectedPokerSha256 = "2357F5876DA47FD6CD7FAE7216C71FCEDE6CCA79C1591E2BA6CFD2F3DB61694E"; + const string ExpectedPokerSha256 = "3D0237E09CAED350548457BFAB24C4D67D6FAFC3D689C2D87C8BE360B64EC0AF"; [STAThread] static void Main() diff --git a/build/graphics/poker.exe b/build/graphics/poker.exe index 7749556..9c926c0 100644 Binary files a/build/graphics/poker.exe and b/build/graphics/poker.exe differ diff --git a/casino3d.inc b/casino3d.inc index 71f7b91..077633a 100644 --- a/casino3d.inc +++ b/casino3d.inc @@ -150,6 +150,8 @@ include src_graphics\platform_data.inc include src_graphics\casino_actions_data.inc include src_graphics\audio_data.inc include src_graphics\startup_data.inc +include src_graphics\presentation_text.inc +include src_graphics\presentation_data.inc include src_graphics\card_tables_data.inc include src_graphics\native_durak_data.inc include src_graphics\scene_animation_data.inc @@ -4497,6 +4499,8 @@ unlock_done: UnlockMouse3D ENDP UpdateMouseLockState PROC + cmp welcomeActive3D,0 + jne unlock_it cmp engineMode,MODE_HELL_FPS je lock_interior_check cmp engineMode,MODE_STAIRS @@ -5241,6 +5245,7 @@ include src_graphics\audio_code.inc include src_graphics\dice_animation.inc include src_graphics\russian_roulette.inc include src_graphics\startup_code.inc +include src_graphics\presentation_code.inc include src_graphics\card_table_assets.inc include src_graphics\poker_evaluator.inc include src_graphics\native_poker.inc @@ -5341,6 +5346,11 @@ engine_observer_skip: invoke Pressed3D, 1Bh test eax, eax jz engine_tick + cmp welcomeActive3D,0 + je @F + call CloseWelcome3D + jmp engine_tick +@@: cmp engineMode,MODE_HELL_FPS jne @F call LeaveHellFPS3D @@ -5441,6 +5451,12 @@ check_walk_mode: @@: cmp engineMode, MODE_WALK jne check_slots_mode + cmp welcomeActive3D,0 + je welcome_walk_ready + call HandleWelcome3D + call RenderWelcomeScene3D + jmp present_frame +welcome_walk_ready: cmp brActive3D,0 jne engine_backrooms call UpdateHall3D diff --git a/dist/Assembler Casino.exe b/dist/Assembler Casino.exe index 673dcbd..71d05d5 100644 Binary files a/dist/Assembler Casino.exe and b/dist/Assembler Casino.exe differ diff --git a/docs/STARTUP_PRESENTATION.md b/docs/STARTUP_PRESENTATION.md new file mode 100644 index 0000000..badcc4a --- /dev/null +++ b/docs/STARTUP_PRESENTATION.md @@ -0,0 +1,84 @@ +# Startup presentation and hall briefing + +All runtime presentation, text, timing, input and drawing are MASM32. No browser, +video player, Python runtime or additional graphics dependency is introduced. + +## Startup order + +1. Existing Eblan Games animated logo (7 seconds; its F2 variant is retained). +2. Russian AI-use disclosure (10 seconds): artificial intelligence technologies + were used to create some game textures; third-party resources retain their + authors and licences. +3. Code fact (11 seconds): side-by-side editor-style C++ and MASM panels for + clearing the same 640×480 framebuffer. C++ is explicitly an illustrative + equivalent, not a second game runtime. MASM shows the actual clear operation + from `src_graphics/startup_sequence.inc`. +4. Existing first-person city opening, guards and profile selection. + +Pages advance automatically. **Enter/Space** advances one page, clearing held +input so it cannot skip several screens at once. **Escape** skips the startup +pages and begins the existing city scene. Starting again resets all pages. +Existing missing-logo fallback still works; the two new pages need no art files. + +## Post-entry briefing + +After a guest/existing/new profile is admitted and the player enters the hall, +the jukebox starts **Bolden - Art Deco** on repeat at **50% user volume** (5/10). +Existing distance/floor attenuation still applies, so it sounds quieter away +from the cabinet. Volume is applied before playback, avoiding a full-volume +startup burst. No jukebox music plays during startup/profile selection. +Closing the briefing does not restart the track or reset volume; the existing +jukebox controls and +/- volume keys remain available after closing it. + +At the same entry point, the centred panel deploys from a point: horizontal +expansion for 200 ms, then +vertical ease-out for 400 ms. Cyan/magenta rails and a scanning marker frame the +dark panel. The hall is rendered by the normal ASM renderer and dimmed beneath +it; movement, interactions and camera input do not run until the modal closes. + +After deployment, Russian text types at one character per 18 ms with a blinking +cursor: version **4.0 / Windows x86**, engine, activities, controls +and profile persistence. Version follows the existing 4.0 build identity; this +change does not introduce a new release/version bump or alter save formats. + +- **Enter/Space**: complete the text; press again to close. +- **Escape**: close immediately, without opening pause. +- **Enter game** button: same complete/close behaviour, with mouse input. +- No automatic dismissal; the information remains readable. +- Shown once per admission on each launch, not on returning from tables or Hell. + +The core remains assembly-only; the pre-existing C# packaging tools and the +approved C++ fortune inference worker are separate from the game engine. + +## Copy and implementation + +- `tools/generators/make_presentation_text.py`: editable Russian copy; emits + numeric CP1251 declarations into `src_graphics/presentation_text.inc`. +- `src_graphics/presentation_data.inc`: timings, page/briefing state and API. +- `src_graphics/presentation_code.inc`: native bitmap text, code windows, + unfolding modal and typewriter. +- `src_graphics/startup_code.inc`: three-page sequencing. +- `src_graphics/arrival_scene.inc`: briefing starts after admission/entry only. +- `casino3d.inc`: modal input gate, mouse unlock and Escape handling. + +## Verification + +```powershell +python tools/generators/make_presentation_text.py +python tools/tests/check_presentation.py +python tools/tests/check_presentation.py --window +``` + +Tests compile the production procedures with an isolated ASM entry point. They +check automatic and key transitions, restart/reset, existing city-scene handoff, +guest admission, delayed modal trigger, deployment, typing and manual completion, +button/close input cleanup, and unchanged bank, RNG and player save files. +They also query actual MCI playback and gain after entry, verify silence before +admission, retain a changed volume and playback position across modal closure, +then exercise manual track selection and Stop. MCI gain assertions allow device +rounding (four units out of 1000); both success and failure close `jboxmusic`. +`--window` also presents 150 real Win32 frames. + +Outputs: `build/scratch/presentation-00..11.png`, `presentation-contact.png` and +`presentation-window.png`. Existing action/arrival tests also check the revised +startup order and the new admission trigger. diff --git a/poker.asm b/poker.asm index d9ffc29..7070f54 100644 --- a/poker.asm +++ b/poker.asm @@ -2072,7 +2072,14 @@ ENDIF IFDEF HELL_FPS_TEST include tools/tests/hell_fps_test.inc ENDIF +IFDEF PRESENTATION_TEST +include tools/tests/presentation_test.inc +ENDIF main PROC +IFDEF PRESENTATION_TEST + call RunPresentationTest3D + invoke ExitProcess,eax +ENDIF IFDEF HELL_FPS_TEST IFDEF HELL_FPS_WINDOW_TEST call RunHellFPSWindowTest diff --git a/poker.exe b/poker.exe index 7749556..9c926c0 100644 Binary files a/poker.exe and b/poker.exe differ diff --git a/poker.obj b/poker.obj index 7a43fe8..cd86569 100644 Binary files a/poker.obj and b/poker.obj differ diff --git a/src_graphics/arrival_scene.inc b/src_graphics/arrival_scene.inc index b52ad7f..537a9f1 100644 --- a/src_graphics/arrival_scene.inc +++ b/src_graphics/arrival_scene.inc @@ -128,6 +128,10 @@ arrival_entry: mov interactTargetId, 0 mov interactPromptPtr, 0 call ClearKeys3D + call BeginWelcome3D + ; Start only after admission, with the normal positional attenuation. + mov jboxUserVolume, 5 + invoke PlayJukeboxTrack3D, 0 ret arrival_profile_input: invoke Pressed3D, 26h @@ -309,4 +313,3 @@ entry_rows_done: invoke DrawString8x8, 223, 207, OFFSET entryConfirm3D, 00FFFFFFh ret DrawEntryProfileRows3D ENDP - diff --git a/src_graphics/jackbox.inc b/src_graphics/jackbox.inc index e763334..d4c95b4 100644 --- a/src_graphics/jackbox.inc +++ b/src_graphics/jackbox.inc @@ -118,11 +118,18 @@ PlayJukeboxTrack3D PROC uses ebx trackIndex:DWORD test eax, eax jz jbox_play_stop_selected invoke mciSendStringA, eax, 0, 0, 0 - invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0 + test eax, eax + jnz jbox_play_stop_selected mov jboxCurrentTrack, ebx mov jboxIsPlaying, 1 mov jboxEffectiveVolume, -1 + ; Apply volume before play: never start a newly opened device at full gain. call UpdateJukeboxAudio3D + invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0 + test eax, eax + jz jbox_play_done + call StopJukeboxTrack3D +jbox_play_done: ret jbox_play_stop_selected: mov jboxCurrentTrack, -1 diff --git a/src_graphics/presentation_code.inc b/src_graphics/presentation_code.inc new file mode 100644 index 0000000..a8dddab --- /dev/null +++ b/src_graphics/presentation_code.inc @@ -0,0 +1,358 @@ +; Native-pixel, CP1251 bitmap text. Count includes newlines for typewriter timing. +; Unlike DrawString8x8's 320x240 coordinates, this uses the full 640x480 frame. +PresentationText3D PROC uses esi edi ebx x:DWORD,y:DWORD,text:DWORD,ink:DWORD,scale:DWORD,limit:DWORD + local px:DWORD,py:DWORD,row:DWORD,col:DWORD,bits:DWORD,repeatY:DWORD,repeatX:DWORD + mov esi,text + mov eax,x + mov px,eax + mov eax,y + mov py,eax +pres_text_char: + cmp limit,0 + je pres_text_done + movzx eax,BYTE PTR [esi] + test eax,eax + jz pres_text_done + inc esi + dec limit + cmp eax,10 + jne pres_text_glyph + mov eax,x + mov px,eax + mov eax,scale + imul eax,11 + add py,eax + jmp pres_text_char +pres_text_glyph: + lea edi,font8x8[eax*8] + mov row,0 +pres_text_row: + mov eax,row + movzx ebx,BYTE PTR [edi+eax] + mov bits,ebx + mov col,0 +pres_text_col: + test bits,80h + jz pres_text_next_col + mov repeatY,0 +pres_text_repeat_y: + mov eax,row + imul eax,scale + add eax,py + add eax,repeatY + cmp eax,SCREEN_H + jae pres_text_next_y + imul eax,SCREEN_W + mov ebx,eax + mov repeatX,0 +pres_text_repeat_x: + mov eax,col + imul eax,scale + add eax,px + add eax,repeatX + cmp eax,SCREEN_W + jae pres_text_next_x + add eax,ebx + mov edx,ink + mov screenBuffer[eax*4],edx +pres_text_next_x: + inc repeatX + mov eax,repeatX + cmp eax,scale + jb pres_text_repeat_x +pres_text_next_y: + inc repeatY + mov eax,repeatY + cmp eax,scale + jb pres_text_repeat_y +pres_text_next_col: + shl bits,1 + inc col + cmp col,8 + jb pres_text_col + inc row + cmp row,8 + jb pres_text_row + mov eax,scale + shl eax,3 + add px,eax + jmp pres_text_char +pres_text_done: + ret +PresentationText3D ENDP + +RenderStartupNotice3D PROC uses ebx esi edi + local lineY:DWORD,ink:DWORD + invoke DrawBox,0,0,320,240,00060912h + ; Quiet terminal grid, fixed geometry: never consumes gameplay RNG. + mov ebx,12 +pres_notice_grid: + invoke DrawBox,0,ebx,320,1,000C1722h + add ebx,16 + cmp ebx,240 + jb pres_notice_grid + invoke DrawBox,20,26,3,22,00FFCE54h + invoke DrawBox,26,26,274,1,002C6875h + invoke DrawBox,20,212,280,1,002C6875h + cmp startupPage3D,STARTUP_AI + jne pres_notice_code + invoke PresentationText3D,56,60,OFFSET presAITag,004FE5E2h,1,-1 + invoke PresentationText3D,56,88,OFFSET presAIHeader,00FFDF82h,2,-1 + invoke PresentationText3D,40,156,OFFSET presAIBody,00CFDCE4h,2,-1 + invoke PresentationText3D,56,378,OFFSET presAIFooter,004FE5E2h,1,-1 + jmp pres_notice_footer +pres_notice_code: + invoke PresentationText3D,56,64,OFFSET presCodeHeader,00FFDF82h,2,-1 + invoke PresentationText3D,40,112,OFFSET presCodeSub,00CFDCE4h,2,-1 + invoke DrawBoxBorder,12,80,144,90,00080F1Bh,002D7C92h,1 + invoke DrawBoxBorder,164,80,144,90,00080F1Bh,003BE8CAh,1 + invoke DrawBox,13,81,142,14,0017283Ch + invoke DrawBox,165,81,142,14,00143435h + invoke PresentationText3D,36,170,OFFSET presCodeLeft,00D0DDEBh,1,-1 + invoke PresentationText3D,340,170,OFFSET presCodeRight,0062F3D3h,1,-1 + mov esi,0 + mov lineY,204 +pres_code_line: + mov ink,00C9D9E8h + cmp esi,0 + jne @F + mov ink,00657D92h +@@: + invoke PresentationText3D,36,lineY,presCppLines[esi*4],ink,1,-1 + invoke PresentationText3D,340,lineY,presAsmLines[esi*4],ink,1,-1 + add lineY,17 + inc esi + cmp esi,7 + jb pres_code_line + invoke PresentationText3D,24,356,OFFSET presCodeCaption,00FFDF82h,2,-1 + invoke PresentationText3D,40,386,OFFSET presCodeNote,009FB8C8h,1,-1 +pres_notice_footer: + invoke PresentationText3D,40,448,OFFSET presNext,006F939Fh,1,-1 + ; Fade the notice in, then out at its own page boundary. + mov eax,startupElapsed3D + cmp eax,300 + jae pres_notice_fade_out + shl eax,8 + xor edx,edx + mov ecx,300 + div ecx + invoke FadeCityFrame3D,eax + ret +pres_notice_fade_out: + mov ecx,startupPage3D + mov ebx,startupDurations3D[ecx*4] + sub ebx,400 + cmp eax,ebx + jb pres_notice_done + sub eax,ebx + cmp eax,400 + jbe @F + mov eax,400 +@@: + shl eax,8 + xor edx,edx + mov ecx,400 + div ecx + neg eax + add eax,256 + invoke FadeCityFrame3D,eax +pres_notice_done: + ret +RenderStartupNotice3D ENDP + +BeginWelcome3D PROC + mov welcomeActive3D,1 + mov welcomeElapsed3D,0 + mov welcomeChars3D,0 + mov paused3D,0 + mov interactTargetId,0 + mov interactPromptPtr,0 + call ClearKeys3D + call UnlockMouse3D + ret +BeginWelcome3D ENDP + +CloseWelcome3D PROC + mov welcomeActive3D,0 + mov paused3D,0 + call ClearKeys3D + ret +CloseWelcome3D ENDP + +HandleWelcome3D PROC + cmp welcomeActive3D,0 + je welcome_handle_done + mov eax,deltaTime3D + add welcomeElapsed3D,eax + cmp welcomeElapsed3D,WELCOME_OPEN_MS + jb welcome_handle_done + ; Once manually completed, do not rewind the typewriter on the next frame. + cmp welcomeChars3D,welcomeTextLength3D + jae welcome_input + mov eax,welcomeElapsed3D + sub eax,WELCOME_OPEN_MS + xor edx,edx + mov ecx,WELCOME_CHAR_MS + div ecx + cmp eax,welcomeTextLength3D + jbe @F + mov eax,welcomeTextLength3D +@@: + mov welcomeChars3D,eax +welcome_input: + invoke Pressed3D,0Dh + test eax,eax + jnz welcome_confirm + invoke Pressed3D,20h + test eax,eax + jnz welcome_confirm + cmp mouseClick3D,0 + je welcome_handle_done + cmp mouseX3D,212 + jb welcome_handle_done + cmp mouseX3D,292 + jae welcome_handle_done + cmp mouseY3D,184 + jb welcome_handle_done + cmp mouseY3D,198 + jae welcome_handle_done +welcome_confirm: + cmp welcomeChars3D,welcomeTextLength3D + jae welcome_close + mov welcomeChars3D,welcomeTextLength3D + call ClearKeys3D + ret +welcome_close: + call CloseWelcome3D +welcome_handle_done: + ret +HandleWelcome3D ENDP + +RenderWelcomeScene3D PROC + ; Frozen playable hall, not a replacement screenshot or second game runtime. + call UpdateCamera3D + call RenderCeilingAndFloor + call RenderWalls + call PrepareCityDepth3D + call RenderSprites + call RenderHall3D + call RenderHUD + call RenderWelcome3D + ret +RenderWelcomeScene3D ENDP + +RenderWelcome3D PROC uses ebx esi edi + local halfW:DWORD,halfH:DWORD,left:DWORD,top:DWORD,wide:DWORD,panelH:DWORD + local cursorX:DWORD,cursorY:DWORD + cmp welcomeActive3D,0 + je welcome_render_done + invoke FadeCityFrame3D,90 + ; Two-stage deployment: point -> horizontal line -> centred panel. + mov eax,welcomeElapsed3D + cmp eax,200 + jae welcome_open_vertical + imul eax,140 + xor edx,edx + mov ecx,200 + div ecx + inc eax + mov halfW,eax + mov halfH,1 + jmp welcome_panel_bounds +welcome_open_vertical: + mov halfW,140 + sub eax,200 + cmp eax,400 + jbe @F + mov eax,400 +@@: + ; Ease-out quadratic: 1 - (1-t)^2, with bounded integer operands. + neg eax + add eax,400 + imul eax,eax + imul eax,81 + xor edx,edx + mov ecx,160000 + div ecx + neg eax + add eax,82 + mov halfH,eax +welcome_panel_bounds: + mov eax,160 + sub eax,halfW + mov left,eax + mov eax,120 + sub eax,halfH + mov top,eax + mov eax,halfW + shl eax,1 + mov wide,eax + mov eax,halfH + shl eax,1 + mov panelH,eax + invoke DrawBoxBorder,left,top,wide,panelH,0008101Bh,003EE7D8h,1 + mov eax,left + add eax,wide + sub eax,2 + invoke DrawBox,eax,top,2,panelH,00DC4B9Bh + cmp welcomeElapsed3D,WELCOME_OPEN_MS + jb welcome_render_done + ; Chamfered corners, offset signal rails and a narrow animated scan line. + invoke DrawBox,20,38,4,4,0008101Bh + invoke DrawBox,296,198,4,4,0008101Bh + invoke DrawBox,24,34,46,2,003EE7D8h + invoke DrawBox,250,205,46,2,00DC4B9Bh + invoke DrawBox,30,70,260,1,00284E62h + invoke DrawBox,30,180,260,1,00284E62h + mov eax,welcomeElapsed3D + shr eax,5 + xor edx,edx + mov ecx,100 + div ecx + add edx,76 + invoke DrawBox,22,edx,1,2,0058FFF0h + invoke PresentationText3D,60,90,OFFSET welcomeHeader,00FFE29Ah,2,-1 + invoke PresentationText3D,60,120,OFFSET welcomeTag,006C9EB0h,1,-1 + invoke PresentationText3D,60,154,OFFSET welcomeBody,00CDE9E8h,1,welcomeChars3D + cmp welcomeChars3D,welcomeTextLength3D + jae welcome_cursor_done + test welcomeElapsed3D,100h + jnz welcome_cursor_done + mov cursorX,60 + mov cursorY,154 + mov esi,OFFSET welcomeBody + mov edi,welcomeChars3D +welcome_cursor_scan: + test edi,edi + jz welcome_cursor_draw + cmp BYTE PTR [esi],10 + jne welcome_cursor_column + mov cursorX,60 + add cursorY,11 + jmp welcome_cursor_next +welcome_cursor_column: + add cursorX,8 +welcome_cursor_next: + inc esi + dec edi + jmp welcome_cursor_scan +welcome_cursor_draw: + invoke PresentationText3D,cursorX,cursorY,OFFSET welcomeCursor3D,0058FFF0h,1,1 +welcome_cursor_done: + ; Blinking activity indicator; no random flicker and no gameplay RNG changes. + mov eax,welcomeElapsed3D + test eax,200h + jnz @F + invoke DrawBox,280,49,7,2,0058FFF0h +@@: + invoke DrawBoxBorder,212,184,80,14,00172A38h,003EE7D8h,1 + invoke PresentationText3D,436,376,OFFSET welcomeButton,006EF4DEh,1,-1 + mov eax,OFFSET welcomeTypeHint + cmp welcomeChars3D,welcomeTextLength3D + jb @F + mov eax,OFFSET welcomeDoneHint +@@: + invoke PresentationText3D,60,380,eax,008EAFBEh,1,-1 +welcome_render_done: + ret +RenderWelcome3D ENDP diff --git a/src_graphics/presentation_data.inc b/src_graphics/presentation_data.inc new file mode 100644 index 0000000..0082e53 --- /dev/null +++ b/src_graphics/presentation_data.inc @@ -0,0 +1,27 @@ +; All timings and presentation state are native ASM; no extra runtime or assets. +STARTUP_STUDIO equ 0 +STARTUP_AI equ 1 +STARTUP_CODE equ 2 +WELCOME_OPEN_MS equ 600 +WELCOME_CHAR_MS equ 18 +BeginWelcome3D PROTO +HandleWelcome3D PROTO +CloseWelcome3D PROTO +RenderWelcome3D PROTO +RenderWelcomeScene3D PROTO +PresentationText3D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD +RenderStartupNotice3D PROTO +AdvanceStartup3D PROTO +.data +startupPage3D dd STARTUP_STUDIO +startupDurations3D dd 7000,10000,11000 +welcomeActive3D dd 0 +welcomeElapsed3D dd 0 +welcomeChars3D dd 0 +welcomeCursor3D db '_',0 +welcomeTextLength3D equ welcomeBodyEnd-welcomeBody-1 +presCppLines dd OFFSET presCpp0,OFFSET presCpp1,OFFSET presCpp2,OFFSET presCpp3 + dd OFFSET presCpp4,OFFSET presCpp5,OFFSET presCpp6 +presAsmLines dd OFFSET presAsm0,OFFSET presAsm1,OFFSET presAsm2,OFFSET presAsm3 + dd OFFSET presAsm4,OFFSET presAsm5,OFFSET presAsm6 +.code diff --git a/src_graphics/presentation_text.inc b/src_graphics/presentation_text.inc new file mode 100644 index 0000000..620aef6 --- /dev/null +++ b/src_graphics/presentation_text.inc @@ -0,0 +1,86 @@ +; Generated by tools/generators/make_presentation_text.py. CP1251 bytes. +.data +presNext db 69,78,84,69,82,32,47,32,83,80,65,67,69,58,32,196,192,203,220,216,197,32,32,32,69,83,67,58,32,207,208,206 + db 207,211,209,210,200,210,220,0 +presNextEnd label byte +presAIHeader db 206,193,32,200,209,207,206,203,220,199,206,194,192,205,200,200,32,65,73,0 +presAIHeaderEnd label byte +presAITag db 84,82,65,78,83,80,65,82,69,78,67,89,32,78,79,84,73,67,69,32,47,32,48,49,0 +presAITagEnd label byte +presAIBody db 194,32,232,227,240,229,32,229,241,242,252,32,236,224,242,229,240,232,224,235,251,44,32,241,238,231,228,224,237,237,251,229 + db 10,241,32,239,238,236,238,249,252,254,32,232,241,234,243,241,241,242,226,229,237,237,238,227,238,32,232,237,242,229,235,235 + db 229,234,242,224,46,10,10,207,240,232,32,241,238,231,228,224,237,232,232,32,247,224,241,242,232,32,242,229,234,241,242,243 + db 240,32,232,227,240,251,10,232,241,239,238,235,252,231,238,226,224,235,232,241,252,32,242,229,245,237,238,235,238,227,232,232 + db 10,232,241,234,243,241,241,242,226,229,237,237,238,227,238,32,232,237,242,229,235,235,229,234,242,224,46,10,10,210,224,234 + db 230,229,32,232,241,239,238,235,252,231,243,254,242,241,255,32,241,242,238,240,238,237,237,232,229,32,240,229,241,243,240,241 + db 251,46,10,200,245,32,224,226,242,238,240,251,32,232,32,235,232,246,229,237,231,232,232,32,241,238,245,240,224,237,229,237 + db 251,46,0 +presAIBodyEnd label byte +presAIFooter db 209,206,199,196,192,205,206,32,209,32,65,73,46,32,209,206,193,208,192,205,206,32,194,208,211,215,205,211,46,0 +presAIFooterEnd label byte +presCodeHeader db 192,32,194,219,32,199,205,192,203,200,63,0 +presCodeHeaderEnd label byte +presCodeSub db 200,195,208,206,194,206,201,32,196,194,200,198,206,202,32,205,192,207,200,209,192,205,32,205,192,32,77,65,83,77,51,50 + db 0 +presCodeSubEnd label byte +presCodeLeft db 67,43,43,32,47,32,207,208,200,204,197,208,0 +presCodeLeftEnd label byte +presCodeRight db 77,65,83,77,32,47,32,202,206,196,32,221,210,206,201,32,200,195,208,219,0 +presCodeRightEnd label byte +presCodeCaption db 206,196,205,192,32,199,192,196,192,215,192,32,45,32,196,194,192,32,223,199,219,202,192,46,0 +presCodeCaptionEnd label byte +presCodeNote db 208,229,237,228,229,240,44,32,243,239,240,224,226,235,229,237,232,229,32,232,32,232,227,240,251,32,45,32,120,56,54,32 + db 65,83,77,46,10,67,43,43,32,241,235,229,226,224,32,239,238,234,224,231,224,237,32,242,238,235,252,234,238,32,228,235 + db 255,32,241,240,224,226,237,229,237,232,255,46,0 +presCodeNoteEnd label byte +presCpp0 db 47,47,32,67,108,101,97,114,32,116,104,101,32,102,114,97,109,101,98,117,102,102,101,114,0 +presCpp0End label byte +presCpp1 db 118,111,105,100,32,99,108,101,97,114,70,114,97,109,101,40,41,32,123,0 +presCpp1End label byte +presCpp2 db 32,32,102,111,114,32,40,105,110,116,32,105,32,61,32,48,59,0 +presCpp2End label byte +presCpp3 db 32,32,32,32,32,32,32,105,32,60,32,54,52,48,32,42,32,52,56,48,59,32,43,43,105,41,32,123,0 +presCpp3End label byte +presCpp4 db 32,32,32,32,115,99,114,101,101,110,66,117,102,102,101,114,91,105,93,32,61,32,48,120,48,53,48,56,48,57,59,0 +presCpp4End label byte +presCpp5 db 32,32,125,0 +presCpp5End label byte +presCpp6 db 125,0 +presCpp6End label byte +presAsm0 db 59,32,115,116,97,114,116,117,112,95,115,101,113,117,101,110,99,101,46,105,110,99,0 +presAsm0End label byte +presAsm1 db 108,101,97,32,101,100,105,44,32,115,99,114,101,101,110,66,117,102,102,101,114,0 +presAsm1End label byte +presAsm2 db 109,111,118,32,101,97,120,44,32,48,48,48,53,48,56,48,57,104,0 +presAsm2End label byte +presAsm3 db 109,111,118,32,101,99,120,44,32,83,67,82,69,69,78,95,83,73,90,69,0 +presAsm3End label byte +presAsm4 db 114,101,112,32,115,116,111,115,100,0 +presAsm4End label byte +presAsm5 db 59,32,54,52,48,32,42,32,52,56,48,32,112,105,120,101,108,115,0 +presAsm5End label byte +presAsm6 db 59,32,78,97,116,105,118,101,32,120,56,54,32,47,32,87,105,110,51,50,0 +presAsm6End label byte +welcomeHeader db 65,83,83,69,77,66,76,69,82,32,67,65,83,73,78,79,0 +welcomeHeaderEnd label byte +welcomeTag db 76,79,67,65,76,32,83,69,83,83,73,79,78,32,47,32,83,89,83,84,69,77,32,66,82,73,69,70,73,78,71,0 +welcomeTagEnd label byte +welcomeBody db 62,32,194,197,208,209,200,223,32,52,46,48,32,47,32,87,73,78,68,79,87,83,32,120,56,54,10,10,196,238,225,240 + db 238,32,239,238,230,224,235,238,226,224,242,252,32,226,32,65,115,115,101,109,98,108,101,114,32,67,97,115,105,110,111,46 + db 10,202,224,231,232,237,238,32,241,32,241,238,225,241,242,226,229,237,237,251,236,32,50,46,53,68,45,228,226,232,230,234 + db 238,236,10,237,224,32,224,241,241,229,236,225,235,229,240,229,32,77,65,83,77,51,50,46,10,10,194,237,243,242,240,232 + db 58,32,234,224,240,242,238,247,237,251,229,32,232,227,240,251,44,32,224,226,242,238,236,224,242,251,44,10,225,238,243,235 + db 232,237,227,44,32,225,232,235,252,255,240,228,44,32,241,229,234,240,229,242,251,32,232,32,239,238,245,238,228,32,226,32 + db 192,228,46,10,10,87,65,83,68,32,45,32,228,226,232,230,229,237,232,229,46,32,204,251,248,252,32,45,32,238,241,236 + db 238,242,240,46,10,69,32,45,32,226,231,224,232,236,238,228,229,233,241,242,226,232,229,46,32,69,83,67,32,45,32,239 + db 224,243,231,224,46,10,207,240,238,227,240,229,241,241,32,241,238,245,240,224,237,255,229,242,241,255,32,226,32,226,224,248 + db 229,236,32,239,240,238,244,232,235,229,46,0 +welcomeBodyEnd label byte +welcomeTypeHint db 69,78,84,69,82,58,32,207,206,202,192,199,192,210,220,32,210,197,202,209,210,32,214,197,203,200,202,206,204,0 +welcomeTypeHintEnd label byte +welcomeDoneHint db 69,78,84,69,82,32,47,32,69,83,67,58,32,199,192,202,208,219,210,220,32,200,32,205,192,215,192,210,220,32,200,195 + db 208,211,0 +welcomeDoneHintEnd label byte +welcomeButton db 194,206,201,210,200,32,194,32,200,195,208,211,0 +welcomeButtonEnd label byte +.code diff --git a/src_graphics/startup_code.inc b/src_graphics/startup_code.inc index 1734451..fc2d1bb 100644 --- a/src_graphics/startup_code.inc +++ b/src_graphics/startup_code.inc @@ -70,6 +70,8 @@ BeginStartup3D PROC mov interactTargetId, 0 mov interactPromptPtr, 0 mov startupElapsed3D, 0 + mov startupPage3D, STARTUP_STUDIO + mov welcomeActive3D, 0 mov startupVariant3D, 0 mov animationClock3D, 0 mov rr3d_state, RR_READY @@ -89,12 +91,26 @@ FinishStartup3D PROC ret FinishStartup3D ENDP +AdvanceStartup3D PROC + cmp startupPage3D,STARTUP_CODE + jae startup_advance_finish + inc startupPage3D + mov startupElapsed3D,0 + call ClearKeys3D + ret +startup_advance_finish: + call FinishStartup3D + ret +AdvanceStartup3D ENDP + HandleStartupInput3D PROC mov eax, deltaTime3D add startupElapsed3D, eax invoke Pressed3D, 71h test eax, eax jz @F + cmp startupPage3D,STARTUP_STUDIO + jne @F xor startupVariant3D, 1 mov startupElapsed3D, 0 @@: @@ -105,18 +121,22 @@ HandleStartupInput3D PROC test eax, eax jz startup_input_done startup_enter: - call FinishStartup3D + call AdvanceStartup3D startup_input_done: cmp engineMode, MODE_INTRO jne @F - cmp startupElapsed3D, 7000 + mov ecx,startupPage3D + mov eax,startupElapsed3D + cmp eax,startupDurations3D[ecx*4] jb @F - call FinishStartup3D + call AdvanceStartup3D @@: ret HandleStartupInput3D ENDP RenderStartup3D PROC + cmp startupPage3D,STARTUP_STUDIO + jne startup_notice cmp startupVariant3D, 0 jne startup_studio_variant cmp startupSequence3D, 0 @@ -128,6 +148,9 @@ startup_studio_variant: call RenderStudioStartup3D call FadeStartupOut3D ret +startup_notice: + call RenderStartupNotice3D + ret RenderStartup3D ENDP FadeStartupOut3D PROC diff --git a/tools/generators/make_presentation_text.py b/tools/generators/make_presentation_text.py new file mode 100644 index 0000000..1c1d525 --- /dev/null +++ b/tools/generators/make_presentation_text.py @@ -0,0 +1,69 @@ +"""Compile Russian presentation copy to CP1251 byte declarations for MASM.""" +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +TEXT = { + 'presNext': 'ENTER / SPACE: ДАЛЬШЕ ESC: ПРОПУСТИТЬ', + 'presAIHeader': 'ОБ ИСПОЛЬЗОВАНИИ AI', + 'presAITag': 'TRANSPARENCY NOTICE / 01', + 'presAIBody': 'В игре есть материалы, созданные\n' + 'с помощью искусственного интеллекта.\n\n' + 'При создании части текстур игры\n' + 'использовались технологии\n' + 'искусственного интеллекта.\n\n' + 'Также используются сторонние ресурсы.\n' + 'Их авторы и лицензии сохранены.', + 'presAIFooter': 'СОЗДАНО С AI. СОБРАНО ВРУЧНУ.', + 'presCodeHeader': 'А ВЫ ЗНАЛИ?', + 'presCodeSub': 'ИГРОВОЙ ДВИЖОК НАПИСАН НА MASM32', + 'presCodeLeft': 'C++ / ПРИМЕР', + 'presCodeRight': 'MASM / КОД ЭТОЙ ИГРЫ', + 'presCodeCaption': 'ОДНА ЗАДАЧА — ДВА ЯЗЫКА.', + 'presCodeNote': 'Рендер, управление и игры — x86 ASM.\n' + 'C++ слева показан только для сравнения.', + 'presCpp0': '// Clear the framebuffer', + 'presCpp1': 'void clearFrame() {', + 'presCpp2': ' for (int i = 0;', + 'presCpp3': ' i < 640 * 480; ++i) {', + 'presCpp4': ' screenBuffer[i] = 0x050809;', + 'presCpp5': ' }', + 'presCpp6': '}', + 'presAsm0': '; startup_sequence.inc', + 'presAsm1': 'lea edi, screenBuffer', + 'presAsm2': 'mov eax, 00050809h', + 'presAsm3': 'mov ecx, SCREEN_SIZE', + 'presAsm4': 'rep stosd', + 'presAsm5': '; 640 * 480 pixels', + 'presAsm6': '; Native x86 / Win32', + 'welcomeHeader': 'ASSEMBLER CASINO', + 'welcomeTag': 'LOCAL SESSION / SYSTEM BRIEFING', + 'welcomeBody': '> ВЕРСИЯ 4.0 / WINDOWS x86\n\n' + 'Добро пожаловать в Assembler Casino.\n' + 'Казино с собственным 2.5D-движком\n' + 'на ассемблере MASM32.\n\n' + 'Внутри: карточные игры, автоматы,\n' + 'боулинг, бильярд, секреты и поход в Ад.\n\n' + 'WASD — движение. Мышь — осмотр.\n' + 'E — взаимодействие. ESC — пауза.\n' + 'Прогресс сохраняется в вашем профиле.', + 'welcomeTypeHint': 'ENTER: ПОКАЗАТЬ ТЕКСТ ЦЕЛИКОМ', + 'welcomeDoneHint': 'ENTER / ESC: ЗАКРЫТЬ И НАЧАТЬ ИГРУ', + 'welcomeButton': 'ВОЙТИ В ИГРУ', +} +TEXT = {name:text.replace('\u2014','-') for name,text in TEXT.items()} + + +def main(): + lines = ['; Generated by tools/generators/make_presentation_text.py. CP1251 bytes.', '.data'] + for name, text in TEXT.items(): + data = text.encode('cp1251') + b'\0' + for offset in range(0,len(data),32): + prefix = f'{name} db ' if offset == 0 else ' db ' + lines.append(prefix + ','.join(map(str, data[offset:offset+32]))) + lines.append(f'{name}End label byte') + lines.append('.code') + (ROOT/'src_graphics/presentation_text.inc').write_bytes(('\n'.join(lines)+'\n').encode('ascii')) + + +if __name__ == '__main__': + main() diff --git a/tools/tests/arrival_test.inc b/tools/tests/arrival_test.inc index d3cf26b..2b56fd7 100644 --- a/tools/tests/arrival_test.inc +++ b/tools/tests/arrival_test.inc @@ -202,6 +202,14 @@ RunArrivalTest3D PROC uses ebx esi edi mov startupElapsed3D, 6990 mov deltaTime3D, 16 call HandleStartupInput3D + cmp startupPage3D,STARTUP_AI + jne arrival_test_fail + mov startupElapsed3D,10000 + call HandleStartupInput3D + cmp startupPage3D,STARTUP_CODE + jne arrival_test_fail + mov startupElapsed3D,11000 + call HandleStartupInput3D cmp engineMode, MODE_ARRIVAL jne arrival_test_fail cmp cityPhase3D, CITY_BLACK @@ -595,6 +603,9 @@ arrival_admit_sequence: call HandleArrival3D cmp engineMode, MODE_WALK jne arrival_test_fail + cmp welcomeActive3D,1 + jne arrival_test_fail + call CloseWelcome3D cmp playerY, (27*FP_1+FP_HALF) jne arrival_test_fail invoke CanWalk3D, playerX, playerY diff --git a/tools/tests/casino_actions_test.inc b/tools/tests/casino_actions_test.inc index 6dd550e..ccccd2a 100644 --- a/tools/tests/casino_actions_test.inc +++ b/tools/tests/casino_actions_test.inc @@ -231,6 +231,14 @@ action_studio_snapshots: mov keys3D[57h], 1 mov pressedKeys3D[45h], 1 call HandleStartupInput3D + cmp startupPage3D,STARTUP_AI + jne action_fail_15 + mov pressedKeys3D[0Dh],1 + call HandleStartupInput3D + cmp startupPage3D,STARTUP_CODE + jne action_fail_15 + mov pressedKeys3D[0Dh],1 + call HandleStartupInput3D cmp engineMode, MODE_ARRIVAL jne action_fail_15 cmp keys3D[57h], 0 @@ -256,6 +264,9 @@ action_studio_snapshots: jz action_fail_15 mov pressedKeys3D[20h], 1 call HandleStartupInput3D + cmp startupPage3D,STARTUP_AI + jne action_fail_15 + call FinishStartup3D cmp engineMode, MODE_ARRIVAL jne action_fail_15 cmp rngSeed3D, 12345678h diff --git a/tools/tests/check_jackbox.py b/tools/tests/check_jackbox.py index 069bc84..e54d0e4 100644 --- a/tools/tests/check_jackbox.py +++ b/tools/tests/check_jackbox.py @@ -93,6 +93,12 @@ def test_code_structure(): assert "call RenderJukeboxVolumeHUD" in casino_inc assert "jboxUserVolume dd" in data_inc assert "setaudio jboxmusic volume to" in data_inc + play_proc = code_inc.split("PlayJukeboxTrack3D PROC", 1)[1].split("PlayJukeboxTrack3D ENDP", 1)[0] + assert play_proc.index("call UpdateJukeboxAudio3D") < play_proc.index("OFFSET jboxPlayCmd"), "Set gain before playback" + arrival_inc = (ROOT / "src_graphics/arrival_scene.inc").read_bytes().decode("cp1251") + entry = arrival_inc.split("arrival_entry:", 1)[1].split("arrival_profile_input:", 1)[0] + assert entry.index("mov engineMode, MODE_WALK") < entry.index("mov jboxUserVolume, 5") + assert "invoke PlayJukeboxTrack3D, 0" in entry assert "call LoadJackboxArt3D" in world_inc assert "call FreeJackboxArt3D" in world_inc diff --git a/tools/tests/check_presentation.py b/tools/tests/check_presentation.py new file mode 100644 index 0000000..67ee2e5 --- /dev/null +++ b/tools/tests/check_presentation.py @@ -0,0 +1,81 @@ +"""Compile and verify real MASM startup pages and the post-admission briefing.""" +import argparse +import hashlib +from pathlib import Path +import subprocess +import sys +from PIL import Image, ImageChops, ImageDraw +from check_artifacts import toolchain + +ROOT = Path(__file__).resolve().parents[2] +OUT = ROOT/'build/scratch' +sys.path.insert(0,str(ROOT/'tools/generators')) +from make_presentation_text import TEXT + + +def snapshot_runtime(): + return {p:hashlib.sha256(p.read_bytes()).digest() + for p in (ROOT/'runtime').rglob('*') if p.is_file()} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--window',action='store_true') + args = parser.parse_args() + OUT.mkdir(parents=True,exist_ok=True) + # Prevent silent clipping of the Russian copy or a missing font character. + for name,text in TEXT.items(): + assert all(len(line)<=39 for line in text.splitlines()),name + assert b'\x97' not in text.encode('cp1251'),name + before = snapshot_runtime() + ml,link = toolchain() + sdk = Path('C:/Program Files (x86)/Windows Kits/10/Lib') + lib = next(p/'um/x86' for p in sorted(sdk.iterdir(),reverse=True) + if (p/'um/x86/kernel32.lib').exists()) + commands = [[ml,'/c','/coff','/Cp','/nologo','/I.','/DPRESENTATION_TEST=1', + '/Fobuild/scratch/presentation-test.obj','poker.asm'], + [link,'/nologo','/SUBSYSTEM:CONSOLE','/LIBPATH:'+str(lib), + '/OUT:build/scratch/presentation-test.exe','build/scratch/presentation-test.obj', + 'kernel32.lib','winmm.lib','user32.lib','ws2_32.lib','gdi32.lib']] + if args.window: + commands[0].insert(1,'/DPRESENTATION_WINDOW_TEST=1') + try: + for command in commands: + subprocess.run(list(map(str,command)),cwd=ROOT,check=True,timeout=120) + result = subprocess.run([str(OUT/'presentation-test.exe')],cwd=ROOT,timeout=120) + assert result.returncode == 0,f'Native presentation check failed: {result.returncode:#x}' + finally: + assert before == snapshot_runtime(),'Test changed player saves' + frames=[] + for index in range(12): + raw = (OUT/f'presentation-{index:02}.bgra').read_bytes() + assert len(raw)==640*480*4 + image=Image.frombytes('RGBA',(640,480),raw,'raw','BGRA').convert('RGB') + image.save(OUT/f'presentation-{index:02}.png') + frames.append(image) + assert len({im.tobytes() for im in frames})==len(frames),'Frozen/repeated presentation' + for index in (1,2): + assert len(frames[index].getcolors(640*480))>5,'Missing native text' + # Opening starts at screen centre and expands horizontally, then vertically. + for index in (3,4,5,6,7): + image=frames[index] + assert image.getpixel((320,240)) in ((62,231,216),(8,16,27),(220,75,155)),index + assert ImageChops.difference(frames[9],frames[11]).getbbox(),'Typewriter did not advance' + sheet=Image.new('RGB',(3*640,4*504),(8,12,20)) + draw=ImageDraw.Draw(sheet) + for index,image in enumerate(frames): + x,y=index%3*640,index//3*504 + sheet.paste(image,(x,y)) + draw.text((x+12,y+484),f'Native ASM / {index:02}',fill='white') + sheet.save(OUT/'presentation-contact.png') + if args.window: + image=Image.frombytes('RGBA',(640,480),(OUT/'presentation-window.bgra').read_bytes(),'raw','BGRA') + image.convert('RGB').save(OUT/'presentation-window.png') + print('PASS: ordered startup, automatic/key/skip transitions, guest admission, ' + 'centred deployment, typewriter, input cleanup, 50% entry music, ' + 'MCI playback/volume, no modal restart, manual track/stop, unchanged saves/RNG/bank; ' + '12 native captures'+('; 150 Win32 frames' if args.window else '')) + + +if __name__=='__main__': + main() diff --git a/tools/tests/presentation_test.inc b/tools/tests/presentation_test.inc new file mode 100644 index 0000000..2f2dad2 --- /dev/null +++ b/tools/tests/presentation_test.inc @@ -0,0 +1,368 @@ +; Exercise the shipping ASM startup, admission, overlay and input procedures. +.data +presTestStage dd 1 +presTestIo dd 0 +presTestPath db 'build\scratch\presentation-00.bgra',0 +presTestWindow db 'build\scratch\presentation-window.bgra',0 +presTestTimes dd 0,100,200,300,450,600,1200,2400,10000 +presTestX dd 0 +presTestY dd 0 +presTestAngle dd 0 +presTestBank dd 0 +presTestRng dd 0 +presTestMusicMode db 'status jboxmusic mode',0 +presTestMusicVolume db 'status jboxmusic volume',0 +presTestMusicPosition db 'status jboxmusic position',0 +presTestMusicSeek db 'seek jboxmusic to 5000',0 +presTestMusicReply db 64 dup(0) +.code +PresTestMusicNumber PROC uses esi ebx command:DWORD + invoke mciSendStringA,command,OFFSET presTestMusicReply,64,0 + test eax,eax + jnz pres_music_number_failed + xor eax,eax + lea esi,presTestMusicReply +pres_music_number_digit: + movzx ebx,BYTE PTR [esi] + test ebx,ebx + jz pres_music_number_done + sub ebx,'0' + cmp ebx,9 + ja pres_music_number_failed + imul eax,10 + add eax,ebx + inc esi + jmp pres_music_number_digit +pres_music_number_failed: + mov eax,-1 +pres_music_number_done: + ret +PresTestMusicNumber ENDP + +PresTestCapture PROC uses ebx path:DWORD + invoke CreateFileA,path,40000000h,0,0,2,80h,0 + cmp eax,-1 + je pres_capture_failed + mov ebx,eax + invoke WriteFile,ebx,OFFSET screenBuffer,SCREEN_SIZE*4,OFFSET presTestIo,0 + push eax + invoke CloseHandle,ebx + pop eax + ret +pres_capture_failed: + xor eax,eax + ret +PresTestCapture ENDP + +RunPresentationTest3D PROC uses esi edi ebx + call InitPaths3D + call InitHall3D + call LoadTextures3D + call LoadHallAssets3D + call InitializeCasinoSprites3D + mov rngSeed3D,12345678h + mov bankroll,12345 + mov keys3D[57h],1 + call BeginStartup3D + cmp startupPage3D,STARTUP_STUDIO + jne pres_test_failed + cmp keys3D[57h],0 + jne pres_test_failed + mov startupElapsed3D,2500 + call RenderStartup3D + invoke PresTestCapture,OFFSET presTestPath + mov presTestStage,2 + ; No early transition. At the boundary, studio -> AI (not arrival). + mov startupElapsed3D,6999 + mov deltaTime3D,0 + call HandleStartupInput3D + cmp startupPage3D,STARTUP_STUDIO + jne pres_test_failed + mov deltaTime3D,1 + call HandleStartupInput3D + cmp startupPage3D,STARTUP_AI + jne pres_test_failed + cmp startupElapsed3D,0 + jne pres_test_failed + cmp engineMode,MODE_INTRO + jne pres_test_failed + mov startupElapsed3D,1000 + call RenderStartup3D + mov presTestPath[28],'1' + invoke PresTestCapture,OFFSET presTestPath + ; Enter skips only this page, and held movement keys cannot leak through. + mov keys3D[57h],1 + mov pressedKeys3D[0Dh],1 + mov deltaTime3D,0 + call HandleStartupInput3D + cmp startupPage3D,STARTUP_CODE + jne pres_test_failed + cmp keys3D[57h],0 + jne pres_test_failed + mov startupElapsed3D,1500 + call RenderStartup3D + mov presTestPath[28],'2' + invoke PresTestCapture,OFFSET presTestPath + mov presTestStage,3 + mov startupElapsed3D,11000 + call HandleStartupInput3D + cmp engineMode,MODE_ARRIVAL + jne pres_test_failed + cmp arrivalState3D,ARRIVAL_WALK + jne pres_test_failed + cmp cityPhase3D,CITY_BLACK + jne pres_test_failed + cmp welcomeActive3D,0 + jne pres_test_failed + cmp jboxIsPlaying,0 + jne pres_test_failed + cmp startupPixels3D,0 + jne pres_test_failed + cmp startupSequence3D,0 + jne pres_test_failed + ; Full automatic order, followed by a fresh-launch reset and Escape path. + call BeginStartup3D + mov deltaTime3D,7000 + call HandleStartupInput3D + mov deltaTime3D,10000 + call HandleStartupInput3D + cmp startupPage3D,STARTUP_CODE + jne pres_test_failed + mov deltaTime3D,11000 + call HandleStartupInput3D + cmp engineMode,MODE_ARRIVAL + jne pres_test_failed + call BeginStartup3D + cmp startupPage3D,STARTUP_STUDIO + jne pres_test_failed + call FinishStartup3D + cmp engineMode,MODE_ARRIVAL + jne pres_test_failed + mov presTestStage,4 + ; Guest profile -> admission -> hall triggers the modal only after entry. + mov arrivalState3D,ARRIVAL_PROFILE + mov arrivalSelected3D,0 + mov deltaTime3D,0 + mov pressedKeys3D[0Dh],1 + call HandleArrival3D + cmp arrivalConfirmed3D,1 + jne pres_test_failed + cmp welcomeActive3D,0 + jne pres_test_failed + cmp jboxIsPlaying,0 + jne pres_test_failed + mov deltaTime3D,1200 + call HandleArrival3D + cmp arrivalState3D,ARRIVAL_ENTER + jne pres_test_failed + cmp welcomeActive3D,0 + jne pres_test_failed + cmp jboxIsPlaying,0 + jne pres_test_failed + mov deltaTime3D,1400 + call HandleArrival3D + cmp engineMode,MODE_WALK + jne pres_test_failed + cmp welcomeActive3D,1 + jne pres_test_failed + cmp welcomeElapsed3D,0 + jne pres_test_failed + mov presTestStage,41 + cmp jboxIsPlaying,1 + jne pres_test_failed + cmp jboxCurrentTrack,0 + jne pres_test_failed + cmp jboxUserVolume,5 + jne pres_test_failed + mov presTestStage,42 + invoke mciSendStringA,OFFSET presTestMusicMode,OFFSET presTestMusicReply,64,0 + test eax,eax + jnz pres_test_failed + cmp DWORD PTR presTestMusicReply,79616C70h ; "play" in "playing" + jne pres_test_failed + mov presTestStage,43 + invoke PresTestMusicNumber,OFFSET presTestMusicVolume + cmp eax,-1 + je pres_test_failed + cmp eax,500 + ja pres_test_failed + test eax,eax + jz pres_test_failed + ; Windows MCI rounds through device gain; allow a few units out of 1000. + sub eax,jboxEffectiveVolume + jns @F + neg eax +@@: + cmp eax,4 + ja pres_test_failed + mov eax,playerX + mov presTestX,eax + mov eax,playerY + mov presTestY,eax + mov eax,playerAngle + mov presTestAngle,eax + mov eax,bankroll + mov presTestBank,eax + mov eax,rngSeed3D + mov presTestRng,eax + mov presTestStage,5 + xor esi,esi +pres_test_overlay_frames: + mov eax,presTestTimes[esi*4] + mov welcomeElapsed3D,eax + mov deltaTime3D,0 + call HandleWelcome3D + call RenderWelcomeScene3D + mov eax,esi + add al,'3' + cmp al,'9' + jbe @F + sub al,10 + mov presTestPath[27],'1' +@@: + mov presTestPath[28],al + invoke PresTestCapture,OFFSET presTestPath + test eax,eax + jz pres_test_failed + inc esi + cmp esi,9 + jb pres_test_overlay_frames + mov eax,presTestX + cmp eax,playerX + jne pres_test_failed + mov eax,presTestY + cmp eax,playerY + jne pres_test_failed + mov eax,presTestAngle + cmp eax,playerAngle + jne pres_test_failed + mov eax,presTestRng + cmp eax,rngSeed3D + jne pres_test_failed + mov eax,presTestBank + cmp eax,bankroll + jne pres_test_failed + mov presTestStage,6 + ; Put playback past zero: closing/reopening the modal must not restart it. + invoke mciSendStringA,OFFSET presTestMusicSeek,0,0,0 + test eax,eax + jnz pres_test_failed + invoke mciSendStringA,OFFSET jboxPlayCmd,0,0,0 + test eax,eax + jnz pres_test_failed + mov pressedKeys3D[0BDh],1 + call HandleJukeboxKeys3D + cmp jboxUserVolume,4 + jne pres_test_failed + ; Early input cannot skip the deployment; typing completion is persistent. + call BeginWelcome3D + mov deltaTime3D,100 + mov pressedKeys3D[0Dh],1 + call HandleWelcome3D + cmp welcomeActive3D,1 + jne pres_test_failed + cmp welcomeChars3D,0 + jne pres_test_failed + call ClearKeys3D + mov deltaTime3D,700 + call HandleWelcome3D + cmp welcomeChars3D,0 + je pres_test_failed + cmp welcomeChars3D,welcomeTextLength3D + jae pres_test_failed + mov pressedKeys3D[0Dh],1 + mov keys3D[57h],1 + call HandleWelcome3D + cmp welcomeActive3D,1 + jne pres_test_failed + cmp welcomeChars3D,welcomeTextLength3D + jne pres_test_failed + cmp keys3D[57h],0 + jne pres_test_failed + mov deltaTime3D,1 + call HandleWelcome3D + cmp welcomeChars3D,welcomeTextLength3D + jne pres_test_failed + mov pressedKeys3D[0Dh],1 + call HandleWelcome3D + cmp welcomeActive3D,0 + jne pres_test_failed + cmp pressedKeys3D[0Dh],0 + jne pres_test_failed + cmp jboxUserVolume,4 + jne pres_test_failed + invoke PresTestMusicNumber,OFFSET presTestMusicPosition + cmp eax,-1 + je pres_test_failed + cmp eax,5000 + jb pres_test_failed + ; Escape uses the same clean close; click stays inside the button. + call BeginWelcome3D + call CloseWelcome3D + cmp welcomeActive3D,0 + jne pres_test_failed + cmp paused3D,0 + jne pres_test_failed + call BeginWelcome3D + mov welcomeElapsed3D,10000 + mov mouseX3D,220 + mov mouseY3D,190 + mov mouseClick3D,1 + call HandleWelcome3D + cmp welcomeActive3D,0 + jne pres_test_failed +IFDEF PRESENTATION_WINDOW_TEST + mov presTestStage,7 + call Create3DWindow + test eax,eax + jz pres_test_failed + cmp fullscreen3D,0 + je @F + call ToggleFullscreen3D +@@: + invoke SetWindowPos,h3DWnd,0,80,80,800,640,14h + call BeginWelcome3D + invoke ShowWindow,h3DWnd,4 + mov esi,150 +pres_test_window_frame: + invoke PeekMessageA,OFFSET msg3D,0,0,0,PM_REMOVE + test eax,eax + jz pres_test_window_tick + invoke TranslateMessage,OFFSET msg3D + invoke DispatchMessageA,OFFSET msg3D + jmp pres_test_window_frame +pres_test_window_tick: + mov deltaTime3D,50 + call HandleWelcome3D + call RenderWelcomeScene3D + call Present3D + invoke Sleep,17 + dec esi + jnz pres_test_window_frame + invoke PresTestCapture,OFFSET presTestWindow + call CloseWelcome3D + invoke DestroyWindow,h3DWnd +ENDIF + mov presTestStage,8 + ; Manual track switching and Stop still work after automatic entry playback. + invoke PlayJukeboxTrack3D,1 + cmp jboxIsPlaying,1 + jne pres_test_failed + cmp jboxCurrentTrack,1 + jne pres_test_failed + cmp jboxUserVolume,4 + jne pres_test_failed + call StopJukeboxTrack3D + cmp jboxIsPlaying,0 + jne pres_test_failed + cmp jboxCurrentTrack,-1 + jne pres_test_failed + call FreeStartupAssets3D + call FreeArrivalAssets3D + xor eax,eax + ret +pres_test_failed: + call StopJukeboxTrack3D + mov eax,presTestStage + ret +RunPresentationTest3D ENDP