feat: update casino environments

This commit is contained in:
Игорь Федосеев 2026-10-31 02:28:37 +03:00
parent 5dedaf459f
commit 7e4f7d7be1
36 changed files with 2332 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Binary file not shown.

View file

@ -8,7 +8,7 @@ namespace AssemblerCasinoLauncher
{ {
static class Program static class Program
{ {
const string ExpectedPokerSha256 = "25FBDF78EDF3DFD3F6B72657E78C3156F74931A656B4CBDB15260D496C96849A"; const string ExpectedPokerSha256 = "B0F5CE31D9A2467CFE8AF66CA90CC545F75318F11D149473A4377A2A278C8B23";
[STAThread] [STAThread]
static void Main() static void Main()

Binary file not shown.

View file

@ -170,7 +170,8 @@ include src_graphics\native_coin_data.inc
include src_graphics\native_dice_data.inc include src_graphics\native_dice_data.inc
include src_graphics\scratch_data.inc include src_graphics\scratch_data.inc
include src_graphics\hell_tower_data.inc include src_graphics\hell_tower_data.inc
include src_graphics\charms_data.inc include src_graphics\charms_data.inc
include src_graphics\backrooms_data.inc
include src_graphics\observer_data.inc include src_graphics\observer_data.inc
include src_graphics\darts_data.inc include src_graphics\darts_data.inc
include src_graphics\jackbox_data.inc include src_graphics\jackbox_data.inc
@ -471,7 +472,7 @@ screenBuffer dd SCREEN_SIZE dup(?)
ALIGN 4 ALIGN 4
zBuffer dd SCREEN_W dup(?) zBuffer dd SCREEN_W dup(?)
ALIGN 4 ALIGN 4
texturesBuffer dd (104 * 128 * 128) dup(?) texturesBuffer dd (185 * 128 * 128) dup(?)
hudNumBuffer db 32 dup(?) hudNumBuffer db 32 dup(?)
@ -3249,6 +3250,25 @@ dice_start_roll:
invoke RandomRange3D, 6 invoke RandomRange3D, 6
inc eax inc eax
mov dice3d_val2, eax mov dice3d_val2, eax
call BackroomsLuck3D
cmp eax,-1
je br_dice_fixed
xor eax,1
mov ecx,dice3d_choice
dec ecx
xor eax,ecx
mov edx,dice3d_val1
add edx,dice3d_val2
and edx,1
cmp eax,edx
je br_dice_fixed
xor edx,edx
mov eax,dice3d_val2
mov ecx,6
div ecx
inc edx
mov dice3d_val2,edx
br_dice_fixed:
invoke PlayCasinoSound3D, OFFSET soundDiceRoll, 0, 1 invoke PlayCasinoSound3D, OFFSET soundDiceRoll, 0, 1
call SaveProgress3D call SaveProgress3D
ret ret
@ -3485,6 +3505,21 @@ InitSkullsGame ENDP
FinalizeSkullsGuess PROC uses ebx FinalizeSkullsGuess PROC uses ebx
cmp skulls3d_state,3 cmp skulls3d_state,3
jne skulls_guess_done jne skulls_guess_done
cmp brSkullsLuck3D,-1
je br_skulls_fixed
mov eax,skulls3d_choice
cmp brSkullsLuck3D,1
je @F
inc eax
xor edx,edx
mov ecx,3
div ecx
mov eax,edx
@@:
mov skulls3d_ballPos,eax
; Reality distortion explicitly covers relocation before the reveal.
mov skulls3d_timer,0
br_skulls_fixed:
mov eax, skulls3d_choice mov eax, skulls3d_choice
cmp eax, skulls3d_ballPos cmp eax, skulls3d_ballPos
jne skulls_guess_wrong jne skulls_guess_wrong
@ -3568,6 +3603,8 @@ skulls_bet_down:
ret ret
skulls_start_game: skulls_start_game:
call BackroomsLuck3D
mov brSkullsLuck3D,eax
mov eax, skulls3d_bet mov eax, skulls3d_bet
cmp eax, bankroll cmp eax, bankroll
jle @F jle @F
@ -3716,6 +3753,16 @@ RenderSkullsGame PROC uses ebx esi edi
jne @F jne @F
invoke DrawString8x8, 28, 62, OFFSET textSkullsPhase3, 00FFFF00h invoke DrawString8x8, 28, 62, OFFSET textSkullsPhase3, 00FFFF00h
jmp skulls_draw_idle_cups jmp skulls_draw_idle_cups
@@:
; Short artifact glitch, then reveal the actual ball and matching payout.
cmp brSkullsLuck3D,-1
je @F
cmp skulls3d_timer,12
jae @F
inc skulls3d_timer
invoke DrawBox,8,76,304,122,00130F20h
invoke DrawString8x8,108,128,OFFSET brRealityGlitch3D,00FF88CCh
jmp skulls_render_controls
@@: @@:
; Phase 4: Reveal outcome ; Phase 4: Reveal outcome
mov eax, skulls3d_win mov eax, skulls3d_win
@ -4633,6 +4680,8 @@ check_total_movement:
mov playerY, eax mov playerY, eax
input_action: input_action:
cmp brActive3D,0
jne player_input_complete
cmp engineMode,MODE_STAIRS cmp engineMode,MODE_STAIRS
je player_input_complete je player_input_complete
invoke Pressed3D, 45h invoke Pressed3D, 45h
@ -4706,9 +4755,12 @@ CheckInteractions ENDP
; (Legacy console arcade bezel bridge removed) ; (Legacy console arcade bezel bridge removed)
Trigger3DInteraction PROC Trigger3DInteraction PROC
cmp brActive3D,0
jne trigger_backrooms_station
call TriggerHallInteraction3D call TriggerHallInteraction3D
test eax, eax test eax, eax
jnz trigger_done jnz trigger_done
trigger_backrooms_station:
mov eax, interactTargetId mov eax, interactTargetId
test eax, eax test eax, eax
jz trigger_done jz trigger_done
@ -5200,7 +5252,8 @@ include src_graphics\hell_tower.inc
include src_graphics\charms.inc include src_graphics\charms.inc
include src_graphics\observer.inc include src_graphics\observer.inc
include src_graphics\darts.inc include src_graphics\darts.inc
include src_graphics\jackbox.inc include src_graphics\jackbox.inc
include src_graphics\backrooms.inc
; --- Main Loop of the 3D Casino Engine --- ; --- Main Loop of the 3D Casino Engine ---
StartHellTower3D PROC uses ebx esi edi StartHellTower3D PROC uses ebx esi edi
@ -5279,6 +5332,9 @@ engine_observer_skip:
cmp engineMode, MODE_WALK cmp engineMode, MODE_WALK
jne leave_table jne leave_table
pause_interior_walk: pause_interior_walk:
cmp brCutscene3D,0
jne engine_tick
mov brEntryHold3D,0
xor paused3D, 1 xor paused3D, 1
jmp engine_tick jmp engine_tick
leave_table: leave_table:
@ -5343,10 +5399,15 @@ check_arrival_mode:
check_walk_mode: check_walk_mode:
cmp engineMode, MODE_WALK cmp engineMode, MODE_WALK
jne check_slots_mode jne check_slots_mode
cmp brActive3D,0
jne engine_backrooms
call UpdateHall3D call UpdateHall3D
call UpdateCamera3D call UpdateCamera3D
call CheckInteractions call CheckInteractions
call HandlePlayerInput call HandlePlayerInput
call BackroomsEntry3D
cmp brActive3D,0
jne engine_backrooms
call HandleJukeboxKeys3D call HandleJukeboxKeys3D
call UpdateInteriorTravel3D call UpdateInteriorTravel3D
call UpdateInteriorEye3D call UpdateInteriorEye3D
@ -5365,6 +5426,14 @@ walk_world_ready:
call RenderHUD call RenderHUD
call RenderMinimap call RenderMinimap
jmp present_frame jmp present_frame
engine_backrooms:
call HandleBackrooms3D
cmp brActive3D,0
je check_walk_mode
cmp engineMode,MODE_WALK
jne check_slots_mode
call RenderBackrooms3D
jmp present_frame
check_slots_mode: check_slots_mode:
cmp engineMode,MODE_JACKBOX cmp engineMode,MODE_JACKBOX
jne check_jackbox_done jne check_jackbox_done
@ -5531,6 +5600,7 @@ check_russian_mode:
call RenderRussianGame3D call RenderRussianGame3D
jmp present_frame jmp present_frame
draw_pause: draw_pause:
mov brEntryHold3D,0
invoke DrawBoxBorder, 28, 36, 264, 168, 00151C21h, 00D8BC6Ah, 2 invoke DrawBoxBorder, 28, 36, 264, 168, 00151C21h, 00D8BC6Ah, 2
invoke DrawString8x8, 44, 48, OFFSET textPause3D, 00FFFFFFh invoke DrawString8x8, 44, 48, OFFSET textPause3D, 00FFFFFFh
cmp displayFill3D, 0 cmp displayFill3D, 0
@ -5559,7 +5629,10 @@ pause_draw_quality_value:
invoke DrawString8x8, 44, 130, OFFSET textSoundLevel3D, 00BBBBBBh invoke DrawString8x8, 44, 130, OFFSET textSoundLevel3D, 00BBBBBBh
invoke FormatNumber, settingsVolume, OFFSET hudNumBuffer invoke FormatNumber, settingsVolume, OFFSET hudNumBuffer
invoke DrawString8x8, 124, 130, OFFSET hudNumBuffer, 00FFFFFFh invoke DrawString8x8, 124, 130, OFFSET hudNumBuffer, 00FFFFFFh
invoke DrawString8x8, 44, 150, OFFSET textFullscreenKey3D, 00A0A0A0h cmp brOwned3D,0
jne @F
invoke DrawString8x8, 44, 150, OFFSET textFullscreenKey3D, 00A0A0A0h
@@:
invoke DrawString8x8, 44, 170, OFFSET textResume3D, 00D8BC6Ah invoke DrawString8x8, 44, 170, OFFSET textResume3D, 00D8BC6Ah
invoke DrawString8x8, 44, 188, OFFSET textQuit3D, 00BBBBBBh invoke DrawString8x8, 44, 188, OFFSET textQuit3D, 00BBBBBBh
invoke Pressed3D, 31h invoke Pressed3D, 31h
@ -5597,12 +5670,37 @@ pause_low_spec_saved:
call ApplySoundVolume call ApplySoundVolume
call SaveProgress3D call SaveProgress3D
@@: @@:
cmp brOwned3D,0
je br_pause_inventory_done
invoke DrawString8x8,44,150,OFFSET brToggle3D,00D8BC6Ah
invoke DrawString8x8,36,214,OFFSET brInventory3D,00D8BC6Ah
mov edx,OFFSET brDisabledText3D
cmp brEnabled3D,0
je @F
mov edx,OFFSET brEnabledText3D
@@:
invoke DrawString8x8,248,150,edx,00FFFFFFh
invoke Pressed3D,52h
test eax,eax
jz br_pause_inventory_done
xor brEnabled3D,1
call SaveProgress3D
br_pause_inventory_done:
invoke Pressed3D, 51h invoke Pressed3D, 51h
test eax, eax test eax, eax
jz @F jz @F
call SaveProgress3D call SaveProgress3D
jmp engine_shutdown jmp engine_shutdown
present_frame: present_frame:
cmp brActive3D,0
je br_record_clock_done
cmp engineMode,MODE_WALK
je br_record_clock_done
cmp paused3D,0
jne br_record_clock_done
mov eax,deltaTime3D
add brClock3D,eax
br_record_clock_done:
call UpdateJukeboxAudio3D call UpdateJukeboxAudio3D
call RenderTowerInGameBadge call RenderTowerInGameBadge
call RenderJukeboxVolumeHUD call RenderJukeboxVolumeHUD
@ -5638,6 +5736,7 @@ present_frame:
invoke Sleep, ecx invoke Sleep, ecx
jmp engine_loop jmp engine_loop
engine_idle: engine_idle:
mov brEntryHold3D,0
call UnlockMouse3D call UnlockMouse3D
invoke Sleep, 50 invoke Sleep, 50
jmp engine_loop jmp engine_loop
@ -5659,6 +5758,7 @@ engine_shutdown:
@@: @@:
call SaveProgress3D call SaveProgress3D
invoke PlayCasinoSound3D, 0, 0, 0 invoke PlayCasinoSound3D, 0, 0, 0
invoke FreeAnimPack3D,OFFSET brMaterials3D
call FreeRussianAssets3D call FreeRussianAssets3D
call FreeDarts3D call FreeDarts3D
call FreeStartupAssets3D call FreeStartupAssets3D

Binary file not shown.

View file

@ -820,7 +820,14 @@ save_stats_played_loop:
mov DWORD PTR [saveBuffer+296],eax mov DWORD PTR [saveBuffer+296],eax
mov eax,charmEnabledMask3D mov eax,charmEnabledMask3D
mov DWORD PTR [saveBuffer+300],eax mov DWORD PTR [saveBuffer+300],eax
mov DWORD PTR [saveBuffer+304],2 mov eax,brOwned3D
shl eax,16
mov ecx,brEnabled3D
and ecx,brOwned3D
shl ecx,17
or eax,ecx
or eax,2
mov DWORD PTR [saveBuffer+304],eax
invoke ArtifactSaveSignature3D,OFFSET saveBuffer invoke ArtifactSaveSignature3D,OFFSET saveBuffer
mov DWORD PTR [saveBuffer+308],eax mov DWORD PTR [saveBuffer+308],eax
call BuildSaveFullPath call BuildSaveFullPath
@ -887,6 +894,14 @@ load_file_opened:
mov charmDevilUses,eax mov charmDevilUses,eax
mov eax,DWORD PTR [saveBuffer+300] mov eax,DWORD PTR [saveBuffer+300]
mov charmEnabledMask3D,eax mov charmEnabledMask3D,eax
mov eax,DWORD PTR [saveBuffer+304]
shr eax,16
and eax,1
mov brOwned3D,eax
mov eax,DWORD PTR [saveBuffer+304]
shr eax,17
and eax,1
mov brEnabled3D,eax
load_signature_ok: load_signature_ok:
mov eax, DWORD PTR [saveBuffer+4] mov eax, DWORD PTR [saveBuffer+4]
mov bankroll, eax mov bankroll, eax
@ -2043,7 +2058,14 @@ test_error_exit_engine:
RunHellTowerTest3D ENDP RunHellTowerTest3D ENDP
ENDIF ENDIF
IFDEF BACKROOMS_TEST
include tools/tests/backrooms_test.inc
ENDIF
main PROC main PROC
IFDEF BACKROOMS_TEST
call RunBackroomsTest3D
invoke ExitProcess,eax
ENDIF
IFDEF HELL_TOWER_TEST IFDEF HELL_TOWER_TEST
call RunHellTowerTest3D call RunHellTowerTest3D
invoke ExitProcess,eax invoke ExitProcess,eax

BIN
poker.exe

Binary file not shown.

BIN
poker.obj

Binary file not shown.

View file

@ -1,4 +1,7 @@
HandleArtifactShop3D PROC uses ebx HandleArtifactShop3D PROC uses ebx
call HandleBackroomsInventory3D
test eax,eax
jnz artifact_shop_done
invoke Pressed3D,09h invoke Pressed3D,09h
test eax,eax test eax,eax
jz @F jz @F
@ -95,6 +98,11 @@ HandleArtifactShop3D ENDP
RenderArtifactShop3D PROC uses ebx esi edi RenderArtifactShop3D PROC uses ebx esi edi
local y:DWORD,x:DWORD,ink:DWORD local y:DWORD,x:DWORD,ink:DWORD
cmp brInventoryOpen3D,0
je @F
call RenderBackroomsInventory3D
ret
@@:
invoke DrawBox,0,0,320,240,0015191Bh invoke DrawBox,0,0,320,240,0015191Bh
invoke DrawBox,0,0,320,24,002B302Ah invoke DrawBox,0,0,320,24,002B302Ah
invoke DrawString8x8,8,7,OFFSET artifactShopTitle3D,00EED5A0h invoke DrawString8x8,8,7,OFFSET artifactShopTitle3D,00EED5A0h
@ -140,7 +148,13 @@ artifact_shop_card:
invoke DrawString8x8,120,77,charmDescs3D[ebx*4],00FFFFFFh invoke DrawString8x8,120,77,charmDescs3D[ebx*4],00FFFFFFh
; Detail descriptions use full width below cards to avoid clipped text. ; Detail descriptions use full width below cards to avoid clipped text.
invoke DrawString8x8,8,216,artifactRules3D[ebx*4],00DDCDAAh invoke DrawString8x8,8,216,artifactRules3D[ebx*4],00DDCDAAh
cmp brOwned3D,0
je @F
invoke DrawString8x8,8,228,OFFSET brInventoryHelp3D,00D8BC6Ah
jmp br_shop_help_done
@@:
invoke DrawString8x8,8,228,OFFSET artifactHelp3D,0099AAA5h invoke DrawString8x8,8,228,OFFSET artifactHelp3D,0099AAA5h
br_shop_help_done:
invoke DrawString8x8,120,96,OFFSET artifactPack3D,0099AAA5h invoke DrawString8x8,120,96,OFFSET artifactPack3D,0099AAA5h
invoke FormatNumber,charmUsesPack3D[ebx*4],OFFSET artifactNum3D invoke FormatNumber,charmUsesPack3D[ebx*4],OFFSET artifactNum3D
invoke DrawString8x8,280,96,OFFSET artifactNum3D,00FFFFFFh invoke DrawString8x8,280,96,OFFSET artifactNum3D,00FFFFFFh

View file

@ -32,8 +32,15 @@ ValidateArtifactSave3D PROC uses esi edi source:DWORD
mov esi,source mov esi,source
cmp DWORD PTR [esi+280],32465241h cmp DWORD PTR [esi+280],32465241h
jne artifact_save_invalid jne artifact_save_invalid
cmp DWORD PTR [esi+304],2 mov eax,[esi+304]
and eax,0FFFCFFFFh
cmp eax,2
jne artifact_save_invalid jne artifact_save_invalid
test DWORD PTR [esi+304],20000h
jz @F
test DWORD PTR [esi+304],10000h
jz artifact_save_invalid
@@:
invoke ArtifactSaveSignature3D,esi invoke ArtifactSaveSignature3D,esi
cmp eax,[esi+308] cmp eax,[esi+308]
jne artifact_save_invalid jne artifact_save_invalid
@ -65,6 +72,8 @@ ArtifactCredit3D PROC amount:DWORD
ArtifactCredit3D ENDP ArtifactCredit3D ENDP
ResetArtifacts3D PROC ResetArtifacts3D PROC
mov brOwned3D,0
mov brEnabled3D,1
mov charmCloverUses,0 mov charmCloverUses,0
mov charmHorseshoeUses,0 mov charmHorseshoeUses,0
mov charmSkullUses,0 mov charmSkullUses,0

1068
src_graphics/backrooms.inc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,86 @@
; Dedicated world/session. All coordinates are 16.16, all time is active ms.
HALL_BACKROOMS equ 6
BR_WALL equ 180
BR_FLOOR equ 181
BR_CEILING equ 182
BR_LAMP equ 183
BR_EXIT equ 184
BackroomsEntry3D PROTO
BeginBackrooms3D PROTO
GenerateBackrooms3D PROTO
HandleBackrooms3D PROTO
RenderBackrooms3D PROTO
LeaveBackrooms3D PROTO :DWORD
BackroomsLuck3D PROTO
BackroomsWheelLuck3D PROTO :DWORD
BackroomsDeck3D PROTO :DWORD,:DWORD,:DWORD
BackroomsBlackjackDeck3D PROTO
BackroomsPokerDeck3D PROTO
BackroomsDurakDeck3D PROTO
BackroomsBuckshotLuck3D PROTO
BackroomsRussianLuck3D PROTO
BackroomsRussianCylinder3D PROTO
LoadBackroomsAssets3D PROTO
HandleBackroomsInventory3D PROTO
RenderBackroomsInventory3D PROTO
.data
brActive3D dd 0
brEntryHold3D dd 0
brEntryAngle3D dd 0
brCutscene3D dd 0
brClock3D dd 0
brSkullsLuck3D dd -1
brSlotLuck3D dd -1
brRussianLuck3D dd -1
brBuckshotLuck3D dd -1
brDeckTargets3D dd 16 dup(0)
brDialogue3D dd 0
brOwned3D dd 0
brEnabled3D dd 1
brVhs3D dd 1
brExitCell3D dd 0
brTarget3D dd -1
brInventoryOpen3D dd 0
brMap3D db 1024 dup(BR_WALL)
brDistance3D dd 1024 dup(-1)
brStack3D dd 1024 dup(0)
brCandidates3D dd 4 dup(0)
brSteps3D dd -64,2,64,-2
brGames3D dd INTERACT_COIN,INTERACT_DICE,INTERACT_SLOTS_777,INTERACT_BLACKJACK,INTERACT_ROULETTE,INTERACT_SLOTS_CLOVER
brGameCells3D dd 6 dup(0)
brGamePacks3D dd OFFSET hallCardFurniture3D,OFFSET hallCardFurniture3D,OFFSET hallSlotFurniture3D,OFFSET hallCardFurniture3D,OFFSET hallWheelFurniture3D,OFFSET hallSlotFurniture3D
brGameFrames3D dd 0,0,0,0,0,1
brGamePrompts3D dd OFFSET brCoinPrompt3D,OFFSET brDicePrompt3D,OFFSET brSlotPrompt3D,OFFSET brCardPrompt3D,OFFSET brWheelPrompt3D,OFFSET brCloverPrompt3D
brCoinPrompt3D db 'E: COIN',0
brDicePrompt3D db 'E: DICE',0
brSlotPrompt3D db 'E: 777',0
brCardPrompt3D db 'E: BLACKJACK',0
brWheelPrompt3D db 'E: ROULETTE',0
brCloverPrompt3D db 'E: CLOVERPIT',0
brProp3D CityProp3D <0,0,FP_1*3/5,FP_1*4/5,0,OFFSET hallCardFurniture3D>
brMaterials3D AnimPack3D <0>
brMaterialPath3D db "assets\sprites\backrooms\materials.bin",0
brRec3D db 'REC',0
brTime3D db '00:00:00',0
brExitPrompt3D db 'E: EXIT',0
brHelp3D db 'V: VHS ESC: PAUSE',0
brInventory3D db 'REALITY ERROR: 90% / INFINITE',0
brToggle3D db 'R: TOGGLE REALITY ERROR',0
brEnabledText3D db 'ON',0
brDisabledText3D db 'OFF',0
brRealityGlitch3D db 'REALITY ERROR',0
brAward3D db 'REALITY ERROR / INFINITE / 90%',0
brInventoryHelp3D db 'R: REALITY ERROR / E: TOGGLE',0
brInventoryReturn3D db 'R / TAB: BACK',0
brInventoryRules1 db '90% FAVORABLE RANDOM OUTCOME',0
brInventoryRules2 db 'NO CHARGES / NO DUPLICATES',0
brInventoryRules3 db 'NORMAL CASINO ONLY',0
brInventoryRules4 db 'OFF IN TOWER / BACKROOMS',0
brInventoryRules5 db 'SKILL / CARD RULES STILL APPLY',0
brInventoryRules6 db 'BILLIARDS/BOWLING/DARTS UNCHANGED',0
brGlyph3D db 0,0
; Exact CP1251 dialogue, wrapped only at spaces by the renderer.
brText3D db 221,242,238,32,237,229,32,242,243,242,44,32,242,251,32,237,229,32
db 228,238,235,230,229,237,32,225,251,242,252,32,231,228,229,241,252,44,32
db 241,250,229,225,232,32,239,238,32,225,240,224,242,241,234,232,32,239,230,0
.code

View file

@ -97,6 +97,7 @@ bs_reload_next:
inc bs3d_blankCount inc bs3d_blankCount
dec bs3d_liveCount dec bs3d_liveCount
@@: @@:
call BackroomsBuckshotLuck3D
; The loading motion loops once per actual cartridge, then racks the pump. ; The loading motion loops once per actual cartridge, then racks the pump.
mov eax, bs3d_shellCount mov eax, bs3d_shellCount
imul eax, 330 imul eax, 330

View file

@ -5,6 +5,19 @@ cityRayY3D dd SCREEN_W dup(?)
; Exterior-only materials do not consume or overwrite the hall's texture slots. ; Exterior-only materials do not consume or overwrite the hall's texture slots.
ResolveCitySurface3D PROC material:DWORD ResolveCitySurface3D PROC material:DWORD
mov eax,material mov eax,material
cmp hallLevel3D,HALL_BACKROOMS
jne br_surface_other_world
cmp eax,BR_WALL
jb @F
cmp eax,BR_EXIT
ja @F
shl eax,16
add eax,OFFSET texturesBuffer
mov ecx,128
mov edx,128
ret
@@:
br_surface_other_world:
cmp eax,BW_MATERIAL cmp eax,BW_MATERIAL
jb @F jb @F
cmp eax,BW_LAST_MATERIAL cmp eax,BW_LAST_MATERIAL
@ -290,6 +303,27 @@ city_vertical_along:
dec edx dec edx
and eax, edx and eax, edx
@@: @@:
; Door lettering faces the viewer on either wall face. Other worlds keep UVs.
cmp hallLevel3D,HALL_BACKROOMS
jne br_door_uv_done
cmp material,BR_EXIT
jne br_door_uv_done
cmp axis,0
jne br_door_uv_y
mov edx,playerX
cmp edx,coordinate
jle br_door_uv_done
jmp br_door_uv_flip
br_door_uv_y:
mov edx,playerY
cmp edx,coordinate
jge br_door_uv_done
br_door_uv_flip:
mov edx,textureW
dec edx
sub edx,eax
mov eax,edx
br_door_uv_done:
mov sourceX, eax mov sourceX, eax
mov eax, viewEye3D mov eax, viewEye3D
sub eax, zHigh sub eax, zHigh

View file

@ -19,7 +19,13 @@ hall_plane_row:
sub ecx, eax sub ecx, eax
mov eax, viewCeiling3D mov eax, viewCeiling3D
sub eax, viewEye3D sub eax, viewEye3D
mov material, 101 mov material, 101
cmp brActive3D,0
je @F
cmp hallLevel3D,HALL_BACKROOMS
jne @F
mov material,BR_CEILING
@@:
cmp hallLevel3D,2 cmp hallLevel3D,2
jne @F jne @F
mov material,125 mov material,125
@ -130,7 +136,27 @@ hall_plane_crossroad:
jl hall_plane_sample jl hall_plane_sample
hall_plane_sidewalk: hall_plane_sidewalk:
mov ebx, 72 mov ebx, 72
hall_plane_sample: hall_plane_sample:
cmp brActive3D,0
je br_plane_sample_done
cmp hallLevel3D,HALL_BACKROOMS
jne br_plane_sample_done
mov eax,row
cmp eax,viewHorizon3D
jae br_plane_sample_done
mov ebx,BR_CEILING
mov eax,wx
shr eax,16
and eax,3
cmp eax,1
jne br_plane_sample_done
mov eax,wy
shr eax,16
and eax,3
cmp eax,1
jne br_plane_sample_done
mov ebx,BR_LAMP
br_plane_sample_done:
cmp outdoorRender3D, 0 cmp outdoorRender3D, 0
jne hall_plane_low_sample jne hall_plane_low_sample
mov eax, row mov eax, row

View file

@ -120,6 +120,7 @@ StartNativeBlackjack3D PROC
mov cbDealerCount3D, 2 mov cbDealerCount3D, 2
mov cbCursor3D, 4 mov cbCursor3D, 4
invoke ShuffleCards3D, OFFSET cbDeck3D invoke ShuffleCards3D, OFFSET cbDeck3D
call BackroomsBlackjackDeck3D
mov eax, cbDeck3D[0] mov eax, cbDeck3D[0]
mov cbPlayer3D[0], eax mov cbPlayer3D[0], eax
mov eax, cbDeck3D[4] mov eax, cbDeck3D[4]

View file

@ -104,6 +104,41 @@ clp_start_rows:
inc esi inc esi
cmp esi,5 cmp esi,5
jb clp_start_column jb clp_start_column
call BackroomsLuck3D
cmp eax,-1
je br_clover_fixed
mov ebx,eax
xor esi,esi
br_clover_row:
xor edi,edi
br_clover_column:
mov eax,esi
imul eax,2
add eax,edi
xor edx,edx
mov ecx,7
div ecx
cmp ebx,1
jne @F
mov edx,1
@@:
mov eax,esi
imul eax,5
add eax,edi
mov clpGrid3D[eax*4],edx
mov eax,clpSteps3D[edi*4]
add eax,esi
mov ecx,edi
shl ecx,8
lea ecx,clpTape3D[ecx]
mov [ecx+eax*4],edx
inc edi
cmp edi,5
jb br_clover_column
inc esi
cmp esi,3
jb br_clover_row
br_clover_fixed:
call SaveProgress3D call SaveProgress3D
invoke PlayCasinoSound3D,OFFSET soundSlotSpin,0,1 invoke PlayCasinoSound3D,OFFSET soundSlotSpin,0,1
clp_start_done: clp_start_done:

View file

@ -308,6 +308,16 @@ coin_native_start:
invoke RandomRange3D,2 invoke RandomRange3D,2
inc eax inc eax
mov coin3d_result,eax mov coin3d_result,eax
call BackroomsLuck3D
cmp eax,-1
je br_coin_fixed
xor eax,1
mov ecx,coin3d_choice
dec ecx
xor eax,ecx
inc eax
mov coin3d_result,eax
br_coin_fixed:
call SaveProgress3D call SaveProgress3D
invoke PlayCasinoSound3D,OFFSET soundCoin,0,1 invoke PlayCasinoSound3D,OFFSET soundCoin,0,1
call ClearKeys3D call ClearKeys3D

View file

@ -40,6 +40,7 @@ dk_compact:
inc esi inc esi
cmp esi, 52 cmp esi, 52
jb dk_compact jb dk_compact
call BackroomsDurakDeck3D
mov eax, dkDeck3D[35*4] mov eax, dkDeck3D[35*4]
mov durakTrumpCard, eax mov durakTrumpCard, eax
invoke DurakCardSuit, eax invoke DurakCardSuit, eax

View file

@ -75,6 +75,7 @@ StartNativePoker3D PROC uses ebx esi
mov pkRaise3D, eax mov pkRaise3D, eax
mov pkCurrent3D, eax mov pkCurrent3D, eax
invoke ShuffleCards3D, OFFSET pkDeck3D invoke ShuffleCards3D, OFFSET pkDeck3D
call BackroomsPokerDeck3D
mov esi, pkButton3D mov esi, pkButton3D
xor esi, 1 xor esi, 1
shl esi, 3 ; first card goes to the non-button player shl esi, 3 ; first card goes to the non-button player

View file

@ -69,11 +69,22 @@ StartNativeSlots3D PROC uses ebx esi
mov slotLeaveAfter3D,0 mov slotLeaveAfter3D,0
mov slotError3D,0 mov slotError3D,0
mov slotEffects3D,0 mov slotEffects3D,0
call BackroomsLuck3D
mov brSlotLuck3D,eax
xor esi,esi xor esi,esi
slot_start_reel: slot_start_reel:
mov eax,slotStops3D[esi*4] mov eax,slotStops3D[esi*4]
mov slotStartStops3D[esi*4],eax mov slotStartStops3D[esi*4],eax
invoke RandomRange3D,20 invoke RandomRange3D,20
cmp brSlotLuck3D,-1
je br_slot_target
mov eax,0
cmp brSlotLuck3D,1
je br_slot_target
; Distinct symbols, with no refund pair, for the unlucky 10%.
mov eax,esi
inc eax
br_slot_target:
mov slotTargets3D[esi*4],eax mov slotTargets3D[esi*4],eax
mov edx,slotStrip3D[eax*4] mov edx,slotStrip3D[eax*4]
mov slotReel1[esi*4],edx mov slotReel1[esi*4],edx

View file

@ -352,6 +352,7 @@ wheel_regular_spin:
mov roulState, 1 mov roulState, 1
mov wheelTime3D, 0 mov wheelTime3D, 0
invoke RandomRange3D, 37 invoke RandomRange3D, 37
invoke BackroomsWheelLuck3D,eax
mov wheelResult3D, eax mov wheelResult3D, eax
call SaveProgress3D call SaveProgress3D
invoke PlayCasinoSound3D, OFFSET soundRoulette,0,1 invoke PlayCasinoSound3D, OFFSET soundRoulette,0,1

View file

@ -137,6 +137,12 @@ event_key_up:
cmp ecx, 255 cmp ecx, 255
ja event_done ja event_done
mov keys3D[ecx], 0 mov keys3D[ecx], 0
cmp ecx,57h
je event_entry_release
cmp ecx,26h
jne event_done
event_entry_release:
mov brEntryHold3D,0
jmp event_done jmp event_done
event_fullscreen: event_fullscreen:
test lp, 40000000h test lp, 40000000h
@ -144,6 +150,7 @@ event_fullscreen:
call ToggleFullscreen3D call ToggleFullscreen3D
jmp event_done jmp event_done
event_focus_lost: event_focus_lost:
mov brEntryHold3D,0
mov dtHolding3D,0 mov dtHolding3D,0
mov dtMouseReleased3D,0 mov dtMouseReleased3D,0
call ClearKeys3D call ClearKeys3D
@ -505,9 +512,12 @@ UpdateCamera3D ENDP
; Four corners of a 0.3-tile-wide body; bounds checked before every map read. ; Four corners of a 0.3-tile-wide body; bounds checked before every map read.
CanWalk3D PROC uses ebx esi edi px:DWORD, py:DWORD CanWalk3D PROC uses ebx esi edi px:DWORD, py:DWORD
cmp brActive3D,0
jne br_generic_collision
invoke CanWalkHall3D, px, py invoke CanWalkHall3D, px, py
test eax, eax test eax, eax
jz walk_blocked jz walk_blocked
br_generic_collision:
mov esi, -9830 mov esi, -9830
walk_corner_x: walk_corner_x:
mov edi, -9830 mov edi, -9830
@ -550,6 +560,8 @@ LoadProgress3D PROC uses ebx
mov charmSkullUses, 0 mov charmSkullUses, 0
mov charmDevilUses, 0 mov charmDevilUses, 0
mov charmEnabledMask3D,7 mov charmEnabledMask3D,7
mov brOwned3D,0
mov brEnabled3D,1
invoke CreateFileA, OFFSET savePath3D, 80000000h, 1, 0, 3, 80h, 0 invoke CreateFileA, OFFSET savePath3D, 80000000h, 1, 0, 3, 80h, 0
cmp eax, -1 cmp eax, -1
je progress_loaded je progress_loaded
@ -566,7 +578,23 @@ LoadProgress3D PROC uses ebx
cmp eax,saveData3D[36] cmp eax,saveData3D[36]
jne progress_loaded jne progress_loaded
mov eax,saveData3D[32] mov eax,saveData3D[32]
mov charmEnabledMask3D,eax test eax,0FFFCFFF0h
jnz progress_loaded
test eax,20000h
jz @F
test eax,10000h
jz progress_loaded
@@:
mov ecx,eax
and ecx,15
mov charmEnabledMask3D,ecx
shr eax,16
and eax,1
mov brOwned3D,eax
mov eax,saveData3D[32]
shr eax,17
and eax,1
mov brEnabled3D,eax
jmp progress_version_ok jmp progress_version_ok
progress_legacy_version: progress_legacy_version:
cmp saveData3D, 33443343h cmp saveData3D, 33443343h
@ -653,7 +681,15 @@ SaveProgress3D PROC uses ebx
mov saveData3D[24], eax mov saveData3D[24], eax
mov eax, charmDevilUses mov eax, charmDevilUses
mov saveData3D[28], eax mov saveData3D[28], eax
mov eax,charmEnabledMask3D mov eax,brOwned3D
and eax,1
shl eax,16
mov ecx,brEnabled3D
and ecx,brOwned3D
and ecx,1
shl ecx,17
or eax,ecx
or eax,charmEnabledMask3D
mov saveData3D[32],eax mov saveData3D[32],eax
invoke PreviewArtifactSignature3D,OFFSET saveData3D invoke PreviewArtifactSignature3D,OFFSET saveData3D
mov saveData3D[36],eax mov saveData3D[36],eax

View file

@ -69,6 +69,10 @@ rr_party_cartridge:
jb rr_party_cartridge jb rr_party_cartridge
invoke RandomRange3D,6 invoke RandomRange3D,6
mov rr3d_chamber,eax mov rr3d_chamber,eax
cmp rrPartyReloads3D,0
je @F
call BackroomsRussianCylinder3D
@@:
inc rrPartyReloads3D inc rrPartyReloads3D
ret ret
RRLoadCylinder3D ENDP RRLoadCylinder3D ENDP

View file

@ -98,9 +98,11 @@ StartRussianRound3D PROC uses ebx esi
sub bankroll, eax sub bankroll, eax
mov rr3d_payout, 0 mov rr3d_payout, 0
mov rr3d_error, 0 mov rr3d_error, 0
mov brRussianLuck3D,-1
call RRLoadCylinder3D call RRLoadCylinder3D
invoke RandomRange3D, rrPartySize3D invoke RandomRange3D, rrPartySize3D
mov rr3d_turn, eax mov rr3d_turn, eax
call BackroomsRussianLuck3D
mov rr3d_state, RR_LOAD mov rr3d_state, RR_LOAD
mov rr3d_timer, 0 mov rr3d_timer, 0
invoke PlayCasinoSound3D, OFFSET rrLoadSound3D, 0, 20003h invoke PlayCasinoSound3D, OFFSET rrLoadSound3D, 0, 20003h

View file

@ -246,6 +246,16 @@ BuyScratch3D PROC uses ebx esi edi
jmp scratch_buy_save jmp scratch_buy_save
@@: @@:
invoke RandomRange3D,1000 invoke RandomRange3D,1000
push eax
call BackroomsLuck3D
pop edx
cmp eax,-1
je br_scratch_original
imul eax,650
jmp br_scratch_fixed
br_scratch_original:
mov eax,edx
br_scratch_fixed:
cmp eax,600 cmp eax,600
jb scratch_losing_bag jb scratch_losing_bag
xor esi,esi xor esi,esi

View file

@ -321,6 +321,16 @@ portrait_fill:
jb portrait_fill jb portrait_fill
mov scrPortraitOutcome3D,0 mov scrPortraitOutcome3D,0
invoke RandomRange3D,1000 invoke RandomRange3D,1000
push eax
call BackroomsLuck3D
pop edx
cmp eax,-1
je br_portrait_original
imul eax,920
jmp br_portrait_fixed
br_portrait_original:
mov eax,edx
br_portrait_fixed:
cmp eax,600 cmp eax,600
jb portrait_build_payout jb portrait_build_payout
mov ebx,eax mov ebx,eax

View file

@ -0,0 +1,32 @@
"""Offline-only texture packing; runtime generation/rendering stays MASM."""
from pathlib import Path
import struct
from PIL import Image, ImageDraw, ImageFont
ROOT = Path(__file__).resolve().parents[2]
def main():
source = ROOT / 'assets/sprites/backrooms'
frames = []
for name in ('wall', 'floor', 'ceiling', 'ceiling_lamp'):
image = Image.open(source / f'Backrooms_{name}.jpg').convert('RGBA')
image = image.resize((128, 128), Image.Resampling.LANCZOS)
frames.append(image.tobytes('raw', 'BGRA'))
# Exit is an offline-generated material, not a second game implementation.
image = Image.new('RGBA', (128, 128), (36, 60, 38, 255))
draw = ImageDraw.Draw(image)
draw.rectangle((9, 4, 119, 127), fill=(61, 84, 55), outline=(143, 160, 98), width=5)
# Eye-level sign stays visible even when standing in the exit cell.
draw.rectangle((18, 45, 110, 73), fill=(32, 170, 77))
font = ImageFont.truetype('arialbd.ttf', 23)
draw.text((64, 59), 'EXIT', fill='white', font=font, anchor='mm')
draw.rectangle((94, 76, 99, 82), fill=(240, 211, 101))
frames.append(image.tobytes('raw', 'BGRA'))
out = source / 'materials.bin'
out.write_bytes(struct.pack('<5I', 0x314D4E41, 128, 128, len(frames), len(frames)*65536) + b''.join(frames))
print(f'Packed {len(frames)} Backrooms materials: {out.relative_to(ROOT)}')
if __name__ == '__main__':
main()

View file

@ -0,0 +1,391 @@
; Additional native invariants. No player files or runtime writes.
RunBackroomsExtendedTest3D PROC uses ebx esi edi
local rounds:DWORD,wins:DWORD,kind:DWORD,party:DWORD,bullets:DWORD,steps:DWORD
mov brOwned3D,1
mov brEnabled3D,0
mov rngSeed3D,87654
call BackroomsLuck3D
cmp eax,-1
jne br_extended_fail
cmp rngSeed3D,87654
jne br_extended_fail
mov brEnabled3D,1
mov obsActive3D,1
call BackroomsLuck3D
cmp eax,-1
jne br_extended_fail
mov obsActive3D,0
mov brTestStage,111
; Actual blackjack party dealing, all guest counts.
mov party,0
br_ext_bj_party:
mov wins,0
mov rounds,1000
br_ext_bj_round:
mov bankroll,1000000
mov eax,party
mov cbBotCount3D,eax
mov cbState3D,CB_IDLE
call StartNativeBlackjack3D
cmp cbPlayerScore3D,21
jne @F
inc wins
cmp cbDealerScore3D,19
jne br_extended_fail
@@:
dec rounds
jnz br_ext_bj_round
cmp wins,860
jb br_extended_fail
cmp wins,940
ja br_extended_fail
inc party
cmp party,4
jb br_ext_bj_party
mov cbBotCount3D,0
; Every scratch design, generated by the actual purchase path.
mov scrReady3D,1
mov brTestStage,112
mov kind,0
br_ext_ticket_type:
mov eax,kind
add eax,120
mov brTestStage,eax
mov wins,0
mov rounds,1000
br_ext_ticket:
mov bankroll,1000000
mov scrState3D,0
mov scrPriceIndex3D,0
mov eax,kind
mov scrType3D,eax
call BuyScratch3D
mov eax,scrGross3D
test eax,eax
jz @F
inc wins
@@:
cmp kind,4
jb @F
call ScratchPortraitPayout3D
cmp eax,scrGross3D
jne br_extended_fail
@@:
dec rounds
jnz br_ext_ticket
cmp wins,860
jb br_extended_fail
cmp wins,940
ja br_extended_fail
inc kind
cmp kind,7
jb br_ext_ticket_type
; Roulette: a real straight-up bet, including actual multiplier matching.
lea edi,wheelBets3D
mov brTestStage,113
xor eax,eax
mov ecx,49
rep stosd
mov wheelBets3D[7*4],100
mov wheelStake3D,100
mov wins,0
mov rounds,1000
br_ext_wheel:
invoke BackroomsWheelLuck3D,0
cmp eax,36
ja br_extended_fail
invoke WheelBetMultiplier3D,7,eax
test eax,eax
jz @F
inc wins
@@:
dec rounds
jnz br_ext_wheel
cmp wins,860
jb br_extended_fail
cmp wins,940
ja br_extended_fail
; Durak: 36 distinct legal cards, correctly exposed trump on favorable deals.
mov rounds,1000
br_ext_durak:
mov brTestStage,114
mov bankroll,1000000
mov dkState3D,DK_IDLE
call StartNativeDurak3D
xor esi,esi
br_ext_durak_card:
mov eax,dkDeck3D[esi*4]
cmp eax,52
jae br_extended_fail
invoke DurakCardRank,eax
cmp eax,4
jb br_extended_fail
lea edi,[esi+1]
br_ext_durak_unique:
cmp edi,36
jae br_ext_durak_next
mov eax,dkDeck3D[edi*4]
cmp eax,dkDeck3D[esi*4]
je br_extended_fail
inc edi
jmp br_ext_durak_unique
br_ext_durak_next:
inc esi
cmp esi,36
jb br_ext_durak_card
cmp dkDeck3D,33
jne @F
cmp durakTrumpSuit,2
jne br_extended_fail
@@:
dec rounds
jnz br_ext_durak
; Russian roulette: real party elimination/reload rules, all 1..3 guests/bullets.
mov party,1
mov brTestStage,115
br_ext_rr_party:
mov bullets,1
br_ext_rr_bullets:
mov rounds,300
mov wins,0
br_ext_rr_round:
mov brTestStage,151
mov eax,party
mov rrPartyCount3D,eax
call RRPartyReset3D
mov eax,bullets
mov rr3d_bullets,eax
mov rrPartySettled3D,0
mov rrPartyStake3D,50
mov rr3d_bet,50
mov rr3d_turn,0
call BackroomsRussianLuck3D
mov rrPartyReloads3D,1
mov steps,0
br_ext_rr_shot:
mov eax,rr3d_mask
mov ecx,rr3d_chamber
bt eax,ecx
jnc br_ext_rr_advance
call RRPartyHit3D
cmp rrPartySettled3D,0
jne br_ext_rr_outcome
br_ext_rr_advance:
call RRPartyNext3D
inc steps
cmp steps,100
jae br_extended_fail
jmp br_ext_rr_shot
br_ext_rr_outcome:
mov brTestStage,152
cmp rr3d_state,RR_WIN
jne @F
inc wins
cmp brRussianLuck3D,1
jne br_extended_fail
@@:
cmp brRussianLuck3D,1
jne @F
mov eax,party
imul eax,10
add eax,bullets
add eax,200
mov brTestStage,eax
cmp rr3d_state,RR_WIN
jne br_extended_fail
@@:
dec rounds
jnz br_ext_rr_round
mov brTestStage,154
cmp wins,240
jb br_extended_fail
cmp wins,295
ja br_extended_fail
inc bullets
cmp bullets,4
jb br_ext_rr_bullets
inc party
cmp party,4
jb br_ext_rr_party
; Buckshot permutations retain cartridge counts and valid binary identities.
mov rounds,1000
br_ext_bs:
mov brTestStage,116
call BuckshotReload3D
xor edi,edi
xor esi,esi
br_ext_bs_shell:
mov eax,bs3d_shells[esi*4]
cmp eax,1
ja br_extended_fail
add edi,eax
inc esi
cmp esi,bs3d_shellCount
jb br_ext_bs_shell
cmp edi,bs3d_liveCount
jne br_extended_fail
add edi,bs3d_blankCount
cmp edi,bs3d_shellCount
jne br_extended_fail
dec rounds
jnz br_ext_bs
xor eax,eax
ret
br_extended_fail:
mov eax,brTestStage
ret
RunBackroomsExtendedTest3D ENDP
RunBackroomsSaveTest3D PROC uses ebx esi edi
; Signed but impossible enabled-without-owned flag must be rejected.
mov DWORD PTR saveBuffer[304],20002h
invoke ArtifactSaveSignature3D,OFFSET saveBuffer
mov DWORD PTR saveBuffer[308],eax
invoke ValidateArtifactSave3D,OFFSET saveBuffer
test eax,eax
jnz br_save_fail
; Native preview keeps its 40-byte, signed backward-compatible layout.
invoke CopyCString,OFFSET savePath3D,OFFSET brTestPreviewPath
mov nativeProfileActive3D,0
mov arrivalConfirmed3D,1
mov brOwned3D,1
mov brEnabled3D,1
call SaveProgress3D
mov brOwned3D,0
mov brEnabled3D,0
call LoadProgress3D
cmp brOwned3D,1
jne br_save_fail
cmp brEnabled3D,1
jne br_save_fail
mov brActive3D,1
mov brEnabled3D,0
invoke LeaveBackrooms3D,1
cmp brOwned3D,1
jne br_save_fail
cmp brEnabled3D,0
jne br_save_fail
call LoadProgress3D
cmp brEnabled3D,0
jne br_save_fail
xor eax,eax
ret
br_save_fail:
mov eax,102
ret
RunBackroomsSaveTest3D ENDP
.data
brContextModes dd MODE_COIN,MODE_DICE,MODE_SLOTS,MODE_BLACKJACK,MODE_ROULETTE,MODE_CLOVER
brContextMap db 1024 dup(0)
.code
RunBackroomsContextTest3D PROC uses ebx esi edi
local index:DWORD,x:DWORD,y:DWORD
call GenerateBackrooms3D
mov brActive3D,1
mov brCutscene3D,0
mov hallLevel3D,HALL_BACKROOMS
mov worldMap3D,OFFSET brMap3D
lea esi,brMap3D
lea edi,brContextMap
mov ecx,256
rep movsd
mov index,0
br_context_game:
mov esi,index
mov ebx,brGameCells3D[esi*4]
mov eax,ebx
and eax,31
shl eax,16
add eax,FP_HALF
mov x,eax
mov playerX,eax
mov eax,ebx
shr eax,5
shl eax,16
add eax,FP_HALF
mov y,eax
mov playerY,eax
mov playerAngle,77
mov engineMode,MODE_WALK
mov mouseLocked3D,0
mov deltaTime3D,16
call ClearKeys3D
mov pressedKeys3D[45h],1
call HandleBackrooms3D
mov esi,index
mov eax,brContextModes[esi*4]
cmp eax,engineMode
jne br_context_fail
cmp eax,MODE_DICE
jne @F
call DiceEscape3D
jmp br_context_returned
@@:
call CardTableEscape3D
br_context_returned:
cmp engineMode,MODE_WALK
jne br_context_fail
mov eax,x
cmp playerX,eax
jne br_context_fail
mov eax,y
cmp playerY,eax
jne br_context_fail
cmp playerAngle,77
jne br_context_fail
cmp worldMap3D,OFFSET brMap3D
jne br_context_fail
cmp hallLevel3D,HALL_BACKROOMS
jne br_context_fail
lea esi,brMap3D
lea edi,brContextMap
mov ecx,1024
repe cmpsb
jne br_context_fail
inc index
cmp index,6
jb br_context_game
mov brActive3D,0
mov hallLevel3D,0
mov worldMap3D,OFFSET casinoMap
xor eax,eax
ret
br_context_fail:
mov eax,103
ret
RunBackroomsContextTest3D ENDP
RunBackroomsWallTest3D PROC uses esi
mov brActive3D,0
mov engineMode,MODE_WALK
mov mouseLocked3D,0
mov playerX,16*FP_1+FP_HALF
mov playerY,27*FP_1+FP_HALF
mov playerAngle,128
mov deltaTime3D,16
mov brEntryHold3D,0
call ClearKeys3D
mov keys3D[57h],1
mov esi,500
br_wall_walk:
call HandlePlayerInput
call BackroomsEntry3D
cmp brActive3D,1
je br_wall_entered
dec esi
jnz br_wall_walk
mov eax,104
ret
br_wall_entered:
cmp playerY,30*FP_1
jae br_wall_fail
cmp playerY,30*FP_1-15000
jb br_wall_fail
call ClearKeys3D
xor eax,eax
ret
br_wall_fail:
mov eax,105
ret
RunBackroomsWallTest3D ENDP

View file

@ -0,0 +1,288 @@
.data
brTestPath db 'build\scratch\backrooms-native.bin',0
brTestFrame db 'build\scratch\backrooms-frame.bin',0
brTestExitFrame db 'build\scratch\backrooms-exit.bin',0
brTestIo dd 0
brTestSeeds dd 256
brTestStage dd 91
brTestRows dd 0
brTestLuckPath db 'build\scratch\backrooms-luck.bin',0
brTestProfileDir db 'build\scratch',0
brTestProfileName db 'backrooms-profile.dat',0
brTestProfilePath db 'build\scratch\backrooms-profile.dat',0
brTestPreviewPath db 'build\scratch\br-preview.dat',0
brTestFacesPath db 'build\scratch\backrooms-exit-faces.bin',0
brTestFaceNeighbors dd -32,32,-1,1
brTestFaceAngles dd 384,128,256,0
.code
include tools/tests/backrooms_extended_test.inc
RunBackroomsTest3D PROC uses ebx esi edi
invoke CreateFileA,OFFSET brTestPath,40000000h,0,0,2,80h,0
mov ebx,eax
cmp eax,-1
je br_test_fail
mov rngSeed3D,12345
br_test_seed:
call GenerateBackrooms3D
invoke WriteFile,ebx,OFFSET brMap3D,1024,OFFSET brTestIo,0
invoke WriteFile,ebx,OFFSET brExitCell3D,4,OFFSET brTestIo,0
invoke WriteFile,ebx,OFFSET brGameCells3D,24,OFFSET brTestIo,0
dec brTestSeeds
jnz br_test_seed
invoke CloseHandle,ebx
mov brTestStage,92
mov obsActive3D,0
mov tournamentMode,0
mov brActive3D,0
mov engineMode,MODE_WALK
mov hallLevel3D,0
mov playerX,16*FP_1+FP_HALF
mov playerY,30*FP_1-11000
mov playerAngle,128
mov keys3D[57h],1
mov deltaTime3D,50
mov esi,39
br_test_entry:
call BackroomsEntry3D
cmp brActive3D,0
jne br_test_fail
dec esi
jnz br_test_entry
cmp brEntryHold3D,1950
jne br_test_fail
mov brTestStage,93
mov playerAngle,129
call BackroomsEntry3D
cmp brEntryHold3D,0
jne br_test_fail
mov playerAngle,128
call BackroomsEntry3D
cmp brEntryHold3D,50
jne br_test_fail
mov keys3D[57h],0
call BackroomsEntry3D
cmp brEntryHold3D,0
jne br_test_fail
mov brTestStage,94
mov keys3D[57h],1
mov brEntryHold3D,1950
mov brEntryAngle3D,128
call BackroomsEntry3D
cmp brActive3D,1
jne br_test_fail
cmp brCutscene3D,1
jne br_test_fail
mov brClock3D,900
call HandleBackrooms3D
mov brTestStage,95
invoke CanWalk3D,FP_1+FP_HALF,FP_1+FP_HALF
cmp eax,1
jne br_test_fail
invoke CanWalk3D,FP_HALF,FP_HALF
test eax,eax
jnz br_test_fail
mov brTestStage,96
; Set up standard projection LUT through the real hall initializer first.
call InitHall3D
call RunBackroomsWallTest3D
test eax,eax
jnz br_test_return
mov brActive3D,1
mov hallLevel3D,HALL_BACKROOMS
mov worldMap3D,OFFSET brMap3D
mov floorTexture3D,BR_FLOOR
mov viewHorizon3D,HALF_H
mov brClock3D,8100
mov brDialogue3D,2500
mov brCutscene3D,0
mov playerX,FP_1+FP_HALF
mov playerY,FP_1+FP_HALF
mov playerAngle,64
call RenderBackrooms3D
invoke CreateFileA,OFFSET brTestFrame,40000000h,0,0,2,80h,0
mov ebx,eax
invoke WriteFile,ebx,OFFSET screenBuffer,SCREEN_SIZE*4,OFFSET brTestIo,0
invoke CloseHandle,ebx
; Door capture from its reachable exit cell, facing a neighboring wall.
mov ebx,brExitCell3D
mov eax,ebx
and eax,31
shl eax,16
add eax,FP_HALF
mov playerX,eax
mov eax,ebx
shr eax,5
shl eax,16
add eax,FP_HALF
mov playerY,eax
mov playerAngle,384
cmp brMap3D[ebx-32],0
jne br_test_exit_camera
mov playerAngle,128
cmp brMap3D[ebx+32],0
jne br_test_exit_camera
mov playerAngle,256
cmp brMap3D[ebx-1],0
jne br_test_exit_camera
mov playerAngle,0
br_test_exit_camera:
mov brDialogue3D,10000
call RenderBackrooms3D
invoke CreateFileA,OFFSET brTestExitFrame,40000000h,0,0,2,80h,0
mov ebx,eax
invoke WriteFile,ebx,OFFSET screenBuffer,SCREEN_SIZE*4,OFFSET brTestIo,0
invoke CloseHandle,ebx
mov arrivalConfirmed3D,0
call RunBackroomsDoorFrames3D
invoke LeaveBackrooms3D,1
cmp brOwned3D,1
jne br_test_fail
cmp brActive3D,0
jne br_test_fail
cmp hallLevel3D,0
jne br_test_fail
mov brTestStage,97
call RunBackroomsContextTest3D
test eax,eax
jnz br_test_return
mov brActive3D,1
call BackroomsLuck3D
cmp eax,-1
jne br_test_fail
mov brActive3D,0
mov tournamentMode,1
call BackroomsLuck3D
cmp eax,-1
jne br_test_fail
mov tournamentMode,0
xor edi,edi
mov esi,100000
br_test_luck:
call BackroomsLuck3D
add edi,eax
dec esi
jnz br_test_luck
cmp edi,89000
jb br_test_fail
cmp edi,91000
ja br_test_fail
call RunBackroomsExtendedTest3D
test eax,eax
jnz br_test_return
mov brTestStage,98
call LoadNativeCoinArt3D
call InitNativeSlots3D
call InitNativeClover3D
call InitNativeBlackjack3D
call InitNativePoker3D
invoke CreateFileA,OFFSET brTestLuckPath,40000000h,0,0,2,80h,0
mov ebx,eax
mov brTestRows,1000
br_test_rounds:
mov bankroll,1000000
mov coin3d_choice,1
mov coin3d_state,COIN_IDLE
mov pressedKeys3D[20h],1
call HandleNativeCoin3D
invoke WriteFile,ebx,OFFSET coin3d_result,4,OFFSET brTestIo,0
mov dice3d_state,0
mov dice3d_choice,1
mov pressedKeys3D[20h],1
call HandleDiceRules3D
invoke WriteFile,ebx,OFFSET dice3d_val1,8,OFFSET brTestIo,0
mov slotSpinState,SLOT_READY
call StartNativeSlots3D
invoke WriteFile,ebx,OFFSET slotReel1,12,OFFSET brTestIo,0
mov clpState3D,SLOT_READY
call StartNativeClover3D
call CloverEvaluate3D
invoke WriteFile,ebx,OFFSET clpTotal3D,4,OFFSET brTestIo,0
mov cbBotCount3D,0
mov cbState3D,CB_IDLE
call StartNativeBlackjack3D
invoke WriteFile,ebx,OFFSET cbPlayerScore3D,4,OFFSET brTestIo,0
invoke WriteFile,ebx,OFFSET cbDeck3D,208,OFFSET brTestIo,0
mov pkState3D,PK_IDLE
call StartNativePoker3D
invoke WriteFile,ebx,OFFSET pkDeck3D,208,OFFSET brTestIo,0
invoke WriteFile,ebx,OFFSET pkButton3D,4,OFFSET brTestIo,0
dec brTestRows
jnz br_test_rounds
invoke CloseHandle,ebx
mov brTestStage,99
mov nativeProfilesDirPtr3D,OFFSET brTestProfileDir
invoke CopyCString,OFFSET saveFileName,OFFSET brTestProfileName
mov nativeProfileActive3D,1
mov brOwned3D,1
mov brEnabled3D,0
call SaveGame
mov brOwned3D,0
mov brEnabled3D,1
call LoadSave
cmp brOwned3D,1
jne br_test_fail
cmp brEnabled3D,0
jne br_test_fail
invoke ValidateGraphicProfile3D,OFFSET brTestProfilePath
cmp eax,1
jne br_test_fail
call RunBackroomsSaveTest3D
test eax,eax
jnz br_test_return
xor eax,eax
ret
br_test_fail:
mov eax,brTestStage
br_test_return:
ret
RunBackroomsTest3D ENDP
; Identical door/camera distance on every face, VHS off/on pairs.
RunBackroomsDoorFrames3D PROC uses ebx esi edi
local face:DWORD,effect:DWORD
invoke CreateFileA,OFFSET brTestFacesPath,40000000h,0,0,2,80h,0
mov ebx,eax
mov face,0
br_test_door_face:
lea edi,brMap3D
xor eax,eax
mov ecx,256
rep stosd
; Keep all rays inside the fixture map, including peripheral FOV rays.
xor esi,esi
br_test_door_border:
mov brMap3D[esi],BR_WALL
mov brMap3D[esi+31*32],BR_WALL
mov eax,esi
shl eax,5
mov brMap3D[eax],BR_WALL
mov brMap3D[eax+31],BR_WALL
inc esi
cmp esi,32
jb br_test_door_border
mov brExitCell3D,16*32+16
mov esi,face
mov eax,brTestFaceNeighbors[esi*4]
mov brMap3D[eax+16*32+16],BR_WALL
mov playerX,16*FP_1+FP_HALF
mov playerY,16*FP_1+FP_HALF
mov eax,brTestFaceAngles[esi*4]
mov playerAngle,eax
mov viewEye3D,HALL_STANDING_EYE
mov viewHorizon3D,HALF_H
mov brDialogue3D,10000
mov effect,0
br_test_door_effect:
mov eax,effect
mov brVhs3D,eax
call RenderBackrooms3D
invoke WriteFile,ebx,OFFSET screenBuffer,SCREEN_SIZE*4,OFFSET brTestIo,0
inc effect
cmp effect,2
jb br_test_door_effect
inc face
cmp face,4
jb br_test_door_face
invoke CloseHandle,ebx
ret
RunBackroomsDoorFrames3D ENDP

View file

@ -0,0 +1,128 @@
"""Execute shipping MASM maze/entry/collision/render, inspect native output."""
from collections import deque
from pathlib import Path
import struct
import subprocess
from PIL import Image
from check_artifacts import toolchain
ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / 'build/scratch'
def check_exit_faces():
raw = (OUT/'backrooms-exit-faces.bin').read_bytes()
assert len(raw) == 8*640*480*4
frames = [Image.frombytes('RGBA', (640, 480), raw[i:i+640*480*4],
'raw', 'BGRA').convert('RGB')
for i in range(0, len(raw), 640*480*4)]
# Source glyph at runtime's exact nearest-neighbor UV scale. Compare both
# orientations rather than merely counting green pixels (mirrors pass that).
pack = (ROOT/'assets/sprites/backrooms/materials.bin').read_bytes()
art = Image.frombytes('RGBA', (128, 128), pack[20+4*65536:], 'raw', 'BGRA')
glyph = [(x, y) for y in range(48, 70) for x in range(27, 101)
if min(art.getpixel((x, y))[:3]) > 240]
for face, name in enumerate(('north', 'south', 'west', 'east')):
clean, vhs = frames[2*face:2*face+2]
clean.save(OUT/f'backrooms-exit-{name}.png')
vhs.save(OUT/f'backrooms-exit-{name}-vhs.png')
white = [(x, y) for y in range(0, 300) for x in range(60, 580)
if min(clean.getpixel((x, y))) > 240]
assert white, f'{name}: EXIT missing'
x0, x1 = min(x for x, y in white), max(x for x, y in white)
y0, y1 = min(y for x, y in white), max(y for x, y in white)
gx0, gx1 = min(x for x, y in glyph), max(x for x, y in glyph)
gy0, gy1 = min(y for x, y in glyph), max(y for x, y in glyph)
def score(mirror):
hits = 0
for x, y in glyph:
u = (x-gx0)/(gx1-gx0)
if mirror:
u = 1-u
px = round(x0+u*(x1-x0))
py = round(y0+(y-gy0)/(gy1-gy0)*(y1-y0))
hits += min(clean.getpixel((px, py))) > 240
return hits/len(glyph)
normal, mirrored = score(False), score(True)
assert normal > .85 and normal > mirrored+.12, (name, normal, mirrored)
# White glyphs may darken on scanlines, never wrap into black/color noise.
corrupt = sum(min(vhs.getpixel(p)) < 180 or
max(vhs.getpixel(p))-min(vhs.getpixel(p)) > 12 for p in white)
assert corrupt == 0, (name, 'VHS corrupt white pixels', corrupt, len(white))
def main():
OUT.mkdir(parents=True, exist_ok=True)
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())
for args in ([ml, '/c', '/coff', '/Cp', '/nologo', '/I.', '/DBACKROOMS_TEST=1',
'/Fobuild/scratch/backrooms.obj', 'poker.asm'],
[link, '/nologo', '/SUBSYSTEM:CONSOLE', '/LIBPATH:'+str(lib),
'/OUT:build/scratch/backrooms-test.exe', 'build/scratch/backrooms.obj',
'kernel32.lib', 'winmm.lib', 'user32.lib', 'ws2_32.lib', 'gdi32.lib']):
subprocess.run(list(map(str, args)), cwd=ROOT, check=True, timeout=120)
subprocess.run([str(OUT/'backrooms-test.exe')], cwd=ROOT, check=True, timeout=90)
raw = (OUT/'backrooms-native.bin').read_bytes()
assert len(raw) == 256 * 1052
fingerprints = set()
minimum = 10000
for offset in range(0, len(raw), 1052):
grid = raw[offset:offset+1024]
exit_cell, *games = struct.unpack_from('<7I', raw, offset+1024)
assert all(grid[x] for x in range(32))
assert all(grid[31*32+x] for x in range(32))
assert all(grid[y*32] and grid[y*32+31] for y in range(32))
distance, queue = {33: 0}, deque([33])
while queue:
cell = queue.popleft()
for neighbor in (cell-32, cell+1, cell+32, cell-1):
if grid[neighbor] == 0 and neighbor not in distance:
distance[neighbor] = distance[cell]+1
queue.append(neighbor)
assert len(distance) == grid.count(0), 'Disconnected open component'
assert distance[exit_cell] == max(distance.values())
assert len(set(games)) == 6 and exit_cell not in games and 33 not in games
assert all(cell in distance for cell in games)
minimum = min(minimum, distance[exit_cell])
fingerprints.add(grid)
assert len(fingerprints) == 256
assert minimum >= 40, minimum
luck = (OUT/'backrooms-luck.bin').read_bytes()
assert len(luck) == 1000*452
wins = dict.fromkeys(('coin', 'dice', '777', 'clover', 'blackjack', 'poker'), 0)
for row in struct.iter_unpack('<113I', luck):
coin, d1, d2, s1, s2, s3, clover, bj = row[:8]
assert coin in (1, 2) and 1 <= d1 <= 6 and 1 <= d2 <= 6
wins['coin'] += coin == 1
wins['dice'] += (d1+d2) % 2 == 0
assert (s1, s2, s3) in ((1, 1, 1), (2, 5, 3))
wins['777'] += s1 == s2 == s3
assert clover in (0, 78)
wins['clover'] += clover > 0
deck, poker, button = row[8:60], row[60:112], row[112]
assert sorted(deck) == sorted(poker) == list(range(52)), 'Duplicate/missing card'
assert bj in (19, 21)
assert bj == (21 if deck[0] == 12 else 19)
wins['blackjack'] += bj == 21
# Actual non-button-first deal order, plus actual burn/flop/turn/river offsets.
seat = button ^ 1
hand = [poker[seat], poker[seat+2], *(poker[i] for i in (5, 6, 7, 9, 11))]
wins['poker'] += set(range(8, 13)).issubset(hand)
assert all(860 <= n <= 940 for n in wins.values()), wins
image = Image.frombytes('RGBA', (640, 480), (OUT/'backrooms-frame.bin').read_bytes(), 'raw', 'BGRA').convert('RGB')
image.save(OUT/'backrooms-frame.png')
assert len(image.getcolors(640*480)) > 100
image = Image.frombytes('RGBA', (640, 480), (OUT/'backrooms-exit.bin').read_bytes(), 'raw', 'BGRA').convert('RGB')
image.save(OUT/'backrooms-exit.png')
assert sum(g > 1.4*r and g > 1.2*b for r, g, b in image.get_flattened_data()) > 100, 'Exit not visibly green'
check_exit_faces()
print('PASS: readable non-mirrored EXIT on all four wall faces; VHS preserves white glyphs')
print(f'PASS: 256 native seeds; sealed connected mazes; farthest exit >= {minimum} steps; unique reachable games; entry hold/reset; collision; isolation; profile roundtrip; render')
print(f'PASS: 1000 production rounds per game, valid unique decks, wins: {wins}')
print('PASS: all blackjack party sizes; 7 scratch designs; roulette; Durak conservation/trump; 9 Russian party/bullet setups; Buckshot counts; preview saves; repeat reward')
if __name__ == '__main__':
main()