feat: add playable bowling match mode and calibrate rooftop character scaling

- Add playable ten-frame bowling against bot with two timing gates (direction/power) and fixed-step physics

- Add lane match interaction, shaded ball surface, pin collisions, bonus balls, and live score sheet

- Scale rooftop terrace visitor sprites to eye level according to the taller rooftop standing eye height

- Resolve mode and interaction ID conflicts with Hell FPS (MODE_BOWLING=30, INTERACT_BOWL_PLAY=56)

- Update launcher hash, installer package, and expansion regression test suites
This commit is contained in:
Artvasil09 2026-11-01 22:50:33 +03:00
parent ef5ebff353
commit 3150d217b5
28 changed files with 1700 additions and 12 deletions

Binary file not shown.

View file

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

View file

@ -27,7 +27,7 @@ namespace PokerGameSetup
sealed class InstallerForm : Form
{
const string Marker = "<<POKER_GAME_GUI_PAYLOAD_ZIP>>";
const string ExpectedPayloadSha256 = "1A2AC9A543AAF354EAD5BFA4716A8677143E27D994D55120AAF4ACF8296AAA6B";
const string ExpectedPayloadSha256 = "D44B44805C80AA6107F2F0226D0B98F0C7B085031911CB97FB89729F9B2510E6";
readonly Color bg = Color.FromArgb(15, 12, 10);
readonly Color panel = Color.FromArgb(28, 22, 18);
readonly Color gold = Color.FromArgb(236, 186, 75);

Binary file not shown.

View file

@ -5367,6 +5367,11 @@ pause_interior_walk:
xor paused3D, 1
jmp engine_tick
leave_table:
cmp engineMode,MODE_BOWLING
jne @F
call LeaveBowlingPlay3D
jmp engine_tick
@@:
cmp engineMode,MODE_FORTUNE
jne @F
call StopFortuneWorker3D
@ -5474,6 +5479,14 @@ engine_backrooms:
call RenderBackrooms3D
jmp present_frame
check_slots_mode:
cmp engineMode,MODE_BOWLING
jne @F
call HandleBowlingPlay3D
cmp engineMode,MODE_WALK
je check_walk_mode
call RenderBowlingPlay3D
jmp present_frame
@@:
cmp engineMode,MODE_JACKBOX
jne check_jackbox_done
call HandleJackbox3D

Binary file not shown.

View file

@ -1,6 +1,6 @@
# Bowling Lounge
The bowling lounge is a walkable room, not a player-controlled bowling minigame.
The bowling lounge is a walkable room with playable ten-frame bowling against a bot.
Enter through the wide opening in the north wall of the main casino, near the
slot-machine side of the hall. Walk through to enter or leave; no interaction
button or timed stair/elevator animation is used.
@ -19,7 +19,36 @@ All room geometry, rendering, input, scoring and visitor simulation run in MASM.
Furniture is built from fixed world-space surfaces, not rotating room billboards.
Human sprites retain the game's existing 2.5D presentation.
## Simulation
## Player Game
Approach a free lane's foul line and press E on the play prompt. Occupied visitor
lanes stay unavailable. There is no entry fee or bankroll change. The camera and
room position are restored on exit; leaving abandons the current lane match.
The throw has two separate timing gates, both accepting either left mouse click
or a fresh Space press. First stop the oscillating direction arrow on its curved
horizontal scale. Then stop the vertical power indicator. The second press
commits the launch. Holding Space cannot accidentally consume both stages.
The arrow and meter update from elapsed time, not frame count. The ball follows
the committed parameters without additional random error.
Player and bot alternate complete frames. A second ball uses the remaining rack;
strikes, spares and tenth-frame bonuses use the shared native bowling scorer.
The lane monitor shows the two scores and current phase. Tab toggles a cumulative
ten-frame score sheet; unresolved bonuses stay blank rather than inventing a total.
The sheet pauses the current throw. Escape or the Exit command returns to the room.
After a completed match, click or Space starts a fresh match.
The player game uses an arcade, not regulation-grade, collision approximation:
16-ms fixed steps, a rolling ball disc, ten pin discs, transferred pin impulses
and pin friction. Gutters latch once entered. Pins knocked by the ball can knock
other pins; removed pins cannot be scored again. The opponent uses the identical
solver, with private lane RNG choosing only its launch aim and power. The camera
follows the rolling ball; the bot uses a full-body shared-cast throwing animation.
The blue ball is a native shaded sphere with a procedural marble surface and
rotating finger-hole details, not a new neural or image runtime.
## Visitor Simulation
Each occupied lane owns a timer, rack mask, two players, frame counters and roll
history. The sequence is pick-up, approach/release, ball travel, pin impact,
@ -55,6 +84,10 @@ Run the offline packer with Python, then build the MASM executable normally.
checks entrance visibility, walkable access, both room transitions, lane/furniture
collision, sitting/restoration, 600 simulated seconds of lane lifecycles, perfect
game/all-spare/open-frame score vectors, and captures room/throw screenshots.
The additional `bowling_play_test.inc` covers LMB/Space input through native window
events, stage separation, deterministic impacts at two frame deltas, gutters,
empty/remaining racks, alternating turns, spare feedback, perfect games and the
tenth-frame bonuses, unresolved score cells, scene captures and exit restoration.
The complete hall suite includes the same bowling checks alongside existing
stairs, doors, rooftop, fortune-room, bar and cabaret regressions. `--parity`
compares the tested procedures with the shipping assembly object.

View file

@ -24,6 +24,9 @@ casino visitor pauses movement while standing up from a chair.
The canvas height is 1.3 world units (192:224 aspect), calibrated against the
hall's 1.05-unit standing eye height. The former 1.95-unit canvas made visitors
oversized compared with the camera, staff and furniture.
The rooftop uses a 1.65-unit standing eye height. Its visitor canvas, width and
foot anchor are scaled by the rooftop/hall standing-eye ratio (about 1.57),
independently of sitting or leaning. Indoor visitor sizes remain unchanged.
Walking uses replacement `person-*-walk-source-v2.png` sheets with contact and
passing poses in front, rear and right profile. The left profile is mirrored.
@ -72,6 +75,8 @@ wide contact and narrow passing foot spans. Distinct pixel hashes alone did not
catch the old sheets' repeated leading-leg poses.
The full hall regression includes room navigation, roof, bars, doors and stairs.
Shipping/tested assembly parity is checked separately.
`roof_character_scale_test.inc` checks all eight rooftop visitors in standing and
walking states against that ratio and captures their native grounded silhouettes.
The older guest and bowling source packs are retained as historical assets;
the visitor renderers no longer load or use them.

BIN
poker.exe

Binary file not shown.

BIN
poker.obj

Binary file not shown.

View file

@ -1,10 +1,22 @@
; Native room, automatic lane simulation and full-body visitors; no AI service.
InitBowling3D PROC uses esi edi
mov bpActive3D,0
call InitBowlingBallSurface3D
mov bwClock3D,-1
mov bwMonitorClock3D,-1
mov bwSeat3D,0
xor esi,esi
bw_init_lane:
mov bwBusy3D[esi*4],0
cmp esi,0
je bw_init_busy
cmp esi,3
je bw_init_busy
cmp esi,6
jne bw_init_state
bw_init_busy:
mov bwBusy3D[esi*4],1
bw_init_state:
mov bwState3D[esi*4],0
imul eax,esi,349
mov bwTime3D[esi*4],eax
@ -50,6 +62,13 @@ FreeBowling3D ENDP
ResolveBowlingSurface3D PROC material:DWORD
mov eax,material
cmp eax,253
jne @F
mov eax,OFFSET bpBallSurface3D
mov ecx,64
mov edx,64
ret
@@:
cmp eax,252
jne @F
mov eax,OFFSET bwSignPixels3D
@ -185,6 +204,22 @@ CheckBowlingInteractions3D PROC uses esi edi
mov interactPromptPtr,OFFSET bwStandPrompt3D
ret
bw_check_seats:
xor edi,edi
bw_check_play:
cmp bwBusy3D[edi*4],0
jne bw_check_play_next
imul eax,edi,BW_LANE_STEP
add eax,3*FP_1
mov interactTargetId,0
invoke HallTarget3D,eax,FP_1*237/10,INTERACT_BOWL_PLAY,OFFSET bpPlayPrompt
cmp interactTargetId,INTERACT_BOWL_PLAY
jne bw_check_play_next
mov bpTarget3D,edi
ret
bw_check_play_next:
inc edi
cmp edi,BW_LANES
jb bw_check_play
lea esi,bwSeats3D
xor edi,edi
bw_check_seat:
@ -212,6 +247,11 @@ CheckBowlingInteractions3D ENDP
TryBowlingInteraction3D PROC uses esi
cmp hallLevel3D,HALL_BOWLING
jne bw_try_none
cmp interactTargetId,INTERACT_BOWL_PLAY
jne @F
invoke BeginBowlingPlay3D,bpTarget3D
ret
@@:
cmp interactTargetId,INTERACT_BOWL_SEAT
jne bw_try_none
cmp bwSeat3D,0
@ -252,3 +292,4 @@ TryBowlingInteraction3D ENDP
include src_graphics\bowling_simulation.inc
include src_graphics\bowling_render.inc
include src_graphics\bowling_play.inc

View file

@ -2,7 +2,7 @@ HALL_BOWLING equ 5
INTERACT_BOWL_SEAT equ 53
INTERACT_BOWL_WATCH equ 54
BW_MATERIAL equ 230
BW_LAST_MATERIAL equ 252
BW_LAST_MATERIAL equ 253
BW_LANES equ 8
BW_LANE_STEP equ FP_1*19/10
InitBowling3D PROTO
@ -96,3 +96,4 @@ bwMonitors3D dd BW_LANES*128*64 dup(?)
bwSignPixels3D dd 128*64 dup(?)
.code
include src_graphics\bowling_layout.inc
include src_graphics\bowling_play_data.inc

View file

@ -0,0 +1,507 @@
; Two timing gates commit one immutable launch; the same solver serves both players.
BeginBowlingPlay3D PROC uses esi lane:DWORD
cmp bpActive3D,0
jne bp_begin_no
mov esi,lane
cmp esi,BW_LANES
jae bp_begin_no
cmp bwBusy3D[esi*4],0
jne bp_begin_no
mov eax,playerX
mov bpHomeX3D,eax
mov eax,playerY
mov bpHomeY3D,eax
mov eax,playerAngle
mov bpHomeAngle3D,eax
mov bpLane3D,esi
imul eax,esi,BW_LANE_STEP
add eax,3*FP_1
mov bpCentre3D,eax
mov bpActive3D,1
mov bwBusy3D[esi*4],2
mov engineMode,MODE_BOWLING
mov paused3D,0
mov bwSeat3D,0
call ResetBowlingPlay3D
mov eax,1
ret
bp_begin_no:
xor eax,eax
ret
BeginBowlingPlay3D ENDP
ResetBowlingPlay3D PROC uses ebx esi edi
mov esi,bpLane3D
lea ebx,[esi*8]
mov bwFrames3D[ebx],0
mov bwFrames3D[ebx+4],0
mov bwRollCount3D[ebx],0
mov bwRollCount3D[ebx+4],0
mov bwScores3D[ebx],0
mov bwScores3D[ebx+4],0
imul eax,esi,192
lea edi,bwRolls3D[eax]
xor eax,eax
mov ecx,48
rep stosd
mov bwTurn3D[esi*4],0
mov bwBall3D[esi*4],0
mov bwRack3D[esi*4],1023
mov bwState3D[esi*4],0
mov bwHit3D[esi*4],0
mov bwTime3D[esi*4],0
mov bpState3D,BP_DIRECTION
mov bpTime3D,0
call BowlingPlayStartAim3D
mov bpScoreOpen3D,0
mov bpLast3D,0
mov bpLastKind3D,0
call BowlingPlayRack3D
call ClearKeys3D
mov mouseClick3D,0
ret
ResetBowlingPlay3D ENDP
; Randomize only the visible starting phase, never the committed trajectory.
BowlingPlayStartAim3D PROC
mov eax,bwRng3D
imul eax,1664525
add eax,1013904223
mov bwRng3D,eax
shr eax,16
and eax,2047
mov bpClock3D,eax
ret
BowlingPlayStartAim3D ENDP
LeaveBowlingPlay3D PROC uses esi
cmp bpActive3D,0
je bp_leave_done
mov esi,bpLane3D
mov bwBusy3D[esi*4],0
mov bwRack3D[esi*4],1023
mov bwHit3D[esi*4],0
mov bwState3D[esi*4],0
mov bpActive3D,0
mov bpScoreOpen3D,0
mov eax,bpHomeX3D
mov playerX,eax
mov eax,bpHomeY3D
mov playerY,eax
mov eax,bpHomeAngle3D
mov playerAngle,eax
mov engineMode,MODE_WALK
call BowlingEye3D
call UpdateCamera3D
call ClearKeys3D
mov mouseClick3D,0
mov bwMonitorClock3D,-1
bp_leave_done:
ret
LeaveBowlingPlay3D ENDP
BowlingPlayRack3D PROC uses esi
xor esi,esi
bp_rack_pin:
mov eax,bwPinX3D[esi*4]
mov bpPinX3D[esi*4],eax
mov eax,bwPinY3D[esi*4]
mov bpPinY3D[esi*4],eax
mov bpPinVX3D[esi*4],0
mov bpPinVY3D[esi*4],0
inc esi
cmp esi,10
jb bp_rack_pin
mov bpHitMask3D,0
mov bpGutter3D,0
mov bpAccumulator3D,0
mov bpBallX3D,0
mov bpBallY3D,FP_1*225/10
ret
BowlingPlayRack3D ENDP
BowlingPlayLaunch3D PROC
mov bpState3D,BP_RELEASE
mov bpTime3D,0
mov bpAccumulator3D,0
mov eax,bpLockedPower3D
imul eax,6*FP_1
cdq
mov ecx,100
idiv ecx
add eax,3*FP_1
imul eax,16
cdq
mov ecx,1000
idiv ecx
mov ecx,eax
neg eax
mov bpBallVY3D,eax
mov eax,bpLockedAim3D
imul ecx
mov ecx,18*FP_1
idiv ecx
mov bpBallVX3D,eax
ret
BowlingPlayLaunch3D ENDP
BowlingPlayHit3D PROC uses esi pin:DWORD,vx:DWORD,vy:DWORD
mov esi,pin
mov ecx,esi
mov eax,1
shl eax,cl
test bpHitMask3D,eax
jnz bp_hit_done
or bpHitMask3D,eax
mov eax,vx
mov bpPinVX3D[esi*4],eax
mov eax,vy
mov bpPinVY3D[esi*4],eax
bp_hit_done:
ret
BowlingPlayHit3D ENDP
; 16-ms fixed steps, lane-relative 16.16 discs. No random hit selection.
BowlingPlayStep3D PROC uses ebx esi edi
local deltaX:DWORD,deltaY:DWORD,impulseX:DWORD,rackMask:DWORD
mov esi,bpLane3D
mov eax,bwRack3D[esi*4]
mov rackMask,eax
mov eax,bpBallVX3D
add bpBallX3D,eax
mov eax,bpBallVY3D
add bpBallY3D,eax
mov eax,bpBallX3D
cdq
xor eax,edx
sub eax,edx
cmp eax,FP_1*74/100
jbe bp_step_pins
mov bpGutter3D,1
mov eax,FP_1*87/100
cmp bpBallX3D,0
jge @F
neg eax
@@:
mov bpBallX3D,eax
mov bpBallVX3D,0
bp_step_pins:
xor esi,esi
bp_step_pin:
mov ecx,esi
mov ebx,1
shl ebx,cl
test rackMask,ebx
jz bp_step_next_pin
test bpHitMask3D,ebx
jnz bp_step_move_pin
cmp bpGutter3D,0
jne bp_step_next_pin
mov eax,bpPinX3D[esi*4]
sub eax,bpBallX3D
mov deltaX,eax
sar eax,8
imul eax,eax
mov edi,eax
mov eax,bpPinY3D[esi*4]
sub eax,bpBallY3D
sar eax,8
imul eax,eax
add eax,edi
cmp eax,56*56
ja bp_step_next_pin
mov eax,deltaX
sar eax,3
add eax,bpBallVX3D
mov impulseX,eax
mov eax,bpBallVY3D
imul eax,3
sar eax,2
invoke BowlingPlayHit3D,esi,impulseX,eax
bp_step_move_pin:
mov eax,bpPinVX3D[esi*4]
add bpPinX3D[esi*4],eax
imul eax,245
sar eax,8
mov bpPinVX3D[esi*4],eax
mov eax,bpPinVY3D[esi*4]
add bpPinY3D[esi*4],eax
imul eax,245
sar eax,8
mov bpPinVY3D[esi*4],eax
xor edi,edi
bp_step_chain:
mov ecx,edi
mov ebx,1
shl ebx,cl
test rackMask,ebx
jz bp_step_chain_next
test bpHitMask3D,ebx
jnz bp_step_chain_next
mov eax,bpPinX3D[edi*4]
sub eax,bpPinX3D[esi*4]
mov deltaX,eax
sar eax,8
imul eax,eax
mov ebx,eax
mov eax,bpPinY3D[edi*4]
sub eax,bpPinY3D[esi*4]
mov deltaY,eax
sar eax,8
imul eax,eax
add eax,ebx
cmp eax,49*49
ja bp_step_chain_next
mov eax,deltaX
sar eax,8
imul bpPinVX3D[esi*4]
mov ebx,eax
mov eax,deltaY
sar eax,8
imul bpPinVY3D[esi*4]
add eax,ebx
jle bp_step_chain_next
mov eax,bpPinVX3D[esi*4]
sar eax,1
mov ebx,deltaX
sar ebx,3
add eax,ebx
mov impulseX,eax
mov eax,bpPinVY3D[esi*4]
sar eax,1
mov ebx,deltaY
sar ebx,3
add eax,ebx
invoke BowlingPlayHit3D,edi,impulseX,eax
bp_step_chain_next:
inc edi
cmp edi,10
jb bp_step_chain
bp_step_next_pin:
inc esi
cmp esi,10
jb bp_step_pin
ret
BowlingPlayStep3D ENDP
BowlingPlayFinishRoll3D PROC uses esi
mov eax,bpHitMask3D
xor ecx,ecx
bp_finish_count:
test eax,eax
jz bp_finish_register
lea edx,[eax-1]
and eax,edx
inc ecx
jmp bp_finish_count
bp_finish_register:
mov bpLast3D,ecx
mov bpLastKind3D,0
mov esi,bpLane3D
cmp bwBall3D[esi*4],1
jne bp_finish_strike
mov eax,bwFirstHit3D[esi*4]
cmp eax,10
je bp_finish_strike
add eax,ecx
cmp eax,10
jne bp_finish_register_score
mov bpLastKind3D,2
jmp bp_finish_register_score
bp_finish_strike:
cmp ecx,10
jne bp_finish_register_score
mov bpLastKind3D,1
bp_finish_register_score:
invoke BowlingRegisterRoll3D,bpLane3D,ecx
mov bpState3D,BP_SETTLE
mov bpTime3D,0
mov bwMonitorClock3D,-1
ret
BowlingPlayFinishRoll3D ENDP
BowlingPlayNext3D PROC uses ebx esi
mov esi,bpLane3D
mov eax,bpHitMask3D
not eax
and bwRack3D[esi*4],eax
cmp bwNewRack3D[esi*4],0
je @F
mov bwRack3D[esi*4],1023
@@:
cmp bwFrameDone3D[esi*4],0
je bp_next_ready
xor bwTurn3D[esi*4],1
lea ebx,[esi*2]
add ebx,bwTurn3D[esi*4]
cmp bwFrames3D[ebx*4],10
jb bp_next_ready
xor bwTurn3D[esi*4],1
lea ebx,[esi*2]
add ebx,bwTurn3D[esi*4]
cmp bwFrames3D[ebx*4],10
jb bp_next_ready
mov bpState3D,BP_FINISHED
ret
bp_next_ready:
call BowlingPlayRack3D
call BowlingPlayStartAim3D
mov bpTime3D,0
mov bpState3D,BP_DIRECTION
cmp bwTurn3D[esi*4],0
je @F
mov bpState3D,BP_BOT
@@:
ret
BowlingPlayNext3D ENDP
HandleBowlingPlay3D PROC uses ebx esi
local click:DWORD,elapsed:DWORD
invoke Pressed3D,9
test eax,eax
jz @F
xor bpScoreOpen3D,1
@@:
mov eax,mouseClick3D
mov click,eax
mov mouseClick3D,0
cmp click,0
je @F
cmp mouseY3D,224
jb @F
cmp mouseX3D,265
jb @F
call LeaveBowlingPlay3D
ret
@@:
invoke Pressed3D,20h
or click,eax
cmp bpScoreOpen3D,0
jne bp_handle_done
cmp bpState3D,BP_FINISHED
jne bp_handle_clock
cmp click,0
je bp_handle_done
call ResetBowlingPlay3D
ret
bp_handle_clock:
mov eax,deltaTime3D
cmp eax,250
jbe @F
mov eax,250
@@:
mov elapsed,eax
add bpClock3D,eax
add bpTime3D,eax
cmp bpState3D,BP_DIRECTION
jne bp_handle_power
mov eax,bpClock3D
xor edx,edx
mov ecx,4
div ecx
and eax,511
mov eax,tableSin[eax*4]
imul eax,6
cdq
mov ecx,5
idiv ecx
mov bpAim3D,eax
cmp click,0
je bp_handle_done
mov bpLockedAim3D,eax
mov bpState3D,BP_POWER
mov bpClock3D,0
mov bpTime3D,0
ret
bp_handle_power:
cmp bpState3D,BP_POWER
jne bp_handle_release
mov eax,bpClock3D
xor edx,edx
mov ecx,1600
div ecx
mov eax,edx
cmp eax,800
jbe @F
neg eax
add eax,1600
@@:
shr eax,3
mov bpPower3D,eax
cmp click,0
je bp_handle_done
mov bpLockedPower3D,eax
call BowlingPlayLaunch3D
ret
bp_handle_release:
cmp bpState3D,BP_RELEASE
jne bp_handle_roll
mov esi,bpLane3D
mov ecx,350
cmp bwTurn3D[esi*4],0
je @F
mov ecx,2400
@@:
cmp bpTime3D,ecx
jb bp_handle_done
mov bpState3D,BP_ROLL
mov bpTime3D,0
ret
bp_handle_roll:
cmp bpState3D,BP_ROLL
jne bp_handle_settle
mov eax,elapsed
add bpAccumulator3D,eax
bp_handle_fixed_step:
cmp bpAccumulator3D,16
jb bp_handle_roll_end
sub bpAccumulator3D,16
call BowlingPlayStep3D
cmp bpBallY3D,3*FP_1
jl bp_handle_impact_done
jmp bp_handle_fixed_step
bp_handle_roll_end:
cmp bpBallY3D,3*FP_1
jl bp_handle_impact_done
cmp bpTime3D,12000
jb bp_handle_done
bp_handle_impact_done:
call BowlingPlayFinishRoll3D
ret
bp_handle_settle:
cmp bpState3D,BP_SETTLE
jne bp_handle_bot
cmp bpTime3D,1800
jb bp_handle_done
call BowlingPlayNext3D
ret
bp_handle_bot:
cmp bpState3D,BP_BOT
jne bp_handle_done
cmp bpTime3D,1100
jb bp_handle_done
mov eax,bwRng3D
imul eax,1664525
add eax,1013904223
mov bwRng3D,eax
shr eax,16
xor edx,edx
mov ecx,101
div ecx
sub edx,50
imul eax,edx,FP_1
cdq
mov ecx,100
idiv ecx
mov bpLockedAim3D,eax
mov eax,bwRng3D
shr eax,24
and eax,31
add eax,60
mov bpLockedPower3D,eax
call BowlingPlayLaunch3D
bp_handle_done:
ret
HandleBowlingPlay3D ENDP
include src_graphics\bowling_play_render.inc

View file

@ -0,0 +1,80 @@
MODE_BOWLING equ 30
INTERACT_BOWL_PLAY equ 56
BP_DIRECTION equ 0
BP_POWER equ 1
BP_RELEASE equ 2
BP_ROLL equ 3
BP_SETTLE equ 4
BP_BOT equ 5
BP_FINISHED equ 6
BeginBowlingPlay3D PROTO :DWORD
LeaveBowlingPlay3D PROTO
ResetBowlingPlay3D PROTO
BowlingPlayStartAim3D PROTO
HandleBowlingPlay3D PROTO
RenderBowlingPlay3D PROTO
BowlingPlayRack3D PROTO
BowlingPlayLaunch3D PROTO
BowlingPlayStep3D PROTO
BowlingPlayFinishRoll3D PROTO
BowlingPlayNext3D PROTO
BowlingPlayHit3D PROTO :DWORD,:DWORD,:DWORD
DrawBowlingPlayPin3D PROTO :DWORD,:DWORD
BowlingFrameTotal3D PROTO :DWORD,:DWORD
DrawBowlingPlayScores3D PROTO
InitBowlingBallSurface3D PROTO
DrawBowlingGaugeLine3D PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
bpActive3D dd 0
bpLane3D dd 1
bpTarget3D dd 1
bpState3D dd BP_DIRECTION
bpClock3D dd 0
bpTime3D dd 0
bpAim3D dd 0
bpPower3D dd 50
bpLockedAim3D dd 0
bpLockedPower3D dd 50
bpLast3D dd 0
bpLastKind3D dd 0
bpGutter3D dd 0
bpAccumulator3D dd 0
bpScoreOpen3D dd 0
bpBallX3D dd 0
bpBallY3D dd 0
bpBallVX3D dd 0
bpBallVY3D dd 0
bpHitMask3D dd 0
bpPinX3D dd 10 dup(0)
bpPinY3D dd 10 dup(0)
bpPinVX3D dd 10 dup(0)
bpPinVY3D dd 10 dup(0)
bpCentre3D dd 0
bpHomeX3D dd 0
bpHomeY3D dd 0
bpHomeAngle3D dd 0
bpText3D db 24 dup(0)
bpMonitorTexts3D dd OFFSET bpDirectionText,OFFSET bpPowerText,OFFSET bpRollText,OFFSET bpRollText
dd OFFSET bpResultText,OFFSET bpBot,OFFSET bpScoreText
.data?
bpBallSurface3D dd 64*64 dup(?)
.data
bpPlayPrompt db 200,195,208,192,210,220,32,194,32,193,206,211,203,200,205,195,0
bpTitle db 193,206,211,203,200,205,195,0
bpYou db 194,219,0
bpBot db 204,192,202,209,0
bpDirectionText db 205,192,207,208,192,194,203,197,205,200,197,0
bpPowerText db 209,200,203,192,0
bpRollText db 193,208,206,209,206,202,0
bpResultText db 209,193,200,210,206,0
bpWinText db 194,219,32,207,206,193,197,196,200,203,200,0
bpLoseText db 207,206,193,197,196,200,203,32,204,192,202,209,0
bpDrawText db 205,200,215,220,223,0
bpAgainText db 209,205,206,194,192,0
bpExitText db 194,219,201,210,200,0
bpFrameText db 212,208,197,201,204,0
bpScoreText db 209,215,168,210,0
bpGutterText db 198,197,203,206,193,0
bpStrikeText db 209,210,208,192,201,202,0
bpSpareText db 209,207,221,208,0
.code

View file

@ -0,0 +1,459 @@
; A small native marble surface avoids shipping a second copy of a ball sprite.
InitBowlingBallSurface3D PROC uses ebx esi edi
local stripe:DWORD
xor esi,esi
lea edi,bpBallSurface3D
bp_ball_tex_row:
xor ebx,ebx
bp_ball_tex_pixel:
mov eax,esi
shl eax,3
and eax,511
mov eax,tableSin[eax*4]
sar eax,11
lea eax,[eax+ebx*8]
and eax,511
mov eax,tableSin[eax*4]
add eax,FP_1
shr eax,12
mov stripe,eax
lea edx,[eax+5]
shl edx,16
lea eax,[eax*2+48]
shl eax,8
or edx,eax
mov eax,stripe
shl eax,1
add eax,155
or eax,edx
or eax,0FF000000h
stosd
inc ebx
cmp ebx,64
jb bp_ball_tex_pixel
inc esi
cmp esi,64
jb bp_ball_tex_row
ret
InitBowlingBallSurface3D ENDP
DrawBowlingPlayPin3D PROC uses ebx esi edi pin:DWORD,centre:DWORD
local x:DWORD,y:DWORD,z:DWORD,headY:DWORD,ry:DWORD,rz:DWORD
mov esi,pin
mov ecx,esi
mov ebx,1
shl ebx,cl
mov edi,bpLane3D
test bwRack3D[edi*4],ebx
jz bp_pin_done
mov eax,bpPinX3D[esi*4]
add eax,centre
mov x,eax
mov eax,bpPinY3D[esi*4]
mov y,eax
mov headY,eax
mov z,FP_1*15/100
mov ry,FP_1/10
mov rz,FP_1*13/100
test bpHitMask3D,ebx
jz bp_pin_draw
mov z,FP_1*7/100
mov ry,FP_1*23/100
mov rz,FP_1*6/100
sub headY,FP_1/4
bp_pin_draw:
invoke DrawRoofEllipsoid3D,x,y,z,FP_1/10,ry,rz,239
mov eax,FP_1*34/100
test bpHitMask3D,ebx
jz @F
mov eax,FP_1*7/100
@@:
invoke DrawRoofEllipsoid3D,x,headY,eax,FP_1*65/1000,FP_1*65/1000,FP_1*6/100,239
mov eax,z
add eax,rz
invoke DrawRoofEllipsoid3D,x,headY,eax,FP_1*45/1000,FP_1*45/1000,FP_1*25/1000,240
bp_pin_done:
ret
DrawBowlingPlayPin3D ENDP
; A frame's cumulative score stays unresolved until all its bonuses are known.
BowlingFrameTotal3D PROC uses ebx esi edi owner:DWORD,wanted:DWORD
local total:DWORD,index:DWORD,available:DWORD,current:DWORD
mov ebx,owner
mov eax,bwRollCount3D[ebx*4]
mov available,eax
imul ebx,96
lea esi,bwRolls3D[ebx]
mov index,0
mov current,0
mov total,0
bp_frame_loop:
mov edi,index
cmp edi,available
jae bp_frame_pending
mov eax,[esi+edi*4]
mov ebx,eax
inc edi
cmp edi,available
jae bp_frame_pending
add eax,[esi+edi*4]
inc edi
cmp ebx,10
je bp_frame_bonus
cmp eax,10
jne bp_frame_open
bp_frame_bonus:
cmp edi,available
jae bp_frame_pending
add eax,[esi+edi*4]
bp_frame_open:
add total,eax
cmp ebx,10
jne @F
dec edi
@@:
mov index,edi
mov eax,current
cmp eax,wanted
je bp_frame_ready
inc current
jmp bp_frame_loop
bp_frame_ready:
mov eax,total
ret
bp_frame_pending:
mov eax,-1
ret
BowlingFrameTotal3D ENDP
DrawBowlingPlayScores3D PROC uses ebx esi edi
local x:DWORD,y:DWORD,owner:DWORD
invoke DrawBoxBorder,4,47,312,139,00151F26h,005BC9BEh,1
invoke DrawString8x8,12,55,OFFSET bpScoreText,00E8D7ADh
xor esi,esi
bp_score_heading:
imul eax,esi,26
add eax,44
mov x,eax
lea eax,[esi+1]
invoke FormatNumber,eax,OFFSET bpText3D
invoke DrawString8x8,x,73,OFFSET bpText3D,00AABDB6h
inc esi
cmp esi,10
jb bp_score_heading
xor edi,edi
bp_score_owner:
lea eax,[edi*2]
imul eax,18
add eax,96
mov y,eax
mov eax,OFFSET bpYou
test edi,edi
jz @F
mov eax,OFFSET bpBot
@@:
invoke DrawString8x8,9,y,eax,005BC9BEh
mov eax,bpLane3D
shl eax,1
add eax,edi
mov owner,eax
xor esi,esi
bp_score_cell:
imul eax,esi,26
add eax,44
mov x,eax
invoke BowlingFrameTotal3D,owner,esi
cmp eax,-1
je bp_score_empty
invoke FormatNumber,eax,OFFSET bpText3D
invoke DrawString8x8,x,y,OFFSET bpText3D,00FFFFFFh
jmp bp_score_next
bp_score_empty:
invoke DrawBox,x,y,8,1,005C727Bh
bp_score_next:
inc esi
cmp esi,10
jb bp_score_cell
inc edi
cmp edi,2
jb bp_score_owner
ret
DrawBowlingPlayScores3D ENDP
DrawBowlingGaugeLine3D PROC uses ebx esi edi x0:DWORD,y0:DWORD,x1:DWORD,y1:DWORD
local px:DWORD,py:DWORD,stepX:DWORD,stepY:DWORD
mov eax,x1
sub eax,x0
cdq
xor eax,edx
sub eax,edx
mov ebx,eax
mov eax,y1
sub eax,y0
cdq
xor eax,edx
sub eax,edx
cmp eax,ebx
jbe @F
mov ebx,eax
@@:
test ebx,ebx
jnz @F
inc ebx
@@:
mov eax,x1
sub eax,x0
shl eax,16
cdq
idiv ebx
mov stepX,eax
mov eax,y1
sub eax,y0
shl eax,16
cdq
idiv ebx
mov stepY,eax
mov eax,x0
shl eax,16
mov px,eax
mov eax,y0
shl eax,16
mov py,eax
inc ebx
bp_gauge_line:
mov eax,px
sar eax,16
mov edx,py
sar edx,16
invoke DrawBox,eax,edx,1,1,00D8F5B7h
mov eax,stepX
add px,eax
mov eax,stepY
add py,eax
dec ebx
jnz bp_gauge_line
ret
DrawBowlingGaugeLine3D ENDP
RenderBowlingPlay3D PROC uses ebx esi edi
local x:DWORD,y:DWORD,color:DWORD,owner:DWORD,startY:DWORD
mov eax,bpCentre3D
mov playerX,eax
mov startY,FP_1*252/10
mov esi,bpLane3D
cmp bwTurn3D[esi*4],0
je @F
mov startY,FP_1*264/10
@@:
mov eax,startY
mov playerY,eax
mov playerAngle,384
call BowlingEye3D
cmp bpState3D,BP_ROLL
je bp_camera_follow
cmp bpState3D,BP_SETTLE
jne bp_camera_ready
bp_camera_follow:
mov eax,bpBallY3D
add eax,FP_1*27/10
cmp eax,7*FP_1
jge @F
mov eax,7*FP_1
@@:
mov playerY,eax
mov viewEye3D,FP_1*7/10
cmp bpState3D,BP_ROLL
jne bp_camera_ready
mov ebx,bpTime3D
cmp ebx,500
jae bp_camera_ready
sub eax,startY
imul eax,ebx
cdq
mov ecx,500
idiv ecx
add eax,startY
mov playerY,eax
imul eax,ebx,FP_1*7/20
cdq
idiv ecx
mov edx,HALL_STANDING_EYE
sub edx,eax
mov viewEye3D,edx
bp_camera_ready:
call UpdateCamera3D
call RenderBowling3D
mov esi,bpLane3D
cmp bwTurn3D[esi*4],1
jne bp_render_ball
cmp bpState3D,BP_BOT
je bp_render_bot_ready
cmp bpState3D,BP_RELEASE
je bp_render_bot_throw
cmp bpState3D,BP_ROLL
jne bp_render_ball
invoke ConfigureCharacter3D,OFFSET bwActor3D,6,CAST_THROW,2300,0,-FP_1
jmp bp_render_bot_draw
bp_render_bot_throw:
invoke ConfigureCharacter3D,OFFSET bwActor3D,6,CAST_THROW,bpTime3D,0,-FP_1
jmp bp_render_bot_draw
bp_render_bot_ready:
invoke ConfigureCharacter3D,OFFSET bwActor3D,6,CAST_IDLE,bpTime3D,0,0
bp_render_bot_draw:
mov eax,bpCentre3D
add eax,FP_1*38/100
mov bwActor3D.posX,eax
mov bwActor3D.posY,FP_1*235/10
invoke DrawCityObject3D,OFFSET bwActor3D,0
bp_render_ball:
cmp bpState3D,BP_FINISHED
je bp_render_hud
mov eax,bpBallX3D
add eax,bpCentre3D
invoke DrawBowlingBall3D,eax,bpBallY3D,253
bp_render_hud:
invoke DrawBox,0,0,320,17,00121B20h
invoke DrawString8x8,8,4,OFFSET bpTitle,00E8D7ADh
mov eax,bpLane3D
shl eax,1
mov owner,eax
mov esi,eax
invoke DrawString8x8,100,4,OFFSET bpYou,005BC9BEh
invoke FormatNumber,bwScores3D[esi*4],OFFSET bpText3D
invoke DrawString8x8,126,4,OFFSET bpText3D,00FFFFFFh
invoke DrawString8x8,205,4,OFFSET bpBot,00E8D7ADh
invoke FormatNumber,bwScores3D[esi*4+4],OFFSET bpText3D
invoke DrawString8x8,248,4,OFFSET bpText3D,00FFFFFFh
invoke DrawBox,0,221,320,19,00121B20h
invoke DrawString8x8,268,227,OFFSET bpExitText,00E8D7ADh
mov esi,bpLane3D
mov eax,bwTurn3D[esi*4]
add owner,eax
mov eax,OFFSET bpYou
cmp bwTurn3D[esi*4],0
je @F
mov eax,OFFSET bpBot
@@:
invoke DrawString8x8,8,227,eax,005BC9BEh
mov ebx,owner
mov eax,bwFrames3D[ebx*4]
cmp bpState3D,BP_SETTLE
jne @F
cmp bwFrameDone3D[esi*4],0
je @F
dec eax
@@:
inc eax
cmp eax,10
jbe @F
mov eax,10
@@:
invoke FormatNumber,eax,OFFSET bpText3D
invoke DrawString8x8,60,227,OFFSET bpFrameText,00AABDB6h
invoke DrawString8x8,116,227,OFFSET bpText3D,00FFFFFFh
cmp bpState3D,BP_DIRECTION
je bp_render_direction
cmp bpState3D,BP_POWER
je bp_render_power
cmp bpState3D,BP_SETTLE
je bp_render_result
cmp bpState3D,BP_FINISHED
je bp_render_finished
invoke DrawString8x8,8,204,OFFSET bpRollText,00E8D7ADh
jmp bp_render_done
bp_render_direction:
invoke DrawString8x8,108,145,OFFSET bpDirectionText,00E8D7ADh
xor esi,esi
bp_render_arc:
lea eax,[esi-70]
imul eax,eax
xor edx,edx
mov ecx,380
div ecx
add eax,169
mov y,eax
mov color,00D67057h
cmp esi,27
jb @F
cmp esi,113
ja @F
mov color,00E7C455h
cmp esi,59
jb @F
cmp esi,81
ja @F
mov color,005BC98Ch
@@:
lea eax,[esi+90]
invoke DrawBox,eax,y,1,6,color
inc esi
cmp esi,141
jb bp_render_arc
mov eax,bpAim3D
imul eax,52
sar eax,16
add eax,160
mov x,eax
invoke DrawBowlingGaugeLine3D,160,205,eax,162
mov eax,x
lea ebx,[eax-5]
invoke DrawBowlingGaugeLine3D,eax,162,ebx,168
mov eax,x
lea ebx,[eax+5]
invoke DrawBowlingGaugeLine3D,eax,162,ebx,168
jmp bp_render_done
bp_render_power:
invoke DrawString8x8,246,62,OFFSET bpPowerText,00E8D7ADh
invoke DrawBoxBorder,276,79,13,124,00121B20h,00AABDB6h,1
invoke DrawBox,278,82,9,118,00D67057h
invoke DrawBox,278,128,9,38,00E7C455h
invoke DrawBox,278,82,9,46,005BC98Ch
mov eax,bpPower3D
imul eax,116
xor edx,edx
mov ecx,100
div ecx
mov edx,198
sub edx,eax
mov y,edx
invoke DrawBox,270,y,25,2,00FFFFFFh
invoke FormatNumber,bpPower3D,OFFSET bpText3D
invoke DrawString8x8,246,180,OFFSET bpText3D,00FFFFFFh
jmp bp_render_done
bp_render_result:
mov eax,OFFSET bpResultText
cmp bpGutter3D,0
je @F
mov eax,OFFSET bpGutterText
@@:
cmp bpLastKind3D,1
jne @F
mov eax,OFFSET bpStrikeText
@@:
cmp bpLastKind3D,2
jne @F
mov eax,OFFSET bpSpareText
@@:
invoke DrawString8x8,104,197,eax,00E8D7ADh
invoke FormatNumber,bpLast3D,OFFSET bpText3D
invoke DrawString8x8,192,197,OFFSET bpText3D,00FFFFFFh
jmp bp_render_done
bp_render_finished:
mov eax,bwScores3D[esi*8]
mov ebx,OFFSET bpDrawText
cmp eax,bwScores3D[esi*8+4]
je @F
mov ebx,OFFSET bpWinText
ja @F
mov ebx,OFFSET bpLoseText
@@:
invoke DrawBoxBorder,72,150,176,58,00121B20h,005BC9BEh,1
invoke DrawString8x8,88,161,ebx,00E8D7ADh
invoke DrawString8x8,136,187,OFFSET bpAgainText,00FFFFFFh
bp_render_done:
cmp bpScoreOpen3D,0
je @F
call DrawBowlingPlayScores3D
@@:
ret
RenderBowlingPlay3D ENDP

View file

@ -158,7 +158,40 @@ bw_planes_done:
RenderBowlingPlanes3D ENDP
DrawBowlingBall3D PROC uses ebx esi x:DWORD,y:DWORD,mat:DWORD
local holeY:DWORD,holeZ:DWORD,phase:DWORD
invoke DrawRoofEllipsoid3D,x,y,FP_1*135/1000,FP_1*12/100,FP_1*12/100,FP_1*12/100,mat
cmp mat,253
jne bw_ball_done
mov eax,y
sar eax,8
and eax,511
mov phase,eax
mov eax,tableCos[eax*4]
test eax,eax
jle bw_ball_done
imul eax,11
cdq
mov ecx,100
idiv ecx
add eax,y
mov holeY,eax
mov eax,phase
mov eax,tableSin[eax*4]
imul eax,11
cdq
idiv ecx
add eax,FP_1*135/1000
mov holeZ,eax
mov eax,x
sub eax,FP_1*35/1000
invoke DrawRoofEllipsoid3D,eax,holeY,holeZ,FP_1*17/1000,FP_1*17/1000,FP_1*17/1000,233
mov eax,x
add eax,FP_1*35/1000
invoke DrawRoofEllipsoid3D,eax,holeY,holeZ,FP_1*17/1000,FP_1*17/1000,FP_1*17/1000,233
mov eax,holeZ
sub eax,FP_1*38/1000
invoke DrawRoofEllipsoid3D,x,holeY,eax,FP_1*18/1000,FP_1*18/1000,FP_1*18/1000,233
bw_ball_done:
ret
DrawBowlingBall3D ENDP
@ -166,6 +199,13 @@ DrawBowlingPin3D PROC uses ebx esi edi lane:DWORD,pin:DWORD,centre:DWORD
local x:DWORD,y:DWORD,t:DWORD,z:DWORD,ry:DWORD,rz:DWORD,headY:DWORD,headZ:DWORD
mov esi,lane
mov edi,pin
cmp bpActive3D,0
je @F
cmp esi,bpLane3D
jne @F
invoke DrawBowlingPlayPin3D,edi,centre
ret
@@:
mov eax,bwPinX3D[edi*4]
add eax,centre
mov x,eax
@ -263,6 +303,8 @@ bw_render_pin:
jb bw_render_pin
cmp bwBusy3D[esi*4],0
je bw_render_lane_next
cmp bwBusy3D[esi*4],2
je bw_render_lane_next
mov time,-1
cmp bwState3D[esi*4],2
jne @F
@ -347,8 +389,8 @@ RenderBowlingVisitors3D PROC uses ebx esi edi
local action:DWORD,clock:DWORD,vx:DWORD,vy:DWORD
xor esi,esi
bw_cast_lane:
cmp bwBusy3D[esi*4],0
je bw_cast_lane_next
cmp bwBusy3D[esi*4],1
jne bw_cast_lane_next
imul eax,esi,BW_LANE_STEP
add eax,3*FP_1
mov centre,eax
@ -483,6 +525,12 @@ bw_cast_lane_next:
; Four other visitors occupy the dining/bar area; ten visitors, no body doubles.
xor esi,esi
bw_cast_dining:
; The match opponent leaves this restaurant seat instead of appearing twice.
cmp bpActive3D,0
je @F
test esi,esi
jz bw_cast_dining_next
@@:
mov eax,bwVisitorX3D[esi*4]
mov bwSmallActor3D.posX,eax
mov eax,bwVisitorY3D[esi*4]
@ -494,6 +542,7 @@ bw_cast_dining:
add bwSmallActor3D.groundZ,FP_1/4
@@:
invoke DrawCityObject3D,OFFSET bwSmallActor3D,0
bw_cast_dining_next:
inc esi
cmp esi,4
jb bw_cast_dining

View file

@ -191,8 +191,8 @@ bw_update_stand:
bw_update_lanes:
xor esi,esi
bw_update_lane:
cmp bwBusy3D[esi*4],0
je bw_update_next
cmp bwBusy3D[esi*4],1
jne bw_update_next
mov eax,deltaTime3D
add bwTime3D[esi*4],eax
mov eax,bwState3D[esi*4]
@ -372,8 +372,15 @@ bw_monitor_lane:
invoke BowlingMonitorText3D,target,36,30,OFFSET bwFreeText3D,0FF55D9CFh
jmp bw_monitor_advance
bw_monitor_scores:
cmp bwBusy3D[esi*4],2
jne bw_monitor_visitors
invoke BowlingMonitorText3D,target,8,21,OFFSET bpYou,0FFFF9F89h
invoke BowlingMonitorText3D,target,70,21,OFFSET bpBot,0FF55D9CFh
jmp bw_monitor_values
bw_monitor_visitors:
invoke BowlingMonitorText3D,target,8,21,OFFSET bwPlayerA3D,0FFFF9F89h
invoke BowlingMonitorText3D,target,70,21,OFFSET bwPlayerB3D,0FF55D9CFh
bw_monitor_values:
lea ebx,[esi*8]
invoke FormatNumber,bwScores3D[ebx],ADDR number
invoke BowlingMonitorText3D,target,8,33,ADDR number,0FFFFFFFFh
@ -381,6 +388,11 @@ bw_monitor_scores:
invoke BowlingMonitorText3D,target,70,33,ADDR number,0FFFFFFFFh
mov eax,bwState3D[esi*4]
mov eax,bwPhaseTexts3D[eax*4]
cmp bwBusy3D[esi*4],2
jne @F
mov eax,bpState3D
mov eax,bpMonitorTexts3D[eax*4]
@@:
invoke BowlingMonitorText3D,target,8,51,eax,0FFFFBF70h
bw_monitor_advance:
inc esi

View file

@ -166,5 +166,23 @@ ConfigureRoofVisitor3D PROC uses ebx esi edi slot:DWORD
mov vy,-FP_1
cast_roof_ready:
invoke ConfigureCharacter3D,OFFSET roofActor3D,roofGuestIdentity3D[esi*4],phase,clock,vx,vy
; The terrace uses a taller world scale than the hall; keep faces at eye level.
mov eax,CAST_HEIGHT
mov ecx,ROOF_STANDING_EYE
imul ecx
mov ecx,HALL_STANDING_EYE
idiv ecx
mov roofActor3D.worldH,eax
imul eax,192
mov ecx,224
cdq
idiv ecx
mov roofActor3D.worldW,eax
mov eax,roofActor3D.worldH
neg eax
cdq
mov ecx,56
idiv ecx
mov roofActor3D.groundZ,eax
ret
ConfigureRoofVisitor3D ENDP

View file

@ -199,7 +199,7 @@ ShadeCityGround3D ENDP
IsCityEmissive3D PROC uses ebx rgb:DWORD,material:DWORD
cmp material,242
jb @F
cmp material,BW_LAST_MATERIAL
cmp material,252
ja @F
mov eax,1
ret

View file

@ -0,0 +1,266 @@
.data
bpTestDirection db "build\hall-expansion\bowling-play-direction.bmp",0
bpTestDirectionLeft db "build\hall-expansion\bowling-play-direction-left.bmp",0
bpTestPower db "build\hall-expansion\bowling-play-power.bmp",0
bpTestPowerLow db "build\hall-expansion\bowling-play-power-low.bmp",0
bpTestOpponent db "build\hall-expansion\bowling-play-opponent.bmp",0
bpTestRoll db "build\hall-expansion\bowling-play-roll.bmp",0
bpTestScores db "build\hall-expansion\bowling-play-scores.bmp",0
.code
BowlingPlayNativeTests PROC uses ebx esi edi
local bank:DWORD,savedRng:DWORD,savedHits:DWORD,remainingSteps:DWORD
mov expansionStage,150
mov eax,bankroll
mov bank,eax
call InitBowling3D
mov hallLevel3D,HALL_BOWLING
mov engineMode,MODE_WALK
mov playerX,5*FP_1
mov playerY,25*FP_1
mov playerAngle,384
invoke BeginBowlingPlay3D,0
test eax,eax
jnz bp_test_fail
invoke BeginBowlingPlay3D,1
cmp eax,1
jne bp_test_fail
cmp engineMode,MODE_BOWLING
jne bp_test_fail
cmp bwBusy3D[4],2
jne bp_test_fail
mov expansionStage,151
mov deltaTime3D,0
mov bpClock3D,0
mov mouseX3D,160
mov mouseY3D,120
mov viewWidth3D,640
mov viewHeight3D,480
mov viewLeft3D,0
mov viewTop3D,0
invoke WindowEvents3D,0,201h,1,00F00140h
call HandleBowlingPlay3D
cmp bpState3D,BP_POWER
jne bp_test_fail
invoke WindowEvents3D,0,100h,20h,40000000h
call HandleBowlingPlay3D
cmp bpState3D,BP_POWER
jne bp_test_fail
cmp bpLockedAim3D,0
jne bp_test_fail
call HandleBowlingPlay3D
cmp bpState3D,BP_POWER
jne bp_test_fail
mov bpClock3D,560
invoke WindowEvents3D,0,100h,20h,0
call HandleBowlingPlay3D
cmp bpState3D,BP_RELEASE
jne bp_test_fail
cmp bpLockedPower3D,70
jne bp_test_fail
mov eax,bwRng3D
mov savedRng,eax
mov deltaTime3D,16
mov remainingSteps,1100
bp_test_roll:
call HandleBowlingPlay3D
cmp bpState3D,BP_SETTLE
je bp_test_roll_done
dec remainingSteps
jnz bp_test_roll
jmp bp_test_fail
bp_test_roll_done:
mov eax,bwRng3D
cmp eax,savedRng
jne bp_test_fail
cmp bpLast3D,1
jb bp_test_fail
cmp bpLast3D,10
ja bp_test_fail
mov eax,bpHitMask3D
mov savedHits,eax
; The complete fixed-step shot is identical at 16 and 32-ms frame deltas.
call ResetBowlingPlay3D
mov bpLockedAim3D,0
mov bpLockedPower3D,70
call BowlingPlayLaunch3D
mov deltaTime3D,32
mov remainingSteps,600
bp_test_rate32:
call HandleBowlingPlay3D
cmp bpState3D,BP_SETTLE
je bp_test_rate32_done
dec remainingSteps
jnz bp_test_rate32
jmp bp_test_fail
bp_test_rate32_done:
mov eax,bpHitMask3D
cmp eax,savedHits
jne bp_test_fail
mov expansionStage,152
call ResetBowlingPlay3D
mov bpLockedAim3D,0
mov bpLockedPower3D,70
call BowlingPlayLaunch3D
mov esi,600
bp_test_repeat:
call BowlingPlayStep3D
dec esi
jnz bp_test_repeat
mov eax,bpHitMask3D
cmp eax,savedHits
jne bp_test_fail
call ResetBowlingPlay3D
mov bpLockedAim3D,2*FP_1
mov bpLockedPower3D,70
call BowlingPlayLaunch3D
mov esi,600
bp_test_gutter:
call BowlingPlayStep3D
dec esi
jnz bp_test_gutter
cmp bpGutter3D,1
jne bp_test_fail
cmp bpHitMask3D,0
jne bp_test_fail
; A removed pin can never be credited again.
call ResetBowlingPlay3D
mov bwRack3D[4],0
mov bpLockedAim3D,0
mov bpLockedPower3D,70
call BowlingPlayLaunch3D
mov esi,600
bp_test_empty_rack:
call BowlingPlayStep3D
dec esi
jnz bp_test_empty_rack
cmp bpHitMask3D,0
jne bp_test_fail
mov expansionStage,153
call ResetBowlingPlay3D
mov bpHitMask3D,1
call BowlingPlayFinishRoll3D
call BowlingPlayNext3D
cmp bwRack3D[4],1022
jne bp_test_fail
cmp bwTurn3D[4],0
jne bp_test_fail
mov bpHitMask3D,1022
call BowlingPlayFinishRoll3D
call BowlingPlayNext3D
cmp bwRack3D[4],1023
jne bp_test_fail
cmp bwTurn3D[4],1
jne bp_test_fail
cmp bpState3D,BP_BOT
jne bp_test_fail
cmp bpLastKind3D,2
jne bp_test_fail
mov deltaTime3D,250
mov esi,5
bp_test_bot:
call HandleBowlingPlay3D
dec esi
jnz bp_test_bot
cmp bpState3D,BP_RELEASE
jne bp_test_fail
mov expansionStage,154
call ResetBowlingPlay3D
mov bpHitMask3D,1023
call BowlingPlayFinishRoll3D
invoke BowlingFrameTotal3D,2,0
cmp eax,-1
jne bp_test_fail
call BowlingPlayNext3D
mov esi,23
bp_test_perfect:
mov bpHitMask3D,1023
call BowlingPlayFinishRoll3D
call BowlingPlayNext3D
dec esi
jnz bp_test_perfect
cmp bpState3D,BP_FINISHED
jne bp_test_fail
cmp bwScores3D[8],300
jne bp_test_fail
cmp bwScores3D[12],300
jne bp_test_fail
invoke BowlingFrameTotal3D,2,9
cmp eax,300
jne bp_test_fail
mov expansionStage,155
call ResetBowlingPlay3D
mov deltaTime3D,0
mov bpClock3D,150
call HandleBowlingPlay3D
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpTestDirection
mov bpClock3D,1550
call HandleBowlingPlay3D
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpTestDirectionLeft
mov bpState3D,BP_POWER
mov bpClock3D,560
call HandleBowlingPlay3D
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpTestPower
mov bpClock3D,200
call HandleBowlingPlay3D
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpTestPowerLow
mov bpScoreOpen3D,1
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpTestScores
mov bpScoreOpen3D,0
mov bpLockedAim3D,0
mov bpLockedPower3D,70
call BowlingPlayLaunch3D
mov bpState3D,BP_ROLL
mov esi,160
bp_test_flight:
call BowlingPlayStep3D
dec esi
jnz bp_test_flight
mov bpTime3D,2560
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpTestRoll
mov eax,bpBallY3D
mov savedHits,eax
mov pressedKeys3D[9],1
mov deltaTime3D,16
call HandleBowlingPlay3D
call HandleBowlingPlay3D
mov eax,bpBallY3D
cmp eax,savedHits
jne bp_test_fail
mov pressedKeys3D[9],1
call HandleBowlingPlay3D
mov eax,bpBallY3D
cmp eax,savedHits
je bp_test_fail
mov bwTurn3D[4],1
mov bpState3D,BP_RELEASE
mov bpTime3D,1700
call RenderBowlingPlay3D
invoke ExpansionSnapshot,OFFSET bpTestOpponent
mov expansionStage,156
call LeaveBowlingPlay3D
cmp engineMode,MODE_WALK
jne bp_test_fail
cmp bwBusy3D[4],0
jne bp_test_fail
cmp bpActive3D,0
jne bp_test_fail
cmp playerX,5*FP_1
jne bp_test_fail
cmp playerY,25*FP_1
jne bp_test_fail
cmp playerAngle,384
jne bp_test_fail
mov eax,bankroll
cmp eax,bank
jne bp_test_fail
ret
bp_test_fail:
invoke ExitProcess,expansionStage
ret
BowlingPlayNativeTests ENDP

View file

@ -0,0 +1,73 @@
; Interactive fixture: production MASM scene and events, no profile writes.
.code
BowlingUiStart PROC
call InitPaths3D
call Create3DWindow
test eax,eax
jz bp_ui_fail
invoke SetWindowLongA,h3DWnd,-4,OFFSET WindowEvents3D
call InitBowling3D
call LoadBowling3D
mov hallLevel3D,HALL_BOWLING
mov worldMap3D,OFFSET bwMap3D
mov playerX,FP_1*49/10
mov playerY,25*FP_1
mov playerAngle,384
invoke BeginBowlingPlay3D,1
call RenderBowlingPlay3D
invoke ShowWindow,h3DWnd,1
call Present3D
invoke SetForegroundWindow,h3DWnd
invoke GetTickCount
mov previousTick3D,eax
bp_ui_loop:
invoke PeekMessageA,OFFSET msg3D,0,0,0,1
test eax,eax
jz bp_ui_frame
cmp DWORD PTR msg3D[4],12h
je bp_ui_close
invoke TranslateMessage,OFFSET msg3D
invoke DispatchMessageA,OFFSET msg3D
jmp bp_ui_loop
bp_ui_frame:
invoke GetTickCount
mov frameStart3D,eax
sub eax,previousTick3D
cmp eax,100
jbe @F
mov eax,100
@@:
mov deltaTime3D,eax
mov eax,frameStart3D
mov previousTick3D,eax
invoke GetForegroundWindow
cmp eax,h3DWnd
jne bp_ui_idle
invoke Pressed3D,1Bh
test eax,eax
jnz bp_ui_close
mov eax,deltaTime3D
add animationClock3D,eax
call HandleBowlingPlay3D
cmp engineMode,MODE_BOWLING
jne bp_ui_mode_error
call RenderBowlingPlay3D
call Present3D
bp_ui_idle:
invoke Sleep,8
jmp bp_ui_loop
bp_ui_close:
call LeaveBowlingPlay3D
call FreeBowling3D
call FreeCharacterCast3D
invoke ReleaseDC,h3DWnd,h3DDC
invoke DestroyWindow,h3DWnd
invoke ExitProcess,0
ret
bp_ui_fail:
invoke ExitProcess,157
ret
bp_ui_mode_error:
invoke ExitProcess,158
ret
BowlingUiStart ENDP

View file

@ -233,8 +233,10 @@ bw_test_bar_visits:
mov engineMode,MODE_WALK
call ClearKeys3D
call CharacterCastNativeTests
call BowlingPlayNativeTests
ret
bw_test_fail:
invoke ExitProcess,expansionStage
ret
BowlingNativeTests ENDP
include tools/tests/bowling_play_test.inc

View file

@ -260,8 +260,11 @@ cast_test_walking:
mov phase,0
cmp esi,CAST_COUNT
jb cast_test_identity
call RoofCharacterScaleNativeTests
ret
cast_test_fail:
invoke ExitProcess,expansionStage
ret
CharacterCastNativeTests ENDP
include tools/tests/roof_character_scale_test.inc

View file

@ -56,6 +56,12 @@ def check():
walking[0].save(OUT/f'cast-walk-{identity}.gif',save_all=True,
append_images=walking[1:],duration=110,loop=0)
roster.save(OUT/'cast-native-roster.png')
for visitor in range(8):
native = Image.open(OUT/f'roof-cast-{visitor}.bmp').convert('RGB')
box = ImageChops.difference(native, Image.new('RGB', native.size, (28,34,40))).getbbox()
assert box and 175 <= box[3]-box[1] <= 270 and 195 <= box[1] <= 250,('roof visitor scale',visitor,box)
native.save(OUT/f'roof-cast-{visitor}.png')
print('PASS: terrace visitor proportions and grounded feet match the taller rooftop camera')
print('PASS: ten identities, contact/passing gait, eye-level scale, 60 poses and 80 native walking frames')

View file

@ -246,6 +246,13 @@ def check_bowling_room():
throws = [Image.open(OUT/f"bowling-throw-{i}.bmp").tobytes() for i in range(4)]
assert len(set(throws)) == 4,"whole-body throw must visibly progress"
assert len(captures) >= 10
for a,b,region in (
("direction","direction-left",(180,314,462,414)),
("power","power-low",(536,154,596,408)),
):
images = [Image.open(OUT/f"bowling-play-{name}.bmp").convert("RGB").crop(region) for name in (a,b)]
assert ImageChops.difference(*images).getbbox(), (a,b,"timing indicator must move")
print("PASS: playable bowling gates, collision solver, frame-rate invariance, remaining racks, bonus balls, opponent and score sheet")
print(f"PASS: eight bowling lanes, {len(reached)} connected walk points, reachable bar/dining/seats, native throw and room captures")

View file

@ -0,0 +1,35 @@
"""Open the production native bowling scene without accessing player profiles."""
import hashlib
import subprocess
from pathlib import Path
from check_hall_expansion import ROOT, OUT, prepare
from check_artifacts import toolchain
def main():
def profile():
return {str(p): hashlib.sha256(p.read_bytes()).digest()
for p in (ROOT/'runtime').rglob('*') if p.is_file()}
prepare()
source = (OUT/'module.asm').read_text(encoding='cp1251')
source = source.replace('end ExpansionTestStart',
'include tools/tests/bowling_play_ui.inc\nend BowlingUiStart')
target = OUT/'bowling-ui'
target.with_suffix('.asm').write_text(source, encoding='cp1251')
ml,link = toolchain()
sdk = sorted(Path('C:/Program Files (x86)/Windows Kits/10/Lib').glob('*/um/x86'))[-1]
subprocess.run([str(ml),'/c','/coff','/Cp','/nologo','/I.',
f'/Fo{target}.obj',f'{target}.asm'],cwd=ROOT,check=True)
subprocess.run([str(link),'/nologo','/SUBSYSTEM:WINDOWS','/ENTRY:BowlingUiStart@0',
f'/LIBPATH:{sdk}',f'/OUT:{target}.exe',f'{target}.obj',
str(OUT/'helpers.obj'),'kernel32.lib','user32.lib','gdi32.lib',
'winmm.lib','ws2_32.lib'],cwd=ROOT,check=True)
before = profile()
result = subprocess.run([str(target.with_suffix('.exe'))],cwd=ROOT,timeout=900)
assert result.returncode == 0,result.returncode
assert profile() == before,'Interactive bowling changed the player profile'
print('PASS: interactive native bowling closed without changing the player profile')
if __name__ == '__main__':
main()

View file

@ -71,10 +71,10 @@ try {
$asmText = $cp1251.GetString($asmBytes)
foreach ($needle in @(
'SAVE_SIGN_SIZE equ 276',
'SAVE_FILE_SIZE equ 280',
'SAVE_FILE_SIZE equ 312',
'CalcSaveSignature PROC',
'mov DWORD PTR [saveBuffer+SAVE_SIGN_SIZE], eax',
'cmp charsRead, SAVE_FILE_SIZE',
'cmp charsRead,SAVE_FILE_SIZE',
'includelib ws2_32.lib'
)) {
if ($asmText.IndexOf($needle) -lt 0) {

View file

@ -0,0 +1,78 @@
.data
roofCastTest0 db "build\hall-expansion\roof-cast-0.bmp",0
roofCastTest1 db "build\hall-expansion\roof-cast-1.bmp",0
roofCastTest2 db "build\hall-expansion\roof-cast-2.bmp",0
roofCastTest3 db "build\hall-expansion\roof-cast-3.bmp",0
roofCastTest4 db "build\hall-expansion\roof-cast-4.bmp",0
roofCastTest5 db "build\hall-expansion\roof-cast-5.bmp",0
roofCastTest6 db "build\hall-expansion\roof-cast-6.bmp",0
roofCastTest7 db "build\hall-expansion\roof-cast-7.bmp",0
roofCastTestPaths dd OFFSET roofCastTest0,OFFSET roofCastTest1,OFFSET roofCastTest2,OFFSET roofCastTest3
dd OFFSET roofCastTest4,OFFSET roofCastTest5,OFFSET roofCastTest6,OFFSET roofCastTest7
.code
RoofCharacterScaleNativeTests PROC uses ebx esi edi
local moving:DWORD
mov expansionStage,160
mov viewEye3D,ROOF_STANDING_EYE
mov viewHorizon3D,HALF_H
xor esi,esi
roof_cast_test_visitor:
mov eax,roofGuestMoving3D[esi*4]
mov moving,eax
xor ebx,ebx
roof_cast_test_pose:
mov roofGuestMoving3D[esi*4],ebx
invoke ConfigureRoofVisitor3D,esi
; Height relative to standing eyes must match the hall, in either pose.
mov eax,roofActor3D.worldH
mov ecx,HALL_STANDING_EYE
imul ecx
mov ecx,ROOF_STANDING_EYE
idiv ecx
sub eax,CAST_HEIGHT
cmp eax,-1
jl roof_cast_test_fail
cmp eax,1
jg roof_cast_test_fail
mov eax,roofActor3D.worldH
imul eax,192
cdq
mov ecx,224
idiv ecx
cmp eax,roofActor3D.worldW
jne roof_cast_test_fail
mov eax,roofActor3D.worldH
neg eax
cdq
mov ecx,56
idiv ecx
cmp eax,roofActor3D.groundZ
jne roof_cast_test_fail
cmp roofActor3D.asset,OFFSET characterCast3D
jne roof_cast_test_fail
inc ebx
cmp ebx,2
jb roof_cast_test_pose
invoke DrawBox,0,0,SCREEN_W,SCREEN_H,001C2228h
lea edi,zBuffer
mov eax,7FFFFFFFh
mov ecx,SCREEN_W
rep stosd
lea edi,cityDepthBuffer3D
mov ecx,SCREEN_SIZE
rep stosd
mov roofActor3D.posX,16*FP_1
mov roofActor3D.posY,16*FP_1
invoke DrawCityObject3D,OFFSET roofActor3D,0
invoke ExpansionSnapshot,roofCastTestPaths[esi*4]
mov eax,moving
mov roofGuestMoving3D[esi*4],eax
inc esi
cmp esi,ROOF_GUEST_COUNT
jb roof_cast_test_visitor
mov viewEye3D,HALL_STANDING_EYE
ret
roof_cast_test_fail:
invoke ExitProcess,expansionStage
ret
RoofCharacterScaleNativeTests ENDP