feat: add startup briefing and entry music

Sequence studio, texture AI disclosure and MASM code comparison before arrival. Show a typed hall briefing after admission.

Start the jukebox at 50% user volume on hall entry. Apply positional gain before playback to avoid a full-volume startup burst.
This commit is contained in:
Игорь Федосеев 2026-11-02 01:04:11 +03:00
parent 3150d217b5
commit 24d66ddf0e
22 changed files with 1171 additions and 7 deletions

View file

@ -33,11 +33,18 @@ $6,660; death/retry preserves the casino bank. See
## Native Entrance And Character Scenes ## 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 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 cigarette, walking and turning through a separate city map, then meeting the
guards and choosing a local profile before entering the walkable hall. guards and choosing a local profile before entering the walkable hall.
The sequence resets on every launch; the intro advances automatically. 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 Sidorovich operates the native
card-back shop. New character sheets contain 32 guard poses and 24 merchant card-back shop. New character sheets contain 32 guard poses and 24 merchant
poses, with separate actions rather than a single repeated idle animation. poses, with separate actions rather than a single repeated idle animation.

Binary file not shown.

View file

@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
{ {
static class Program static class Program
{ {
const string ExpectedPokerSha256 = "2357F5876DA47FD6CD7FAE7216C71FCEDE6CCA79C1591E2BA6CFD2F3DB61694E"; const string ExpectedPokerSha256 = "3D0237E09CAED350548457BFAB24C4D67D6FAFC3D689C2D87C8BE360B64EC0AF";
[STAThread] [STAThread]
static void Main() static void Main()

Binary file not shown.

View file

@ -150,6 +150,8 @@ include src_graphics\platform_data.inc
include src_graphics\casino_actions_data.inc include src_graphics\casino_actions_data.inc
include src_graphics\audio_data.inc include src_graphics\audio_data.inc
include src_graphics\startup_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\card_tables_data.inc
include src_graphics\native_durak_data.inc include src_graphics\native_durak_data.inc
include src_graphics\scene_animation_data.inc include src_graphics\scene_animation_data.inc
@ -4497,6 +4499,8 @@ unlock_done:
UnlockMouse3D ENDP UnlockMouse3D ENDP
UpdateMouseLockState PROC UpdateMouseLockState PROC
cmp welcomeActive3D,0
jne unlock_it
cmp engineMode,MODE_HELL_FPS cmp engineMode,MODE_HELL_FPS
je lock_interior_check je lock_interior_check
cmp engineMode,MODE_STAIRS cmp engineMode,MODE_STAIRS
@ -5241,6 +5245,7 @@ include src_graphics\audio_code.inc
include src_graphics\dice_animation.inc include src_graphics\dice_animation.inc
include src_graphics\russian_roulette.inc include src_graphics\russian_roulette.inc
include src_graphics\startup_code.inc include src_graphics\startup_code.inc
include src_graphics\presentation_code.inc
include src_graphics\card_table_assets.inc include src_graphics\card_table_assets.inc
include src_graphics\poker_evaluator.inc include src_graphics\poker_evaluator.inc
include src_graphics\native_poker.inc include src_graphics\native_poker.inc
@ -5341,6 +5346,11 @@ engine_observer_skip:
invoke Pressed3D, 1Bh invoke Pressed3D, 1Bh
test eax, eax test eax, eax
jz engine_tick jz engine_tick
cmp welcomeActive3D,0
je @F
call CloseWelcome3D
jmp engine_tick
@@:
cmp engineMode,MODE_HELL_FPS cmp engineMode,MODE_HELL_FPS
jne @F jne @F
call LeaveHellFPS3D call LeaveHellFPS3D
@ -5441,6 +5451,12 @@ check_walk_mode:
@@: @@:
cmp engineMode, MODE_WALK cmp engineMode, MODE_WALK
jne check_slots_mode jne check_slots_mode
cmp welcomeActive3D,0
je welcome_walk_ready
call HandleWelcome3D
call RenderWelcomeScene3D
jmp present_frame
welcome_walk_ready:
cmp brActive3D,0 cmp brActive3D,0
jne engine_backrooms jne engine_backrooms
call UpdateHall3D call UpdateHall3D

Binary file not shown.

View file

@ -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.

View file

@ -2072,7 +2072,14 @@ ENDIF
IFDEF HELL_FPS_TEST IFDEF HELL_FPS_TEST
include tools/tests/hell_fps_test.inc include tools/tests/hell_fps_test.inc
ENDIF ENDIF
IFDEF PRESENTATION_TEST
include tools/tests/presentation_test.inc
ENDIF
main PROC main PROC
IFDEF PRESENTATION_TEST
call RunPresentationTest3D
invoke ExitProcess,eax
ENDIF
IFDEF HELL_FPS_TEST IFDEF HELL_FPS_TEST
IFDEF HELL_FPS_WINDOW_TEST IFDEF HELL_FPS_WINDOW_TEST
call RunHellFPSWindowTest call RunHellFPSWindowTest

BIN
poker.exe

Binary file not shown.

BIN
poker.obj

Binary file not shown.

View file

@ -128,6 +128,10 @@ arrival_entry:
mov interactTargetId, 0 mov interactTargetId, 0
mov interactPromptPtr, 0 mov interactPromptPtr, 0
call ClearKeys3D call ClearKeys3D
call BeginWelcome3D
; Start only after admission, with the normal positional attenuation.
mov jboxUserVolume, 5
invoke PlayJukeboxTrack3D, 0
ret ret
arrival_profile_input: arrival_profile_input:
invoke Pressed3D, 26h invoke Pressed3D, 26h
@ -309,4 +313,3 @@ entry_rows_done:
invoke DrawString8x8, 223, 207, OFFSET entryConfirm3D, 00FFFFFFh invoke DrawString8x8, 223, 207, OFFSET entryConfirm3D, 00FFFFFFh
ret ret
DrawEntryProfileRows3D ENDP DrawEntryProfileRows3D ENDP

View file

@ -118,11 +118,18 @@ PlayJukeboxTrack3D PROC uses ebx trackIndex:DWORD
test eax, eax test eax, eax
jz jbox_play_stop_selected jz jbox_play_stop_selected
invoke mciSendStringA, eax, 0, 0, 0 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 jboxCurrentTrack, ebx
mov jboxIsPlaying, 1 mov jboxIsPlaying, 1
mov jboxEffectiveVolume, -1 mov jboxEffectiveVolume, -1
; Apply volume before play: never start a newly opened device at full gain.
call UpdateJukeboxAudio3D call UpdateJukeboxAudio3D
invoke mciSendStringA, OFFSET jboxPlayCmd, 0, 0, 0
test eax, eax
jz jbox_play_done
call StopJukeboxTrack3D
jbox_play_done:
ret ret
jbox_play_stop_selected: jbox_play_stop_selected:
mov jboxCurrentTrack, -1 mov jboxCurrentTrack, -1

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -70,6 +70,8 @@ BeginStartup3D PROC
mov interactTargetId, 0 mov interactTargetId, 0
mov interactPromptPtr, 0 mov interactPromptPtr, 0
mov startupElapsed3D, 0 mov startupElapsed3D, 0
mov startupPage3D, STARTUP_STUDIO
mov welcomeActive3D, 0
mov startupVariant3D, 0 mov startupVariant3D, 0
mov animationClock3D, 0 mov animationClock3D, 0
mov rr3d_state, RR_READY mov rr3d_state, RR_READY
@ -89,12 +91,26 @@ FinishStartup3D PROC
ret ret
FinishStartup3D ENDP 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 HandleStartupInput3D PROC
mov eax, deltaTime3D mov eax, deltaTime3D
add startupElapsed3D, eax add startupElapsed3D, eax
invoke Pressed3D, 71h invoke Pressed3D, 71h
test eax, eax test eax, eax
jz @F jz @F
cmp startupPage3D,STARTUP_STUDIO
jne @F
xor startupVariant3D, 1 xor startupVariant3D, 1
mov startupElapsed3D, 0 mov startupElapsed3D, 0
@@: @@:
@ -105,18 +121,22 @@ HandleStartupInput3D PROC
test eax, eax test eax, eax
jz startup_input_done jz startup_input_done
startup_enter: startup_enter:
call FinishStartup3D call AdvanceStartup3D
startup_input_done: startup_input_done:
cmp engineMode, MODE_INTRO cmp engineMode, MODE_INTRO
jne @F jne @F
cmp startupElapsed3D, 7000 mov ecx,startupPage3D
mov eax,startupElapsed3D
cmp eax,startupDurations3D[ecx*4]
jb @F jb @F
call FinishStartup3D call AdvanceStartup3D
@@: @@:
ret ret
HandleStartupInput3D ENDP HandleStartupInput3D ENDP
RenderStartup3D PROC RenderStartup3D PROC
cmp startupPage3D,STARTUP_STUDIO
jne startup_notice
cmp startupVariant3D, 0 cmp startupVariant3D, 0
jne startup_studio_variant jne startup_studio_variant
cmp startupSequence3D, 0 cmp startupSequence3D, 0
@ -128,6 +148,9 @@ startup_studio_variant:
call RenderStudioStartup3D call RenderStudioStartup3D
call FadeStartupOut3D call FadeStartupOut3D
ret ret
startup_notice:
call RenderStartupNotice3D
ret
RenderStartup3D ENDP RenderStartup3D ENDP
FadeStartupOut3D PROC FadeStartupOut3D PROC

View file

@ -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()

View file

@ -202,6 +202,14 @@ RunArrivalTest3D PROC uses ebx esi edi
mov startupElapsed3D, 6990 mov startupElapsed3D, 6990
mov deltaTime3D, 16 mov deltaTime3D, 16
call HandleStartupInput3D 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 cmp engineMode, MODE_ARRIVAL
jne arrival_test_fail jne arrival_test_fail
cmp cityPhase3D, CITY_BLACK cmp cityPhase3D, CITY_BLACK
@ -595,6 +603,9 @@ arrival_admit_sequence:
call HandleArrival3D call HandleArrival3D
cmp engineMode, MODE_WALK cmp engineMode, MODE_WALK
jne arrival_test_fail jne arrival_test_fail
cmp welcomeActive3D,1
jne arrival_test_fail
call CloseWelcome3D
cmp playerY, (27*FP_1+FP_HALF) cmp playerY, (27*FP_1+FP_HALF)
jne arrival_test_fail jne arrival_test_fail
invoke CanWalk3D, playerX, playerY invoke CanWalk3D, playerX, playerY

View file

@ -231,6 +231,14 @@ action_studio_snapshots:
mov keys3D[57h], 1 mov keys3D[57h], 1
mov pressedKeys3D[45h], 1 mov pressedKeys3D[45h], 1
call HandleStartupInput3D 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 cmp engineMode, MODE_ARRIVAL
jne action_fail_15 jne action_fail_15
cmp keys3D[57h], 0 cmp keys3D[57h], 0
@ -256,6 +264,9 @@ action_studio_snapshots:
jz action_fail_15 jz action_fail_15
mov pressedKeys3D[20h], 1 mov pressedKeys3D[20h], 1
call HandleStartupInput3D call HandleStartupInput3D
cmp startupPage3D,STARTUP_AI
jne action_fail_15
call FinishStartup3D
cmp engineMode, MODE_ARRIVAL cmp engineMode, MODE_ARRIVAL
jne action_fail_15 jne action_fail_15
cmp rngSeed3D, 12345678h cmp rngSeed3D, 12345678h

View file

@ -93,6 +93,12 @@ def test_code_structure():
assert "call RenderJukeboxVolumeHUD" in casino_inc assert "call RenderJukeboxVolumeHUD" in casino_inc
assert "jboxUserVolume dd" in data_inc assert "jboxUserVolume dd" in data_inc
assert "setaudio jboxmusic volume to" 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 LoadJackboxArt3D" in world_inc
assert "call FreeJackboxArt3D" in world_inc assert "call FreeJackboxArt3D" in world_inc

View file

@ -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()

View file

@ -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