.data fortuneSessionBank dd 0 fortuneSessionAnswer db "Situation: begin with one small step.",10 db "Obstacle: avoid rushing and check your assumptions.",10 db "Direction: choose a clear and steady pace.",10 db "Advice: reflect before acting, then take one practical step.",0 fortuneSessionAnswerEnd LABEL BYTE fortuneSessionPlayer dd 0 fortuneFateSavedResult dd 0 fortuneFateSavedSeed dd 0 fortuneFateTestText db "Ivan",0,"Petrov",0,"29022000",0,"IS-42",0 fortuneFateFormShot db "build\hall-expansion\fortune-fate-form.bmp",0 fortuneFateResultShot db "build\hall-expansion\fortune-fate-result.bmp",0 fortuneFateHundredShot db "build\hall-expansion\fortune-fate-hundred.bmp",0 fortuneFateMotion db "build\hall-expansion\fortune-fate-motion-" fortuneFateDigits db "00.bmp",0 fortuneFateTracking db "build\hall-expansion\fortune-fate-track-" fortuneFateTrackingDigits db "00.bmp",0 fortuneFateTrackingBase db "build\hall-expansion\fortune-fate-base-" fortuneFateTrackingBaseDigits db "00.bmp",0 fortuneSessionMotion db "build\hall-expansion\fortune-session-motion-" fortuneSessionDigits db "00.bmp",0 fortuneSessionQuestion db 193,243,228,229,242,32,235,232,32,231,224,226,242,240,224,32,228,238,230,228,252,63,0 fortuneSessionShot0 db "build\hall-expansion\fortune-session-input.bmp",0 fortuneSessionShot1 db "build\hall-expansion\fortune-session-shuffle.bmp",0 fortuneSessionShot2 db "build\hall-expansion\fortune-session-deal-one.bmp",0 fortuneSessionShot3 db "build\hall-expansion\fortune-session-deal-two.bmp",0 fortuneSessionShot4 db "build\hall-expansion\fortune-session-deal-three.bmp",0 fortuneSessionShot5 db "build\hall-expansion\fortune-session-flip-one.bmp",0 fortuneSessionShot6 db "build\hall-expansion\fortune-session-explain-one.bmp",0 fortuneSessionShot7 db "build\hall-expansion\fortune-session-flip-two.bmp",0 fortuneSessionShot8 db "build\hall-expansion\fortune-session-explain-two.bmp",0 fortuneSessionShot9 db "build\hall-expansion\fortune-session-flip-three.bmp",0 fortuneSessionShot10 db "build\hall-expansion\fortune-session-explain-three.bmp",0 fortuneSessionShot11 db "build\hall-expansion\fortune-session-summary.bmp",0 fortuneSessionShot12 db "build\hall-expansion\fortune-session-done.bmp",0 fortuneSessionShot13 db "build\hall-expansion\fortune-session-orb-rise.bmp",0 fortuneSessionShot14 db "build\hall-expansion\fortune-session-orb-mist.bmp",0 fortuneSessionShot15 db "build\hall-expansion\fortune-session-orb-final.bmp",0 fortuneSessionShots dd OFFSET fortuneSessionShot0,OFFSET fortuneSessionShot1,OFFSET fortuneSessionShot2,OFFSET fortuneSessionShot3 dd OFFSET fortuneSessionShot4,OFFSET fortuneSessionShot5,OFFSET fortuneSessionShot6,OFFSET fortuneSessionShot7 dd OFFSET fortuneSessionShot8,OFFSET fortuneSessionShot9,OFFSET fortuneSessionShot10,OFFSET fortuneSessionShot11 dd OFFSET fortuneSessionShot12 .code FortuneSessionStepTest PROC add animationClock3D,100 call UpdateFortuneSession3D ret FortuneSessionStepTest ENDP FortuneDialogNativeTests PROC uses ebx esi edi mov expansionStage,80 mov eax,bankroll mov fortuneSessionBank,eax mov eax,playerX mov fortuneSessionPlayer,eax call BeginFortuneDialog3D cmp fortuneSessionShuffle3D.frames,64 jne fortune_session_fail cmp fortuneSessionDeal3D.frames,32 jne fortune_session_fail cmp fortuneSessionCards3D.frames,45 jne fortune_session_fail cmp fortuneSessionNumerals3D.frames,10 jne fortune_session_fail cmp fortuneProcess3D,0 jne fortune_session_fail cmp fortuneRead3D,0 jne fortune_session_fail invoke WindowEvents3D,0,102h,'1',0 cmp fortuneSubMode3D,FORTUNE_SUBMODE_TAROT jne fortune_session_fail ; Lower edge of the question box is not an Exit-button hit target. mov mouseX3D,260 mov mouseY3D,222 mov mouseClick3D,1 call HandleFortuneDialog3D cmp engineMode,MODE_FORTUNE jne fortune_session_fail call SubmitFortuneQuestion3D cmp fortunePhase3D,0 jne fortune_session_fail mov esi,250 fortune_session_input_limit: invoke WindowEvents3D,0,102h,'a',0 dec esi jnz fortune_session_input_limit cmp fortuneQuestionLength3D,240 jne fortune_session_fail cmp fortuneQuestion3D[240],0 jne fortune_session_fail invoke WindowEvents3D,0,102h,8,0 cmp fortuneQuestionLength3D,239 jne fortune_session_fail mov fortuneQuestionLength3D,0 mov fortuneQuestion3D,0 mov esi,OFFSET fortuneSessionQuestion fortune_session_input: movzx eax,BYTE PTR [esi] test eax,eax jz fortune_session_input_done invoke WindowEvents3D,0,102h,eax,0 inc esi jmp fortune_session_input fortune_session_input_done: mov fortuneSpeechTime3D,3000 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneSessionShot0 call SubmitFortuneQuestion3D cmp fortunePhase3D,1 jne fortune_session_fail ; Scene/render checks use a controlled answer, not CPU/model timing. call CloseFortuneInference3D lea esi,fortuneSessionAnswer lea edi,fortuneAnswer3D mov ecx,fortuneSessionAnswerEnd-fortuneSessionAnswer rep movsb mov fortuneAnswerLength3D,fortuneSessionAnswerEnd-fortuneSessionAnswer-1 call ParseFortuneAnswer3D test eax,eax jz fortune_session_fail mov fortuneWorkerState3D,5 cmp fortuneDealt3D,0 jne fortune_session_fail invoke WindowEvents3D,0,102h,'X',0 cmp fortuneQuestionLength3D,22 jne fortune_session_fail call SubmitFortuneQuestion3D cmp fortunePhase3D,1 jne fortune_session_fail mov fortunePhaseTime3D,700 mov fortuneSpeechTime3D,1000 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneSessionShot1 cmp fortunePhaseTime3D,700 jne fortune_session_fail xor edi,edi fortune_session_motion: mov eax,edi xor edx,edx mov ecx,10 div ecx add al,'0' add dl,'0' mov fortuneSessionDigits[0],al mov fortuneSessionDigits[1],dl mov eax,edi imul eax,25 mov fortunePhaseTime3D,eax call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneSessionMotion inc edi cmp edi,64 jb fortune_session_motion mov ebx,1 fortune_session_phases: mov fortuneReadingShown3D,2 mov eax,fortuneDurations3D[ebx*4] mov fortunePhaseTime3D,eax call FortuneSessionStepTest inc ebx cmp fortunePhase3D,ebx jne fortune_session_fail cmp ebx,6 je fortune_session_reading cmp ebx,8 je fortune_session_reading cmp ebx,10 je fortune_session_reading cmp ebx,11 jne fortune_session_render fortune_session_reading: call FortuneSessionStepTest cmp fortuneReadingShown3D,1 jne fortune_session_fail fortune_session_render: mov fortunePhaseTime3D,600 mov fortuneSpeechTime3D,20000 call RenderFortuneDialog3D invoke ExpansionSnapshot,fortuneSessionShots[ebx*4] cmp ebx,12 jb fortune_session_phases cmp fortuneDealt3D,3 jne fortune_session_fail cmp fortuneRevealed3D,3 jne fortune_session_fail cmp fortuneQuestionLength3D,22 jne fortune_session_fail mov eax,fortuneSpeechLength3D cmp fortuneSpeechVisible3D,eax jne fortune_session_fail ; Distinct underlying cards even when orientation differs. xor esi,esi fortune_session_unique_outer: mov eax,fortuneDrawCards3D[esi*4] xor edx,edx mov ecx,22 div ecx mov edi,edx lea ebx,[esi+1] fortune_session_unique_inner: cmp ebx,3 jae fortune_session_unique_next mov eax,fortuneDrawCards3D[ebx*4] xor edx,edx div ecx cmp edx,edi je fortune_session_fail inc ebx jmp fortune_session_unique_inner fortune_session_unique_next: inc esi cmp esi,2 jb fortune_session_unique_outer mov expansionStage,81 ; Bubble grows as glyphs appear and cannot exceed the scene edge. invoke SetFortuneSpeech3D,OFFSET fortuneSummary3D mov fortuneSpeechTime3D,280 call RenderFortuneDialog3D mov esi,fortuneSpeechWidth3D mov fortuneSpeechTime3D,3000 call RenderFortuneDialog3D cmp fortuneSpeechWidth3D,esi jbe fortune_session_fail cmp fortuneSpeechWidth3D,204 ja fortune_session_fail cmp fortuneSpeechHeight3D,140 ja fortune_session_fail ; The fictional crystal-ball lottery requires a form, not a neural worker. mov mouseX3D,200 mov mouseY3D,232 mov mouseClick3D,1 call HandleFortuneDialog3D cmp fortuneSubMode3D,0 jne fortune_session_fail invoke WindowEvents3D,0,102h,'2',0 cmp fortuneSubMode3D,FORTUNE_SUBMODE_ORB jne fortune_session_fail cmp fortunePhase3D,0 jne fortune_session_fail call SubmitFortuneFate3D cmp fortunePhase3D,0 jne fortune_session_fail cmp fortuneFateField3D,0 jne fortune_session_fail ; CP1251, control filtering and maximum field widths use the real input path. mov ebx,40 fortune_fate_name_limit: invoke WindowEvents3D,0,102h,200,0 dec ebx jnz fortune_fate_name_limit cmp fortuneFateLengths3D,30 jne fortune_session_fail cmp fortuneFateFields3D[30],0 jne fortune_session_fail invoke FortuneChar3D,127 cmp fortuneFateLengths3D,30 jne fortune_session_fail call ResetFortuneFate3D mov esi,OFFSET fortuneFateTestText xor ebx,ebx fortune_fate_input_fields: movzx eax,BYTE PTR [esi] inc esi test eax,eax jz fortune_fate_input_next invoke WindowEvents3D,0,102h,eax,0 jmp fortune_fate_input_fields fortune_fate_input_next: invoke WindowEvents3D,0,102h,9,0 inc ebx cmp ebx,4 jb fortune_fate_input_fields cmp fortuneFateLengths3D[8],10 jne fortune_session_fail call ValidateFortuneBirth3D cmp eax,1 jne fortune_session_fail mov fortuneFateFields3D[137],'1' call ValidateFortuneBirth3D test eax,eax jnz fortune_session_fail call SubmitFortuneFate3D cmp fortuneFateField3D,2 jne fortune_session_fail cmp fortunePhase3D,0 jne fortune_session_fail mov fortuneFateFields3D[137],'0' mov fortuneFateField3D,4 invoke FortuneChar3D,'x' cmp fortuneFateLengths3D[16],1 jne fortune_session_fail ; A value larger than 100 is rejected, not clamped or used as an outcome. mov fortuneFateFields3D[320],'1' mov fortuneFateFields3D[321],'0' mov fortuneFateFields3D[322],'1' mov fortuneFateLengths3D[20],3 call SubmitFortuneFate3D cmp fortunePhase3D,0 jne fortune_session_fail mov fortuneFateFields3D[320],'8' mov fortuneFateFields3D[321],0 mov fortuneFateFields3D[322],0 mov fortuneFateLengths3D[20],1 call SubmitFortuneFate3D cmp fortunePhase3D,0 jne fortune_session_fail cmp fortuneFateField3D,5 jne fortune_session_fail mov fortuneFateFields3D[320],'1' mov fortuneFateFields3D[321],'0' mov fortuneFateLengths3D[20],2 invoke SetFortuneSpeech3D,OFFSET fortuneFateReady3D mov fortuneSpeechTime3D,3000 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneFateFormShot ; Mouse focus and backspace obey per-field limits and reject control bytes. mov mouseX3D,200 mov mouseY3D,168 mov mouseClick3D,1 call HandleFortuneDialog3D cmp fortuneFateField3D,1 jne fortune_session_fail invoke FortuneChar3D,8 cmp fortuneFateLengths3D[4],5 jne fortune_session_fail invoke FortuneChar3D,'v' invoke WindowEvents3D,0,100h,0Dh,0 invoke WindowEvents3D,0,102h,0Dh,0 call HandleFortuneDialog3D invoke WindowEvents3D,0,101h,0Dh,0 cmp fortunePhase3D,1 jne fortune_session_fail mov eax,fortuneFateResult3D cmp eax,9 jb fortune_session_fail cmp eax,10 ja fortune_session_fail mov fortuneFateSavedResult,eax mov eax,rngSeed3D mov fortuneFateSavedSeed,eax call SubmitFortuneFate3D invoke FortuneChar3D,'X' cmp fortuneFateLengths3D[4],6 jne fortune_session_fail mov eax,fortuneFateSavedResult cmp fortuneFateResult3D,eax jne fortune_session_fail mov eax,rngSeed3D cmp eax,fortuneFateSavedSeed jne fortune_session_fail ; Again cannot bypass the ritual or reroll while hands are moving. mov mouseX3D,120 mov mouseY3D,232 mov mouseClick3D,1 call HandleFortuneDialog3D cmp fortunePhase3D,1 jne fortune_session_fail mov fortunePhaseTime3D,700 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneSessionShot13 mov fortunePhaseTime3D,1400 call FortuneSessionStepTest cmp fortunePhase3D,2 jne fortune_session_fail mov fortunePhaseTime3D,1500 mov fortuneSpeechTime3D,3000 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneSessionShot14 xor edi,edi fortune_fate_motion: mov eax,edi xor edx,edx mov ecx,10 div ecx add al,'0' add dl,'0' mov fortuneFateDigits[0],al mov fortuneFateDigits[1],dl mov eax,edi imul eax,32 mov fortunePhaseTime3D,eax call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneFateMotion mov eax,fortuneFateSavedResult cmp fortuneFateResult3D,eax jne fortune_session_fail inc edi cmp edi,32 jb fortune_fate_motion mov fortunePhaseTime3D,5000 call FortuneSessionStepTest cmp fortunePhase3D,3 jne fortune_session_fail mov fortuneSpeechTime3D,3000 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneSessionShot15 mov fortunePhaseTime3D,1200 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneFateResultShot xor edi,edi fortune_fate_tracking_frames: mov eax,edi xor edx,edx mov ecx,10 div ecx add al,'0' add dl,'0' mov fortuneFateTrackingDigits[0],al mov fortuneFateTrackingDigits[1],dl mov fortuneFateTrackingBaseDigits[0],al mov fortuneFateTrackingBaseDigits[1],dl mov eax,edi shl eax,6 add eax,2048 mov fortunePhaseTime3D,eax call RenderFortuneDialog3D cmp fortuneOrbFrame3D,edi jne fortune_session_fail mov eax,fortuneOrbAnchorX3D[edi*4] add eax,128 shr eax,8 add eax,214 cmp fortuneOrbCenterX3D,eax jne fortune_session_fail mov eax,fortuneOrbAnchorY3D[edi*4] add eax,128 shr eax,8 add eax,195 cmp fortuneOrbCenterY3D,eax jne fortune_session_fail cmp animOpacity3D,255 jne fortune_session_fail invoke ExpansionSnapshot,OFFSET fortuneFateTracking mov fortuneFateResult3D,-1 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneFateTrackingBase mov eax,fortuneFateSavedResult mov fortuneFateResult3D,eax inc edi cmp edi,16 jb fortune_fate_tracking_frames mov mouseX3D,120 mov mouseY3D,232 mov mouseClick3D,1 call HandleFortuneDialog3D cmp fortunePhase3D,0 jne fortune_session_fail cmp fortuneFateResult3D,-1 jne fortune_session_fail cmp fortuneFateLengths3D[4],6 jne fortune_session_fail ; Test the full inclusive range, zero, fixed outcomes and both endpoints. mov ebx,400 fortune_fate_range_samples: mov fortunePhase3D,0 mov fortuneFateFields3D[256],'0' mov fortuneFateLengths3D[16],1 mov fortuneFateFields3D[320],'1' mov fortuneFateFields3D[321],'0' mov fortuneFateFields3D[322],'0' mov fortuneFateFields3D[323],0 mov fortuneFateLengths3D[20],3 call SubmitFortuneFate3D cmp fortuneFateResult3D,100 ja fortune_session_fail dec ebx jnz fortune_fate_range_samples ; Endpoints are checked separately without relying on a random sample. mov fortunePhase3D,0 mov fortuneFateFields3D[256],'0' mov fortuneFateFields3D[320],'0' mov fortuneFateFields3D[321],0 mov fortuneFateLengths3D[20],1 call SubmitFortuneFate3D cmp fortuneFateResult3D,0 jne fortune_session_fail mov fortunePhase3D,0 mov fortuneFateFields3D[256],'1' mov fortuneFateFields3D[257],'0' mov fortuneFateFields3D[258],'0' mov fortuneFateLengths3D[16],3 mov fortuneFateFields3D[320],'1' mov fortuneFateFields3D[321],'0' mov fortuneFateFields3D[322],'0' mov fortuneFateLengths3D[20],3 call SubmitFortuneFate3D cmp fortuneFateResult3D,100 jne fortune_session_fail mov fortunePhase3D,3 mov fortunePhaseTime3D,1200 call RenderFortuneDialog3D invoke ExpansionSnapshot,OFFSET fortuneFateHundredShot ; Exit in the middle of an animation as well as after completion. mov mouseX3D,260 mov mouseY3D,232 mov mouseClick3D,1 call HandleFortuneDialog3D cmp engineMode,MODE_WALK jne fortune_session_fail cmp fortuneSessionRoom3D.pixels,0 jne fortune_session_fail cmp fortuneSessionNumerals3D.pixels,0 jne fortune_session_fail cmp fortuneFateLengths3D,0 jne fortune_session_fail cmp fortuneFateFields3D,0 jne fortune_session_fail call BeginFortuneDialog3D invoke FortuneChar3D,'1' invoke FortuneChar3D,'a' call SubmitFortuneQuestion3D mov mouseX3D,260 mov mouseY3D,232 mov mouseClick3D,1 call HandleFortuneDialog3D cmp engineMode,MODE_WALK jne fortune_session_fail cmp fortuneSessionShuffle3D.pixels,0 jne fortune_session_fail mov eax,bankroll cmp eax,fortuneSessionBank jne fortune_session_fail mov eax,playerX cmp eax,fortuneSessionPlayer jne fortune_session_fail ret fortune_session_fail: invoke ExitProcess,expansionStage ret FortuneDialogNativeTests ENDP