Skip to main content

siglus_scene_vm/runtime/
wait.rs

1//! VM wait/blocking state.
2//!
3//! The original engine has many commands/forms that block execution until:
4//! - a certain time passes, or
5//! - the user presses a key / clicks.
6//!
7//! Cross-platform blocking and wait model.
8
9use crate::platform_time::{Duration, Instant};
10
11use crate::audio::{BgmEngine, KoeEngine, PcmEngine, SeEngine};
12
13use super::Value;
14use super::constants::RuntimeConstants;
15use super::globals::{GlobalState, ObjectState, StageFormState};
16use super::int_event::IntEvent;
17
18fn anim_skip_trace_enabled() -> bool {
19    env_is_set!("SG_DEBUG")
20}
21
22fn anim_skip_trace(msg: impl AsRef<str>) {
23    if anim_skip_trace_enabled() {
24        eprintln!("[SG_DEBUG][ANIM_SKIP_TRACE][WAIT] {}", msg.as_ref());
25    }
26}
27
28fn int_event_state(ev: &IntEvent) -> String {
29    format!(
30        "def={} value={} cur={} start={} end={} cur_time={} end_time={} delay={} loop_type={} speed={} real={} active={}",
31        ev.def_value,
32        ev.value,
33        ev.cur_value,
34        ev.start_value,
35        ev.end_value,
36        ev.cur_time,
37        ev.end_time,
38        ev.delay_time,
39        ev.loop_type,
40        ev.speed_type,
41        ev.real_flag,
42        ev.check_event(),
43    )
44}
45
46fn object_event_op_name(ids: &RuntimeConstants, op: i32) -> &'static str {
47    if ids.obj_patno_eve != 0 && op == ids.obj_patno_eve {
48        return "PATNO_EVE";
49    }
50    if ids.obj_x_eve != 0 && op == ids.obj_x_eve {
51        return "X_EVE";
52    }
53    if ids.obj_y_eve != 0 && op == ids.obj_y_eve {
54        return "Y_EVE";
55    }
56    if ids.obj_z_eve != 0 && op == ids.obj_z_eve {
57        return "Z_EVE";
58    }
59    if ids.obj_center_x_eve != 0 && op == ids.obj_center_x_eve {
60        return "CENTER_X_EVE";
61    }
62    if ids.obj_center_y_eve != 0 && op == ids.obj_center_y_eve {
63        return "CENTER_Y_EVE";
64    }
65    if ids.obj_center_z_eve != 0 && op == ids.obj_center_z_eve {
66        return "CENTER_Z_EVE";
67    }
68    if ids.obj_center_rep_x_eve != 0 && op == ids.obj_center_rep_x_eve {
69        return "CENTER_REP_X_EVE";
70    }
71    if ids.obj_center_rep_y_eve != 0 && op == ids.obj_center_rep_y_eve {
72        return "CENTER_REP_Y_EVE";
73    }
74    if ids.obj_center_rep_z_eve != 0 && op == ids.obj_center_rep_z_eve {
75        return "CENTER_REP_Z_EVE";
76    }
77    if ids.obj_scale_x_eve != 0 && op == ids.obj_scale_x_eve {
78        return "SCALE_X_EVE";
79    }
80    if ids.obj_scale_y_eve != 0 && op == ids.obj_scale_y_eve {
81        return "SCALE_Y_EVE";
82    }
83    if ids.obj_scale_z_eve != 0 && op == ids.obj_scale_z_eve {
84        return "SCALE_Z_EVE";
85    }
86    if ids.obj_rotate_x_eve != 0 && op == ids.obj_rotate_x_eve {
87        return "ROTATE_X_EVE";
88    }
89    if ids.obj_rotate_y_eve != 0 && op == ids.obj_rotate_y_eve {
90        return "ROTATE_Y_EVE";
91    }
92    if ids.obj_rotate_z_eve != 0 && op == ids.obj_rotate_z_eve {
93        return "ROTATE_Z_EVE";
94    }
95    if ids.obj_clip_left_eve != 0 && op == ids.obj_clip_left_eve {
96        return "CLIP_LEFT_EVE";
97    }
98    if ids.obj_clip_top_eve != 0 && op == ids.obj_clip_top_eve {
99        return "CLIP_TOP_EVE";
100    }
101    if ids.obj_clip_right_eve != 0 && op == ids.obj_clip_right_eve {
102        return "CLIP_RIGHT_EVE";
103    }
104    if ids.obj_clip_bottom_eve != 0 && op == ids.obj_clip_bottom_eve {
105        return "CLIP_BOTTOM_EVE";
106    }
107    if ids.obj_src_clip_left_eve != 0 && op == ids.obj_src_clip_left_eve {
108        return "SRC_CLIP_LEFT_EVE";
109    }
110    if ids.obj_src_clip_top_eve != 0 && op == ids.obj_src_clip_top_eve {
111        return "SRC_CLIP_TOP_EVE";
112    }
113    if ids.obj_src_clip_right_eve != 0 && op == ids.obj_src_clip_right_eve {
114        return "SRC_CLIP_RIGHT_EVE";
115    }
116    if ids.obj_src_clip_bottom_eve != 0 && op == ids.obj_src_clip_bottom_eve {
117        return "SRC_CLIP_BOTTOM_EVE";
118    }
119    if ids.obj_tr_eve != 0 && op == ids.obj_tr_eve {
120        return "TR_EVE";
121    }
122    if ids.obj_mono_eve != 0 && op == ids.obj_mono_eve {
123        return "MONO_EVE";
124    }
125    if ids.obj_reverse_eve != 0 && op == ids.obj_reverse_eve {
126        return "REVERSE_EVE";
127    }
128    if ids.obj_bright_eve != 0 && op == ids.obj_bright_eve {
129        return "BRIGHT_EVE";
130    }
131    if ids.obj_dark_eve != 0 && op == ids.obj_dark_eve {
132        return "DARK_EVE";
133    }
134    if ids.obj_color_r_eve != 0 && op == ids.obj_color_r_eve {
135        return "COLOR_R_EVE";
136    }
137    if ids.obj_color_g_eve != 0 && op == ids.obj_color_g_eve {
138        return "COLOR_G_EVE";
139    }
140    if ids.obj_color_b_eve != 0 && op == ids.obj_color_b_eve {
141        return "COLOR_B_EVE";
142    }
143    if ids.obj_color_rate_eve != 0 && op == ids.obj_color_rate_eve {
144        return "COLOR_RATE_EVE";
145    }
146    if ids.obj_color_add_r_eve != 0 && op == ids.obj_color_add_r_eve {
147        return "COLOR_ADD_R_EVE";
148    }
149    if ids.obj_color_add_g_eve != 0 && op == ids.obj_color_add_g_eve {
150        return "COLOR_ADD_G_EVE";
151    }
152    if ids.obj_color_add_b_eve != 0 && op == ids.obj_color_add_b_eve {
153        return "COLOR_ADD_B_EVE";
154    }
155    if ids.obj_x_rep_eve != 0 && op == ids.obj_x_rep_eve {
156        return "X_REP_EVE";
157    }
158    if ids.obj_y_rep_eve != 0 && op == ids.obj_y_rep_eve {
159        return "Y_REP_EVE";
160    }
161    if ids.obj_z_rep_eve != 0 && op == ids.obj_z_rep_eve {
162        return "Z_REP_EVE";
163    }
164    if ids.obj_tr_rep_eve != 0 && op == ids.obj_tr_rep_eve {
165        return "TR_REP_EVE";
166    }
167    "UNKNOWN_EVE"
168}
169
170#[derive(Debug, Clone, Copy)]
171pub enum AudioWait {
172    Bgm,
173    BgmFade,
174    KoeAny,
175    SeAny,
176    PcmAny,
177    PcmSlot(u8),
178    PcmSlotFade(u8),
179}
180
181#[derive(Debug, Clone)]
182pub enum EventWait {
183    ObjectAll {
184        stage_form_id: u32,
185        stage_idx: i64,
186        runtime_slot: usize,
187    },
188    ObjectOne {
189        stage_form_id: u32,
190        stage_idx: i64,
191        runtime_slot: usize,
192        op: i32,
193    },
194    ObjectList {
195        stage_form_id: u32,
196        stage_idx: i64,
197        runtime_slot: usize,
198        list_op: i32,
199        list_idx: usize,
200    },
201    GenericIntEvent {
202        form_id: u32,
203        index: Option<usize>,
204    },
205    ScreenEffect {
206        form_id: u32,
207        index: usize,
208        op: i32,
209    },
210    StageEffect {
211        stage_form_id: u32,
212        stage_idx: i64,
213        index: usize,
214        op: i32,
215    },
216    Mask {
217        form_id: u32,
218        index: usize,
219        op: i32,
220    },
221    FogX,
222    CounterThreshold {
223        form_id: u32,
224        index: usize,
225        target: i64,
226    },
227    PcmEvent {
228        form_id: u32,
229        index: usize,
230    },
231}
232
233#[derive(Debug, Clone, Copy)]
234pub struct MovieWait {
235    pub stage_form_id: u32,
236    pub stage_idx: i64,
237    pub runtime_slot: usize,
238    pub return_value_flag: bool,
239}
240
241#[derive(Debug, Clone, Copy)]
242pub struct EmoteWait {
243    pub stage_form_id: u32,
244    pub stage_idx: i64,
245    pub runtime_slot: usize,
246    pub return_value_flag: bool,
247}
248
249#[derive(Debug, Clone, Copy)]
250pub enum QuakeWait {
251    Screen {
252        form_id: u32,
253        index: usize,
254    },
255    Stage {
256        stage_form_id: u32,
257        stage_idx: i64,
258        index: usize,
259    },
260}
261
262fn quake_wait_active(globals: &GlobalState, wait: QuakeWait) -> bool {
263    match wait {
264        QuakeWait::Screen { form_id, index } => globals
265            .screen_forms
266            .get(&form_id)
267            .and_then(|screen| screen.quake_list.get(index))
268            .map(|quake| quake.is_active())
269            .unwrap_or(false),
270        QuakeWait::Stage {
271            stage_form_id,
272            stage_idx,
273            index,
274        } => globals
275            .stage_forms
276            .get(&stage_form_id)
277            .and_then(|stage| stage.quake_lists.get(&stage_idx))
278            .and_then(|quakes| quakes.get(index))
279            .map(|quake| quake.is_active())
280            .unwrap_or(false),
281    }
282}
283
284fn stop_waited_quake(globals: &mut GlobalState, wait: QuakeWait) {
285    let quake = match wait {
286        QuakeWait::Screen { form_id, index } => globals
287            .screen_forms
288            .get_mut(&form_id)
289            .and_then(|screen| screen.quake_list.get_mut(index)),
290        QuakeWait::Stage {
291            stage_form_id,
292            stage_idx,
293            index,
294        } => globals
295            .stage_forms
296            .get_mut(&stage_form_id)
297            .and_then(|stage| stage.quake_lists.get_mut(&stage_idx))
298            .and_then(|quakes| quakes.get_mut(index)),
299    };
300    if let Some(quake) = quake {
301        quake.reinit();
302    }
303}
304
305fn object_runtime_slot(idx: usize, obj: &ObjectState) -> usize {
306    obj.runtime_slot_or(idx)
307}
308
309fn find_object_by_runtime_slot(
310    objects: &[ObjectState],
311    runtime_slot: usize,
312) -> Option<&ObjectState> {
313    for (idx, obj) in objects.iter().enumerate() {
314        if object_runtime_slot(idx, obj) == runtime_slot {
315            return Some(obj);
316        }
317        if let Some(found) = find_object_by_runtime_slot(&obj.runtime.child_objects, runtime_slot) {
318            return Some(found);
319        }
320    }
321    None
322}
323
324fn find_object_by_runtime_slot_mut(
325    mut objects: &mut [ObjectState],
326    runtime_slot: usize,
327) -> Option<&mut ObjectState> {
328    let mut idx = 0usize;
329    while let Some((obj, tail)) = objects.split_first_mut() {
330        if object_runtime_slot(idx, obj) == runtime_slot {
331            return Some(obj);
332        }
333        if let Some(found) =
334            find_object_by_runtime_slot_mut(&mut obj.runtime.child_objects, runtime_slot)
335        {
336            return Some(found);
337        }
338        objects = tail;
339        idx += 1;
340    }
341    None
342}
343
344fn object_event_list_for_wait<'a>(
345    obj: &'a ObjectState,
346    ids: &RuntimeConstants,
347    op: i32,
348) -> Option<&'a Vec<IntEvent>> {
349    obj.int_event_list_by_op(ids, op)
350        .or_else(|| obj.rep_int_event_list_by_rep_op(ids, op))
351}
352
353fn object_event_list_for_wait_mut<'a>(
354    obj: &'a mut ObjectState,
355    ids: &RuntimeConstants,
356    op: i32,
357) -> Option<&'a mut Vec<IntEvent>> {
358    if ids.obj_x_rep_eve != 0 && op == ids.obj_x_rep_eve {
359        Some(&mut obj.runtime.prop_event_lists.x_rep)
360    } else if ids.obj_y_rep_eve != 0 && op == ids.obj_y_rep_eve {
361        Some(&mut obj.runtime.prop_event_lists.y_rep)
362    } else if ids.obj_z_rep_eve != 0 && op == ids.obj_z_rep_eve {
363        Some(&mut obj.runtime.prop_event_lists.z_rep)
364    } else if ids.obj_tr_rep_eve != 0 && op == ids.obj_tr_rep_eve {
365        Some(&mut obj.runtime.prop_event_lists.tr_rep)
366    } else if ids.obj_x_rep != 0 && op == ids.obj_x_rep {
367        Some(&mut obj.runtime.prop_event_lists.x_rep)
368    } else if ids.obj_y_rep != 0 && op == ids.obj_y_rep {
369        Some(&mut obj.runtime.prop_event_lists.y_rep)
370    } else if ids.obj_z_rep != 0 && op == ids.obj_z_rep {
371        Some(&mut obj.runtime.prop_event_lists.z_rep)
372    } else if ids.obj_tr_rep != 0 && op == ids.obj_tr_rep {
373        Some(&mut obj.runtime.prop_event_lists.tr_rep)
374    } else {
375        None
376    }
377}
378
379fn object_active_in_stage_state_by_runtime_slot(
380    st: &StageFormState,
381    stage_idx: i64,
382    runtime_slot: usize,
383) -> Option<&ObjectState> {
384    if let Some(obj) = st
385        .object_lists
386        .get(&stage_idx)
387        .and_then(|list| find_object_by_runtime_slot(list, runtime_slot))
388    {
389        return Some(obj);
390    }
391
392    if let Some(items) = st.btnselitem_lists.get(&stage_idx) {
393        for item in items {
394            if let Some(obj) = find_object_by_runtime_slot(&item.object_list, runtime_slot) {
395                return Some(obj);
396            }
397        }
398    }
399
400    if let Some(mwnds) = st.mwnd_lists.get(&stage_idx) {
401        for mwnd in mwnds {
402            if let Some(obj) = find_object_by_runtime_slot(&mwnd.button_list, runtime_slot) {
403                return Some(obj);
404            }
405            if let Some(obj) = find_object_by_runtime_slot(&mwnd.face_list, runtime_slot) {
406                return Some(obj);
407            }
408            if let Some(obj) = find_object_by_runtime_slot(&mwnd.object_list, runtime_slot) {
409                return Some(obj);
410            }
411        }
412    }
413
414    None
415}
416
417fn object_active_by_runtime_slot(
418    globals: &GlobalState,
419    stage_form_id: u32,
420    stage_idx: i64,
421    runtime_slot: usize,
422) -> Option<&ObjectState> {
423    globals
424        .stage_forms
425        .get(&stage_form_id)
426        .and_then(|st| object_active_in_stage_state_by_runtime_slot(st, stage_idx, runtime_slot))
427}
428
429fn find_object_by_runtime_slot_mut_ptr(
430    objects: &mut [ObjectState],
431    runtime_slot: usize,
432) -> Option<*mut ObjectState> {
433    find_object_by_runtime_slot_mut(objects, runtime_slot).map(|obj| obj as *mut ObjectState)
434}
435
436fn object_active_by_runtime_slot_mut(
437    globals: &mut GlobalState,
438    stage_form_id: u32,
439    stage_idx: i64,
440    runtime_slot: usize,
441) -> Option<&mut ObjectState> {
442    let st = globals.stage_forms.get_mut(&stage_form_id)?;
443
444    if let Some(ptr) = st
445        .object_lists
446        .get_mut(&stage_idx)
447        .and_then(|list| find_object_by_runtime_slot_mut_ptr(list, runtime_slot))
448    {
449        return unsafe { Some(&mut *ptr) };
450    }
451
452    if let Some(items) = st.btnselitem_lists.get_mut(&stage_idx) {
453        for item in items {
454            if let Some(ptr) =
455                find_object_by_runtime_slot_mut_ptr(&mut item.object_list, runtime_slot)
456            {
457                return unsafe { Some(&mut *ptr) };
458            }
459        }
460    }
461
462    if let Some(mwnds) = st.mwnd_lists.get_mut(&stage_idx) {
463        for mwnd in mwnds {
464            if let Some(ptr) =
465                find_object_by_runtime_slot_mut_ptr(&mut mwnd.button_list, runtime_slot)
466            {
467                return unsafe { Some(&mut *ptr) };
468            }
469            if let Some(ptr) =
470                find_object_by_runtime_slot_mut_ptr(&mut mwnd.face_list, runtime_slot)
471            {
472                return unsafe { Some(&mut *ptr) };
473            }
474            if let Some(ptr) =
475                find_object_by_runtime_slot_mut_ptr(&mut mwnd.object_list, runtime_slot)
476            {
477                return unsafe { Some(&mut *ptr) };
478            }
479        }
480    }
481
482    None
483}
484
485fn finish_wait_skipped_event(ev: &mut IntEvent) {
486    let before = if anim_skip_trace_enabled() {
487        Some(int_event_state(ev))
488    } else {
489        None
490    };
491    ev.end_event();
492    ev.frame();
493    if let Some(before) = before {
494        anim_skip_trace(format!(
495            "finish_event before=[{}] after=[{}]",
496            before,
497            int_event_state(ev)
498        ));
499    }
500}
501
502fn event_prop_pairs(ids: &RuntimeConstants) -> [(i32, i32); 36] {
503    [
504        (ids.obj_patno_eve, ids.obj_patno),
505        (ids.obj_x_eve, ids.obj_x),
506        (ids.obj_y_eve, ids.obj_y),
507        (ids.obj_z_eve, ids.obj_z),
508        (ids.obj_center_x_eve, ids.obj_center_x),
509        (ids.obj_center_y_eve, ids.obj_center_y),
510        (ids.obj_center_z_eve, ids.obj_center_z),
511        (ids.obj_center_rep_x_eve, ids.obj_center_rep_x),
512        (ids.obj_center_rep_y_eve, ids.obj_center_rep_y),
513        (ids.obj_center_rep_z_eve, ids.obj_center_rep_z),
514        (ids.obj_scale_x_eve, ids.obj_scale_x),
515        (ids.obj_scale_y_eve, ids.obj_scale_y),
516        (ids.obj_scale_z_eve, ids.obj_scale_z),
517        (ids.obj_rotate_x_eve, ids.obj_rotate_x),
518        (ids.obj_rotate_y_eve, ids.obj_rotate_y),
519        (ids.obj_rotate_z_eve, ids.obj_rotate_z),
520        (ids.obj_clip_left_eve, ids.obj_clip_left),
521        (ids.obj_clip_top_eve, ids.obj_clip_top),
522        (ids.obj_clip_right_eve, ids.obj_clip_right),
523        (ids.obj_clip_bottom_eve, ids.obj_clip_bottom),
524        (ids.obj_src_clip_left_eve, ids.obj_src_clip_left),
525        (ids.obj_src_clip_top_eve, ids.obj_src_clip_top),
526        (ids.obj_src_clip_right_eve, ids.obj_src_clip_right),
527        (ids.obj_src_clip_bottom_eve, ids.obj_src_clip_bottom),
528        (ids.obj_tr_eve, ids.obj_tr),
529        (ids.obj_mono_eve, ids.obj_mono),
530        (ids.obj_reverse_eve, ids.obj_reverse),
531        (ids.obj_bright_eve, ids.obj_bright),
532        (ids.obj_dark_eve, ids.obj_dark),
533        (ids.obj_color_r_eve, ids.obj_color_r),
534        (ids.obj_color_g_eve, ids.obj_color_g),
535        (ids.obj_color_b_eve, ids.obj_color_b),
536        (ids.obj_color_rate_eve, ids.obj_color_rate),
537        (ids.obj_color_add_r_eve, ids.obj_color_add_r),
538        (ids.obj_color_add_g_eve, ids.obj_color_add_g),
539        (ids.obj_color_add_b_eve, ids.obj_color_add_b),
540    ]
541}
542
543fn object_prop_op_for_event_op(ids: &RuntimeConstants, event_op: i32) -> Option<i32> {
544    event_prop_pairs(ids)
545        .into_iter()
546        .find_map(|(ev_op, prop_op)| (ev_op != 0 && event_op == ev_op).then_some(prop_op))
547}
548
549fn finish_wait_skipped_object_event_by_op(
550    obj: &mut ObjectState,
551    ids: &RuntimeConstants,
552    event_op: i32,
553) {
554    let file = obj.file_name.as_deref().unwrap_or("-").to_string();
555    let runtime_slot = obj.runtime_slot_or(usize::MAX);
556    let event_name = object_event_op_name(ids, event_op);
557    let Some(value) = obj.int_event_by_op_mut(ids, event_op).map(|ev| {
558        anim_skip_trace(format!(
559            "finish_object_event begin slot={} file={} op={}({}) state=[{}]",
560            runtime_slot,
561            file,
562            event_op,
563            event_name,
564            int_event_state(ev)
565        ));
566        finish_wait_skipped_event(ev);
567        anim_skip_trace(format!(
568            "finish_object_event event_done slot={} file={} op={}({}) state=[{}]",
569            runtime_slot,
570            file,
571            event_op,
572            event_name,
573            int_event_state(ev)
574        ));
575        ev.get_total_value() as i64
576    }) else {
577        anim_skip_trace(format!(
578            "finish_object_event missing slot={} file={} op={}({})",
579            runtime_slot, file, event_op, event_name
580        ));
581        return;
582    };
583    if let Some(prop_op) = object_prop_op_for_event_op(ids, event_op) {
584        obj.set_int_prop(ids, prop_op, value);
585        anim_skip_trace(format!(
586            "finish_object_event prop_write slot={} file={} event_op={}({}) prop_op={} value={} obj_tr={} obj_alpha={} obj_pos=({}, {})",
587            runtime_slot,
588            file,
589            event_op,
590            event_name,
591            prop_op,
592            value,
593            obj.get_int_prop(ids, ids.obj_tr),
594            obj.base.alpha,
595            obj.get_int_prop(ids, ids.obj_x),
596            obj.get_int_prop(ids, ids.obj_y),
597        ));
598    } else {
599        anim_skip_trace(format!(
600            "finish_object_event no_prop_map slot={} file={} event_op={}({}) value={}",
601            runtime_slot, file, event_op, event_name, value
602        ));
603    }
604}
605
606fn finish_wait_skipped_object_events(obj: &mut ObjectState, ids: &RuntimeConstants) {
607    let file = obj.file_name.as_deref().unwrap_or("-").to_string();
608    let runtime_slot = obj.runtime_slot_or(usize::MAX);
609    anim_skip_trace(format!(
610        "finish_object_all begin slot={} file={} any_active={} tr={} alpha={} pos=({}, {})",
611        runtime_slot,
612        file,
613        obj.any_event_active(),
614        obj.get_int_prop(ids, ids.obj_tr),
615        obj.base.alpha,
616        obj.get_int_prop(ids, ids.obj_x),
617        obj.get_int_prop(ids, ids.obj_y),
618    ));
619    let mut final_values = Vec::new();
620    for (event_op, prop_op) in event_prop_pairs(ids) {
621        if event_op == 0 || prop_op == 0 {
622            continue;
623        }
624        if let Some(ev) = obj.int_event_by_op_mut(ids, event_op) {
625            if ev.check_event() {
626                anim_skip_trace(format!(
627                    "finish_object_all active slot={} file={} op={}({}) state=[{}]",
628                    runtime_slot,
629                    file,
630                    event_op,
631                    object_event_op_name(ids, event_op),
632                    int_event_state(ev)
633                ));
634            }
635            finish_wait_skipped_event(ev);
636            final_values.push((event_op, prop_op, ev.get_total_value() as i64));
637        }
638    }
639    obj.runtime.prop_event_lists.end_all();
640    obj.runtime.prop_event_lists.frame();
641    for (event_op, prop_op, value) in final_values {
642        obj.set_int_prop(ids, prop_op, value);
643        anim_skip_trace(format!(
644            "finish_object_all prop_write slot={} file={} event_op={}({}) prop_op={} value={}",
645            runtime_slot,
646            file,
647            event_op,
648            object_event_op_name(ids, event_op),
649            prop_op,
650            value
651        ));
652    }
653    anim_skip_trace(format!(
654        "finish_object_all end slot={} file={} any_active={} tr={} alpha={} pos=({}, {})",
655        runtime_slot,
656        file,
657        obj.any_event_active(),
658        obj.get_int_prop(ids, ids.obj_tr),
659        obj.base.alpha,
660        obj.get_int_prop(ids, ids.obj_x),
661        obj.get_int_prop(ids, ids.obj_y),
662    ));
663}
664
665fn finish_event_wait_by_key(w: &EventWait, globals: &mut GlobalState, ids: &RuntimeConstants) {
666    match w {
667        EventWait::ObjectAll {
668            stage_form_id,
669            stage_idx,
670            runtime_slot,
671        } => {
672            if let Some(obj) = object_active_by_runtime_slot_mut(
673                globals,
674                *stage_form_id,
675                *stage_idx,
676                *runtime_slot,
677            ) {
678                finish_wait_skipped_object_events(obj, ids);
679            }
680        }
681        EventWait::ObjectOne {
682            stage_form_id,
683            stage_idx,
684            runtime_slot,
685            op,
686        } => {
687            if let Some(obj) = object_active_by_runtime_slot_mut(
688                globals,
689                *stage_form_id,
690                *stage_idx,
691                *runtime_slot,
692            ) {
693                finish_wait_skipped_object_event_by_op(obj, ids, *op);
694            }
695        }
696        EventWait::ObjectList {
697            stage_form_id,
698            stage_idx,
699            runtime_slot,
700            list_op,
701            list_idx,
702        } => {
703            if let Some(obj) = object_active_by_runtime_slot_mut(
704                globals,
705                *stage_form_id,
706                *stage_idx,
707                *runtime_slot,
708            ) {
709                let file = obj.file_name.as_deref().unwrap_or("-").to_string();
710                if let Some(ev) = object_event_list_for_wait_mut(obj, ids, *list_op)
711                    .and_then(|v| v.get_mut(*list_idx))
712                {
713                    anim_skip_trace(format!(
714                        "finish_object_list_event begin stage_form={} stage={} slot={} file={} list_op={}({}) list_idx={} state=[{}]",
715                        stage_form_id,
716                        stage_idx,
717                        runtime_slot,
718                        file,
719                        list_op,
720                        object_event_op_name(ids, *list_op),
721                        list_idx,
722                        int_event_state(ev)
723                    ));
724                    finish_wait_skipped_event(ev);
725                    anim_skip_trace(format!(
726                        "finish_object_list_event end stage_form={} stage={} slot={} file={} list_op={}({}) list_idx={} state=[{}]",
727                        stage_form_id,
728                        stage_idx,
729                        runtime_slot,
730                        file,
731                        list_op,
732                        object_event_op_name(ids, *list_op),
733                        list_idx,
734                        int_event_state(ev)
735                    ));
736                } else {
737                    anim_skip_trace(format!(
738                        "finish_object_list_event missing stage_form={} stage={} slot={} file={} list_op={}({}) list_idx={}",
739                        stage_form_id,
740                        stage_idx,
741                        runtime_slot,
742                        file,
743                        list_op,
744                        object_event_op_name(ids, *list_op),
745                        list_idx
746                    ));
747                }
748            }
749        }
750        EventWait::GenericIntEvent { form_id, index } => match index {
751            Some(i) => {
752                if let Some(ev) = globals
753                    .int_event_lists
754                    .get_mut(form_id)
755                    .and_then(|v| v.get_mut(*i))
756                {
757                    anim_skip_trace(format!(
758                        "finish_generic_int_event begin form_id={} index={} state=[{}]",
759                        form_id,
760                        i,
761                        int_event_state(ev)
762                    ));
763                    finish_wait_skipped_event(ev);
764                    anim_skip_trace(format!(
765                        "finish_generic_int_event end form_id={} index={} state=[{}]",
766                        form_id,
767                        i,
768                        int_event_state(ev)
769                    ));
770                }
771            }
772            None => {
773                if let Some(ev) = globals.int_event_roots.get_mut(form_id) {
774                    anim_skip_trace(format!(
775                        "finish_generic_int_event begin form_id={} index=None state=[{}]",
776                        form_id,
777                        int_event_state(ev)
778                    ));
779                    finish_wait_skipped_event(ev);
780                    anim_skip_trace(format!(
781                        "finish_generic_int_event end form_id={} index=None state=[{}]",
782                        form_id,
783                        int_event_state(ev)
784                    ));
785                }
786            }
787        },
788        EventWait::ScreenEffect { form_id, index, op } => {
789            if let Some(ev) = globals
790                .screen_forms
791                .get_mut(form_id)
792                .and_then(|screen| screen.effect_list.get_mut(*index))
793                .and_then(|effect| effect.int_event_by_op_mut(ids, *op))
794            {
795                finish_wait_skipped_event(ev);
796            }
797        }
798        EventWait::StageEffect {
799            stage_form_id,
800            stage_idx,
801            index,
802            op,
803        } => {
804            if let Some(ev) = globals
805                .stage_forms
806                .get_mut(stage_form_id)
807                .and_then(|stage| stage.effect_lists.get_mut(stage_idx))
808                .and_then(|effects| effects.get_mut(*index))
809                .and_then(|effect| effect.int_event_by_op_mut(ids, *op))
810            {
811                finish_wait_skipped_event(ev);
812            }
813        }
814        EventWait::Mask { form_id, index, op } => {
815            if let Some(mask) = globals
816                .mask_lists
817                .get_mut(form_id)
818                .and_then(|list| list.masks.get_mut(*index))
819            {
820                let ev = if *op == super::constants::elm_value::MASK_X_EVE {
821                    Some(&mut mask.x_event)
822                } else if *op == super::constants::elm_value::MASK_Y_EVE {
823                    Some(&mut mask.y_event)
824                } else {
825                    None
826                };
827                if let Some(ev) = ev {
828                    finish_wait_skipped_event(ev);
829                }
830            }
831        }
832        EventWait::FogX => {
833            finish_wait_skipped_event(&mut globals.fog_global.x_event);
834            globals.fog_global.scroll_x = globals.fog_global.x_event.get_total_value() as f32;
835        }
836        EventWait::CounterThreshold { .. } => {}
837        // C++ PCMEVENT_WAIT_KEY only releases the waiting process. It does not
838        // stop or fast-forward the sound event itself.
839        EventWait::PcmEvent { .. } => {}
840    }
841}
842
843#[derive(Debug, Default, Clone)]
844pub struct VmWait {
845    pub until: Option<Instant>,
846    pub until_frame: Option<u64>,
847    /// True only when `until` represents MWND OPEN/CLOSE animation wait.
848    mwnd_animation_wait: bool,
849    pub waiting_for_key: bool,
850    /// TNM_PROC_TYPE_SEL_BTN. Unlike KEY_WAIT, a button selection is released
851    /// only by C_elm_btn_select::is_processing() becoming false for its
852    /// configured sync_type. Ordinary key/message wait notifications must not
853    /// pop this process.
854    selbtn: bool,
855    /// OBJBTNGROUP.SEL completes only when its button group decides or cancels.
856    /// An unrelated click must not supply a default selection result.
857    group_selection: Option<(u32, i64, usize)>,
858    /// TNM_PROC_TYPE_KEY_WAIT created by KEYLIST.WAIT/WAIT_FORCE.
859    generic_key_wait: bool,
860    generic_key_wait_skip_disabled: bool,
861    /// TNM_PROC_TYPE_MESSAGE_WAIT: block only until the typewriter has
862    /// revealed the complete message.  This is deliberately distinct from
863    /// MESSAGE_KEY_WAIT, which waits for user input after reveal.
864    pub message_reveal: bool,
865    /// PP/R/PAGE push MESSAGE_KEY_WAIT below MESSAGE_WAIT. It must not become
866    /// active until MESSAGE_WAIT has popped.
867    message_key_after_reveal: bool,
868    message_key_wait: bool,
869    /// If set, a key press cancels the current time wait (TIMEWAIT_KEY behavior).
870    skip_time_on_key: bool,
871
872    pub audio: Option<AudioWait>,
873    /// C_tnm_proc::key_skip_enable_flag for audio waits. Audio WAIT_KEY is
874    /// still an audio proc; it must not be represented by the generic
875    /// MESSAGE/INPUT `waiting_for_key` bit. The original flow proc consumes
876    /// only VK_EX_DECIDE down-up and leaves the sound playing.
877    audio_key_skip: bool,
878    audio_return_value: bool,
879
880    pub event: Option<EventWait>,
881    event_key_skip: bool,
882    event_return_value: bool,
883
884    pub movie: Option<MovieWait>,
885    movie_key_skip: bool,
886
887    emote: Option<EmoteWait>,
888    emote_key_skip: bool,
889
890    global_movie: bool,
891    global_movie_key_skip: bool,
892    global_movie_return_value: bool,
893
894    movie_skip_info: Option<MovieWait>,
895
896    pub quake: Option<QuakeWait>,
897    quake_key_skip: bool,
898    pub pending_value: Option<Value>,
899
900    /// Blocks VM execution until a runtime modal UI supplies a return value.
901    pub system_modal: bool,
902
903    pub wipe: bool,
904    wipe_key_skip: bool,
905    wipe_return_value: bool,
906
907    block_generation: u64,
908}
909
910impl VmWait {
911    pub fn block_generation(&self) -> u64 {
912        self.block_generation
913    }
914
915    pub fn needs_runtime_poll(&self) -> bool {
916        self.message_reveal
917            || self.message_key_wait
918            || self.generic_key_wait
919            || self.selbtn
920            || self.until.is_some()
921            || self.until_frame.is_some()
922            || self.audio.is_some()
923            || self.event.is_some()
924            || self.movie.is_some()
925            || self.emote.is_some()
926            || self.quake.is_some()
927            || self.global_movie
928            || self.wipe
929    }
930
931    /// Whether wall-clock or frame-driven state can make this wait finish.
932    ///
933    /// Pure key waits still need to be checked after an input event, but they
934    /// must not keep the render loop running while the player is idle.
935    pub fn needs_continuous_frame(&self) -> bool {
936        self.message_reveal
937            || self.until.is_some()
938            || self.until_frame.is_some()
939            || self.audio.is_some()
940            || self.event.is_some()
941            || self.movie.is_some()
942            || self.emote.is_some()
943            || self.quake.is_some()
944            || self.global_movie
945            || self.wipe
946    }
947
948    fn mark_block_request(&mut self) {
949        self.block_generation = self.block_generation.wrapping_add(1);
950    }
951
952    pub fn poll(
953        &mut self,
954        stack: &mut Vec<Value>,
955        bgm: &mut BgmEngine,
956        koe: &mut KoeEngine,
957        se: &mut SeEngine,
958        pcm: &mut PcmEngine,
959        globals: &mut GlobalState,
960        ids: &RuntimeConstants,
961        skipping: bool,
962    ) -> bool {
963        let blocked = self.is_blocked(bgm, koe, se, pcm, globals, ids, skipping);
964        if !blocked && let Some(v) = self.pending_value.take() {
965            stack.push(v);
966        }
967        blocked
968    }
969
970    pub fn is_blocked(
971        &mut self,
972        bgm: &mut BgmEngine,
973        koe: &mut KoeEngine,
974        se: &mut SeEngine,
975        pcm: &mut PcmEngine,
976        globals: &mut GlobalState,
977        ids: &RuntimeConstants,
978        skipping: bool,
979    ) -> bool {
980        // TNM_PROC_TYPE_SEL_BTN is not a key wait. flow_proc.cpp
981        // tnm_sel_btn_proc() keeps the process on the stack until
982        // C_elm_btn_select::is_processing() becomes false for sync_type.
983        if self.selbtn {
984            let processing = match globals.selbtn.sync_type {
985                0 => globals.selbtn.processing_flag_0,
986                1 => globals.selbtn.processing_flag_1,
987                2 => globals.selbtn.processing_flag_2,
988                _ => false,
989            };
990            if !processing {
991                self.selbtn = false;
992            }
993        }
994
995        // Auto-clear time waits when the deadline is reached.
996        if let Some(t) = self.until
997            && Instant::now() >= t
998        {
999            let key_skippable_timewait = self.skip_time_on_key;
1000            self.until = None;
1001            self.skip_time_on_key = false;
1002            self.mwnd_animation_wait = false;
1003            if key_skippable_timewait {
1004                anim_skip_trace("timewait_key naturally finished pending=0");
1005                self.pending_value = Some(Value::Int(0));
1006            }
1007        }
1008
1009        // C++ TIMEWAIT/TIMEWAIT_KEY are released immediately by global skip
1010        // unless the current proc has skip_disable_flag. TIMEWAIT_KEY returns
1011        // the same 0 value as a natural timeout.
1012        if skipping && self.until.is_some() {
1013            let return_value = self.skip_time_on_key;
1014            self.until = None;
1015            self.skip_time_on_key = false;
1016            self.mwnd_animation_wait = false;
1017            if return_value {
1018                self.pending_value = Some(Value::Int(0));
1019            }
1020        }
1021
1022        if let Some(frame) = self.until_frame
1023            && globals.render_frame >= frame
1024        {
1025            self.until_frame = None;
1026        }
1027
1028        // KEYLIST.WAIT is TNM_PROC_TYPE_KEY_WAIT and obeys global skip.
1029        // WAIT_FORCE sets C_tnm_proc::skip_disable_flag, so it remains blocked.
1030        if skipping && self.generic_key_wait && !self.generic_key_wait_skip_disabled {
1031            self.generic_key_wait = false;
1032            self.generic_key_wait_skip_disabled = false;
1033            self.waiting_for_key = false;
1034        }
1035
1036        // Auto-clear audio waits when the predicate is satisfied.
1037        if let Some(w) = self.audio {
1038            let done = match w {
1039                AudioWait::Bgm => !bgm.is_playing(),
1040                AudioWait::BgmFade => !bgm.is_fade_out_doing(),
1041                AudioWait::KoeAny => !koe.is_playing_any(),
1042                AudioWait::SeAny => !se.is_playing_any(),
1043                AudioWait::PcmAny => !pcm.is_playing_any(),
1044                AudioWait::PcmSlot(s) => !pcm.is_playing_slot(s as usize),
1045                AudioWait::PcmSlotFade(s) => !pcm.is_fading_slot(s as usize),
1046            };
1047            if done {
1048                self.audio = None;
1049                self.audio_key_skip = false;
1050                if self.audio_return_value {
1051                    self.pending_value = Some(Value::Int(0));
1052                }
1053                self.audio_return_value = false;
1054            }
1055        }
1056
1057        // All original audio flow procs (BGM/KOE/PCM/PCMCH) test skipping
1058        // after natural completion. Skip releases only the proc; the player
1059        // keeps running. The returned value is the normal-completion value 0.
1060        if skipping && self.audio.is_some() {
1061            self.audio = None;
1062            self.audio_key_skip = false;
1063            if self.audio_return_value {
1064                self.pending_value = Some(Value::Int(0));
1065            }
1066            self.audio_return_value = false;
1067        }
1068
1069        // Auto-clear event waits when the predicate is satisfied.
1070        let event_done = if let Some(w) = self.event.as_ref() {
1071            match w {
1072                EventWait::ObjectAll {
1073                    stage_form_id,
1074                    stage_idx,
1075                    runtime_slot,
1076                } => object_active_by_runtime_slot(
1077                    globals,
1078                    *stage_form_id,
1079                    *stage_idx,
1080                    *runtime_slot,
1081                )
1082                .map(|obj| !obj.any_event_active())
1083                .unwrap_or(true),
1084                EventWait::ObjectOne {
1085                    stage_form_id,
1086                    stage_idx,
1087                    runtime_slot,
1088                    op,
1089                } => object_active_by_runtime_slot(
1090                    globals,
1091                    *stage_form_id,
1092                    *stage_idx,
1093                    *runtime_slot,
1094                )
1095                .map(|obj| {
1096                    !obj.int_event_by_op(ids, *op)
1097                        .map(|e| e.check_event())
1098                        .unwrap_or(false)
1099                })
1100                .unwrap_or(true),
1101                EventWait::ObjectList {
1102                    stage_form_id,
1103                    stage_idx,
1104                    runtime_slot,
1105                    list_op,
1106                    list_idx,
1107                } => object_active_by_runtime_slot(
1108                    globals,
1109                    *stage_form_id,
1110                    *stage_idx,
1111                    *runtime_slot,
1112                )
1113                .map(|obj| {
1114                    let active = object_event_list_for_wait(obj, ids, *list_op)
1115                        .and_then(|v| v.get(*list_idx))
1116                        .map(|e| e.check_event())
1117                        .unwrap_or(false);
1118                    !active
1119                })
1120                .unwrap_or(true),
1121                EventWait::GenericIntEvent { form_id, index } => match index {
1122                    Some(i) => globals
1123                        .int_event_lists
1124                        .get(form_id)
1125                        .and_then(|v| v.get(*i))
1126                        .map(|e| !e.check_event())
1127                        .unwrap_or(true),
1128                    None => globals
1129                        .int_event_roots
1130                        .get(form_id)
1131                        .map(|e| !e.check_event())
1132                        .unwrap_or(true),
1133                },
1134                EventWait::ScreenEffect { form_id, index, op } => globals
1135                    .screen_forms
1136                    .get(form_id)
1137                    .and_then(|screen| screen.effect_list.get(*index))
1138                    .and_then(|effect| effect.int_event_by_op(ids, *op))
1139                    .map(|event| !event.check_event())
1140                    .unwrap_or(true),
1141                EventWait::StageEffect {
1142                    stage_form_id,
1143                    stage_idx,
1144                    index,
1145                    op,
1146                } => globals
1147                    .stage_forms
1148                    .get(stage_form_id)
1149                    .and_then(|stage| stage.effect_lists.get(stage_idx))
1150                    .and_then(|effects| effects.get(*index))
1151                    .and_then(|effect| effect.int_event_by_op(ids, *op))
1152                    .map(|event| !event.check_event())
1153                    .unwrap_or(true),
1154                EventWait::Mask { form_id, index, op } => globals
1155                    .mask_lists
1156                    .get(form_id)
1157                    .and_then(|list| list.masks.get(*index))
1158                    .and_then(|mask| {
1159                        if *op == super::constants::elm_value::MASK_X_EVE {
1160                            Some(&mask.x_event)
1161                        } else if *op == super::constants::elm_value::MASK_Y_EVE {
1162                            Some(&mask.y_event)
1163                        } else {
1164                            None
1165                        }
1166                    })
1167                    .map(|event| !event.check_event())
1168                    .unwrap_or(true),
1169                EventWait::FogX => !globals.fog_global.x_event.check_event(),
1170                EventWait::CounterThreshold {
1171                    form_id,
1172                    index,
1173                    target,
1174                } => globals
1175                    .counter_lists
1176                    .get(form_id)
1177                    .and_then(|v| v.get(*index))
1178                    .map(|c| c.get_count() - *target >= 0)
1179                    .unwrap_or(true),
1180                EventWait::PcmEvent { form_id, index } => globals
1181                    .pcm_event_lists
1182                    .get(form_id)
1183                    .and_then(|v| v.get(*index))
1184                    .map(|event| !event.is_active())
1185                    .unwrap_or(true),
1186            }
1187        } else {
1188            false
1189        };
1190        if event_done {
1191            let was_event_key_skip = self.event_key_skip;
1192            anim_skip_trace(format!(
1193                "event_wait naturally finished event={:?} key_skip={} return_value={}",
1194                self.event.as_ref(),
1195                was_event_key_skip,
1196                self.event_return_value
1197            ));
1198            self.event = None;
1199            self.event_key_skip = false;
1200            if was_event_key_skip {
1201                self.waiting_for_key = false;
1202            }
1203            if self.event_return_value {
1204                self.pending_value = Some(Value::Int(0));
1205            }
1206            self.event_return_value = false;
1207        }
1208
1209        // COUNTER.WAIT/WAIT_KEY is represented by CounterThreshold. Unlike
1210        // general INTEVENT waits, the original counter flow proc releases on
1211        // global skip and WAIT_KEY returns 0.
1212        if skipping && matches!(self.event, Some(EventWait::CounterThreshold { .. })) {
1213            self.event = None;
1214            self.event_key_skip = false;
1215            if self.event_return_value {
1216                self.pending_value = Some(Value::Int(0));
1217            }
1218            self.event_return_value = false;
1219            self.waiting_for_key = false;
1220        }
1221
1222        if self
1223            .quake
1224            .map(|wait| !quake_wait_active(globals, wait))
1225            .unwrap_or(false)
1226        {
1227            self.quake = None;
1228            self.quake_key_skip = false;
1229            self.waiting_for_key = false;
1230        }
1231        if skipping && let Some(wait) = self.quake.take() {
1232            stop_waited_quake(globals, wait);
1233            self.quake_key_skip = false;
1234            self.waiting_for_key = false;
1235        }
1236
1237        // Auto-clear GLOBAL.MOV waits when playback ends.
1238        if self.global_movie && !globals.mov.playing {
1239            if self.global_movie_return_value {
1240                self.pending_value = Some(Value::Int(0));
1241            }
1242            self.global_movie = false;
1243            self.global_movie_key_skip = false;
1244            self.global_movie_return_value = false;
1245        }
1246
1247        // Auto-clear OBJECT movie waits when playback ends.
1248        if let Some(w) = self.movie {
1249            let done = object_active_by_runtime_slot(
1250                globals,
1251                w.stage_form_id,
1252                w.stage_idx,
1253                w.runtime_slot,
1254            )
1255            .map(|obj| !obj.movie.check_movie())
1256            .unwrap_or(true);
1257
1258            if done {
1259                if w.return_value_flag {
1260                    self.pending_value = Some(Value::Int(0));
1261                }
1262                self.movie = None;
1263                self.movie_key_skip = false;
1264            }
1265        }
1266
1267        // C++ TNM_PROC_TYPE_OBJ_EMOTE_WAIT completes when IsAnimating() becomes false.
1268        if let Some(w) = self.emote {
1269            let done = object_active_by_runtime_slot(
1270                globals,
1271                w.stage_form_id,
1272                w.stage_idx,
1273                w.runtime_slot,
1274            )
1275            .map(|obj| !obj.emote.is_animating())
1276            .unwrap_or(true);
1277
1278            if done {
1279                let was_key_skip = self.emote_key_skip;
1280                if w.return_value_flag {
1281                    self.pending_value = Some(Value::Int(0));
1282                }
1283                self.emote = None;
1284                self.emote_key_skip = false;
1285                if was_key_skip {
1286                    self.waiting_for_key = false;
1287                }
1288            }
1289        }
1290
1291        // Auto-clear wipe waits when the wipe is finished.
1292        if self.wipe && globals.wipe_done() {
1293            self.wipe = false;
1294            if self.wipe_return_value {
1295                self.pending_value = Some(Value::Int(0));
1296            }
1297            self.wipe_return_value = false;
1298            if self.wipe_key_skip {
1299                self.wipe_key_skip = false;
1300                if self.waiting_for_key {
1301                    self.waiting_for_key = false;
1302                }
1303            }
1304        }
1305
1306        if let Some((form_id, stage_idx, group_idx)) = self.group_selection {
1307            let waiting = globals
1308                .stage_forms
1309                .get(&form_id)
1310                .and_then(|st| st.group_lists.get(&stage_idx))
1311                .and_then(|groups| groups.get(group_idx))
1312                .map(|group| group.wait_flag && group.started)
1313                .unwrap_or(false);
1314            if !waiting {
1315                self.group_selection = None;
1316            }
1317        }
1318
1319        self.selbtn
1320            || self.group_selection.is_some()
1321            || self.waiting_for_key
1322            || self.message_reveal
1323            || self.until.is_some()
1324            || self.until_frame.is_some()
1325            || self.audio.is_some()
1326            || self.event.is_some()
1327            || self.movie.is_some()
1328            || self.emote.is_some()
1329            || self.quake.is_some()
1330            || self.global_movie
1331            || self.system_modal
1332            || self.wipe
1333    }
1334
1335    pub fn wait_system_modal(&mut self) {
1336        self.mark_block_request();
1337        self.system_modal = true;
1338    }
1339
1340    pub fn finish_system_modal(&mut self, value: Value) {
1341        if self.system_modal {
1342            self.system_modal = false;
1343            self.pending_value = Some(value);
1344        }
1345    }
1346
1347    pub fn finish_system_modal_void(&mut self) {
1348        if self.system_modal {
1349            self.system_modal = false;
1350            self.pending_value = None;
1351        }
1352    }
1353
1354    pub fn system_modal_active(&self) -> bool {
1355        self.system_modal
1356    }
1357
1358    pub fn wait_ms(&mut self, ms: u64) {
1359        if ms == 0 {
1360            return;
1361        }
1362        self.mark_block_request();
1363        self.until = Some(Instant::now() + Duration::from_millis(ms));
1364        self.skip_time_on_key = false;
1365        self.mwnd_animation_wait = false;
1366    }
1367
1368    pub fn wait_mwnd_animation(&mut self, ms: u64) {
1369        if ms == 0 {
1370            return;
1371        }
1372        self.mark_block_request();
1373        self.until = Some(Instant::now() + Duration::from_millis(ms));
1374        self.skip_time_on_key = false;
1375        self.mwnd_animation_wait = true;
1376    }
1377
1378    pub fn mwnd_animation_waiting(&self) -> bool {
1379        self.mwnd_animation_wait && self.until.is_some()
1380    }
1381
1382    pub fn wait_next_frame(&mut self, current_frame: u64) {
1383        self.mark_block_request();
1384        self.until_frame = Some(current_frame.saturating_add(1));
1385        self.skip_time_on_key = false;
1386    }
1387
1388    /// Wait for a duration, but allow any key/mouse press to cancel the wait.
1389    pub fn wait_ms_key(&mut self, ms: u64) {
1390        if ms == 0 {
1391            anim_skip_trace("wait_ms_key ignored ms=0");
1392            return;
1393        }
1394        self.mark_block_request();
1395        self.until = Some(Instant::now() + Duration::from_millis(ms));
1396        self.skip_time_on_key = true;
1397        self.mwnd_animation_wait = false;
1398        anim_skip_trace(format!(
1399            "wait_ms_key start ms={} block_generation={}",
1400            ms, self.block_generation
1401        ));
1402    }
1403
1404    pub fn wait_selbtn(&mut self) {
1405        self.mark_block_request();
1406        self.selbtn = true;
1407    }
1408
1409    pub fn set_selbtn_result(&mut self, result: i64) {
1410        // C_elm_btn_select::decide() pushes the selected zero-based index before
1411        // clearing processing_flag_2. Keep the value pending while the dedicated
1412        // SEL_BTN proc is still blocking; poll() materializes it when the chosen
1413        // sync point releases.
1414        self.pending_value = Some(Value::Int(result));
1415    }
1416
1417    pub fn wait_key(&mut self) {
1418        self.mark_block_request();
1419        self.waiting_for_key = true;
1420    }
1421
1422    pub fn wait_group_selection(&mut self, form_id: u32, stage_idx: i64, group_idx: usize) {
1423        self.mark_block_request();
1424        self.group_selection = Some((form_id, stage_idx, group_idx));
1425    }
1426
1427    pub(crate) fn button_selection_waiting(&self) -> bool {
1428        self.selbtn || self.group_selection.is_some()
1429    }
1430
1431    pub fn wait_input_key(&mut self, skip_disabled: bool) {
1432        self.mark_block_request();
1433        self.waiting_for_key = true;
1434        self.generic_key_wait = true;
1435        self.generic_key_wait_skip_disabled = skip_disabled;
1436    }
1437
1438    pub fn waiting_for_key(&self) -> bool {
1439        self.waiting_for_key
1440    }
1441
1442    pub fn wait_message_reveal(&mut self) {
1443        self.mark_block_request();
1444        self.message_reveal = true;
1445        self.message_key_after_reveal = false;
1446    }
1447
1448    pub fn wait_message_reveal_then_key(&mut self) {
1449        self.mark_block_request();
1450        self.message_reveal = true;
1451        self.message_key_after_reveal = true;
1452        self.message_key_wait = false;
1453    }
1454
1455    pub fn message_reveal_waiting(&self) -> bool {
1456        self.message_reveal
1457    }
1458
1459    /// Pop MESSAGE_WAIT and expose the MESSAGE_KEY_WAIT that PP/R/PAGE had
1460    /// already pushed below it. Returns true when that second proc became active.
1461    pub fn finish_message_reveal(&mut self) -> bool {
1462        self.message_reveal = false;
1463        if self.message_key_after_reveal {
1464            self.message_key_after_reveal = false;
1465            self.message_key_wait = true;
1466            self.waiting_for_key = true;
1467            true
1468        } else {
1469            false
1470        }
1471    }
1472
1473    pub fn message_key_waiting(&self) -> bool {
1474        self.message_key_wait
1475    }
1476
1477    pub fn finish_message_key_wait(&mut self) {
1478        self.message_key_wait = false;
1479        self.waiting_for_key = false;
1480    }
1481
1482    pub fn wait_quake(&mut self, wait: QuakeWait, key_skip: bool) {
1483        self.mark_block_request();
1484        self.quake = Some(wait);
1485        self.quake_key_skip = key_skip;
1486        if key_skip {
1487            self.waiting_for_key = true;
1488        }
1489    }
1490
1491    pub fn wait_audio(&mut self, w: AudioWait, key: bool) {
1492        self.wait_audio_with_return(w, key, false);
1493    }
1494
1495    pub fn wait_audio_with_return(&mut self, w: AudioWait, key: bool, return_value_flag: bool) {
1496        self.mark_block_request();
1497        self.audio = Some(w);
1498        self.audio_key_skip = key;
1499        self.audio_return_value = return_value_flag;
1500    }
1501
1502    pub fn wait_object_all_events(
1503        &mut self,
1504        stage_form_id: u32,
1505        stage_idx: i64,
1506        runtime_slot: usize,
1507        key_skip: bool,
1508    ) {
1509        self.mark_block_request();
1510        self.event = Some(EventWait::ObjectAll {
1511            stage_form_id,
1512            stage_idx,
1513            runtime_slot,
1514        });
1515        anim_skip_trace(format!(
1516            "wait_object_all_events start stage_form={} stage={} slot={} key_skip={} block_generation={}",
1517            stage_form_id, stage_idx, runtime_slot, key_skip, self.block_generation
1518        ));
1519        self.event_key_skip = key_skip;
1520        self.event_return_value = false;
1521        if key_skip {
1522            self.waiting_for_key = true;
1523        }
1524    }
1525
1526    pub fn wait_object_event(
1527        &mut self,
1528        stage_form_id: u32,
1529        stage_idx: i64,
1530        runtime_slot: usize,
1531        op: i32,
1532        key_skip: bool,
1533        return_value_flag: bool,
1534    ) {
1535        self.mark_block_request();
1536        self.event = Some(EventWait::ObjectOne {
1537            stage_form_id,
1538            stage_idx,
1539            runtime_slot,
1540            op,
1541        });
1542        anim_skip_trace(format!(
1543            "wait_object_event start stage_form={} stage={} slot={} op={} key_skip={} return_value={} block_generation={}",
1544            stage_form_id,
1545            stage_idx,
1546            runtime_slot,
1547            op,
1548            key_skip,
1549            return_value_flag,
1550            self.block_generation
1551        ));
1552        self.event_key_skip = key_skip;
1553        self.event_return_value = return_value_flag;
1554        if key_skip {
1555            self.waiting_for_key = true;
1556        }
1557    }
1558
1559    pub fn wait_object_event_list(
1560        &mut self,
1561        stage_form_id: u32,
1562        stage_idx: i64,
1563        runtime_slot: usize,
1564        list_op: i32,
1565        list_idx: usize,
1566        key_skip: bool,
1567        return_value_flag: bool,
1568    ) {
1569        self.mark_block_request();
1570        self.event = Some(EventWait::ObjectList {
1571            stage_form_id,
1572            stage_idx,
1573            runtime_slot,
1574            list_op,
1575            list_idx,
1576        });
1577        anim_skip_trace(format!(
1578            "wait_object_event_list start stage_form={} stage={} slot={} list_op={} list_idx={} key_skip={} return_value={} block_generation={}",
1579            stage_form_id,
1580            stage_idx,
1581            runtime_slot,
1582            list_op,
1583            list_idx,
1584            key_skip,
1585            return_value_flag,
1586            self.block_generation
1587        ));
1588        self.event_key_skip = key_skip;
1589        self.event_return_value = return_value_flag;
1590        if key_skip {
1591            self.waiting_for_key = true;
1592        }
1593    }
1594
1595    pub fn wait_global_movie(&mut self, key_skip: bool, return_value_flag: bool) {
1596        self.mark_block_request();
1597        self.global_movie = true;
1598        self.global_movie_key_skip = key_skip;
1599        self.global_movie_return_value = return_value_flag;
1600    }
1601
1602    pub fn wait_object_movie(
1603        &mut self,
1604        stage_form_id: u32,
1605        stage_idx: i64,
1606        runtime_slot: usize,
1607        key_skip: bool,
1608        return_value_flag: bool,
1609    ) {
1610        self.mark_block_request();
1611        self.movie = Some(MovieWait {
1612            stage_form_id,
1613            stage_idx,
1614            runtime_slot,
1615            return_value_flag,
1616        });
1617        self.movie_key_skip = key_skip;
1618    }
1619
1620    pub fn wait_object_emote(
1621        &mut self,
1622        stage_form_id: u32,
1623        stage_idx: i64,
1624        runtime_slot: usize,
1625        key_skip: bool,
1626        return_value_flag: bool,
1627    ) {
1628        self.mark_block_request();
1629        self.emote = Some(EmoteWait {
1630            stage_form_id,
1631            stage_idx,
1632            runtime_slot,
1633            return_value_flag,
1634        });
1635        self.emote_key_skip = key_skip;
1636        if key_skip {
1637            self.waiting_for_key = true;
1638        }
1639    }
1640
1641    pub fn wait_generic_int_event(
1642        &mut self,
1643        form_id: u32,
1644        index: Option<usize>,
1645        key_skip: bool,
1646        return_value_flag: bool,
1647    ) {
1648        self.mark_block_request();
1649        self.event = Some(EventWait::GenericIntEvent { form_id, index });
1650        anim_skip_trace(format!(
1651            "wait_generic_int_event start form_id={} index={:?} key_skip={} return_value={} block_generation={}",
1652            form_id, index, key_skip, return_value_flag, self.block_generation
1653        ));
1654        self.event_key_skip = key_skip;
1655        self.event_return_value = return_value_flag;
1656        if key_skip {
1657            self.waiting_for_key = true;
1658        }
1659    }
1660
1661    pub fn wait_pcm_event(
1662        &mut self,
1663        form_id: u32,
1664        index: usize,
1665        key_skip: bool,
1666        return_value_flag: bool,
1667    ) {
1668        self.mark_block_request();
1669        self.event = Some(EventWait::PcmEvent { form_id, index });
1670        self.event_key_skip = key_skip;
1671        self.event_return_value = return_value_flag;
1672        if key_skip {
1673            self.waiting_for_key = true;
1674        }
1675    }
1676
1677    pub fn wait_screen_effect(
1678        &mut self,
1679        form_id: u32,
1680        index: usize,
1681        op: i32,
1682        key_skip: bool,
1683        return_value_flag: bool,
1684    ) {
1685        self.mark_block_request();
1686        self.event = Some(EventWait::ScreenEffect { form_id, index, op });
1687        self.event_key_skip = key_skip;
1688        self.event_return_value = return_value_flag;
1689        if key_skip {
1690            self.waiting_for_key = true;
1691        }
1692    }
1693
1694    pub fn wait_stage_effect(
1695        &mut self,
1696        stage_form_id: u32,
1697        stage_idx: i64,
1698        index: usize,
1699        op: i32,
1700        key_skip: bool,
1701        return_value_flag: bool,
1702    ) {
1703        self.mark_block_request();
1704        self.event = Some(EventWait::StageEffect {
1705            stage_form_id,
1706            stage_idx,
1707            index,
1708            op,
1709        });
1710        self.event_key_skip = key_skip;
1711        self.event_return_value = return_value_flag;
1712        if key_skip {
1713            self.waiting_for_key = true;
1714        }
1715    }
1716
1717    pub fn wait_mask_event(
1718        &mut self,
1719        form_id: u32,
1720        index: usize,
1721        op: i32,
1722        key_skip: bool,
1723        return_value_flag: bool,
1724    ) {
1725        self.mark_block_request();
1726        self.event = Some(EventWait::Mask { form_id, index, op });
1727        self.event_key_skip = key_skip;
1728        self.event_return_value = return_value_flag;
1729        if key_skip {
1730            self.waiting_for_key = true;
1731        }
1732    }
1733
1734    pub fn wait_fog_x_event(&mut self, key_skip: bool, return_value_flag: bool) {
1735        self.mark_block_request();
1736        self.event = Some(EventWait::FogX);
1737        self.event_key_skip = key_skip;
1738        self.event_return_value = return_value_flag;
1739        if key_skip {
1740            self.waiting_for_key = true;
1741        }
1742    }
1743
1744    pub fn wait_counter(
1745        &mut self,
1746        form_id: u32,
1747        index: usize,
1748        target: i64,
1749        key_skip: bool,
1750        return_value_flag: bool,
1751    ) {
1752        self.mark_block_request();
1753        self.event = Some(EventWait::CounterThreshold {
1754            form_id,
1755            index,
1756            target,
1757        });
1758        self.event_key_skip = key_skip;
1759        self.event_return_value = return_value_flag;
1760        if key_skip {
1761            self.waiting_for_key = true;
1762        }
1763    }
1764
1765    pub fn wait_wipe(&mut self, key_skip: bool) {
1766        self.wait_wipe_with_return(key_skip, false);
1767    }
1768
1769    pub fn wait_wipe_with_return(&mut self, key_skip: bool, return_value_flag: bool) {
1770        self.mark_block_request();
1771        self.wipe = true;
1772        self.wipe_key_skip = key_skip;
1773        self.wipe_return_value = return_value_flag;
1774        // C++ TNM_PROC_TYPE_WIPE_WAIT semantics (flow_proc.cpp
1775        // tnm_wipe_wait_proc): the wait releases when the wipe finishes by
1776        // time; a decide key press only *skips early*. It is never a hard
1777        // key wait, so waiting_for_key must stay untouched here.
1778    }
1779
1780    /// Notify the wait system that a key/mouse input happened.
1781    ///
1782    /// Returns true if the input is interpreted as a wipe-skip (used by WIPE/WAIT_WIPE).
1783    pub fn notify_key(&mut self, _globals: &mut GlobalState, _ids: &RuntimeConstants) -> bool {
1784        let wipe_skipped = self.wipe && self.wipe_key_skip;
1785        self.waiting_for_key = false;
1786        self.generic_key_wait = false;
1787        self.generic_key_wait_skip_disabled = false;
1788        // C++ TIMEWAIT_KEY, event WAIT_KEY, and MOV/OBJECT movie waits are
1789        // not skipped by arbitrary key-down/mouse-down input here. They
1790        // consume DECIDE/CANCEL down-up in notify_movie_down_up(). Audio
1791        // waits follow the same rule, but consume DECIDE only.
1792
1793        if wipe_skipped {
1794            self.wipe = false;
1795            self.wipe_key_skip = false;
1796            if self.wipe_return_value {
1797                self.pending_value = Some(Value::Int(1));
1798            }
1799            self.wipe_return_value = false;
1800        }
1801
1802        wipe_skipped
1803    }
1804
1805    /// Notify key-skippable waits that DECIDE/CANCEL completed a down-up pair.
1806    ///
1807    /// This matches C++ `tnm_time_wait_proc`, `tnm_mov_wait_proc`, and
1808    /// `tnm_obj_mov_wait_proc`: TIMEWAIT_KEY and MOV_WAIT_KEY consume only
1809    /// VK_EX_DECIDE or VK_EX_CANCEL down-up, returning 1 or -1 respectively.
1810    /// Generic key/mouse events must not skip these waits.
1811    pub fn notify_movie_down_up(
1812        &mut self,
1813        globals: &mut GlobalState,
1814        ids: &RuntimeConstants,
1815        result: i64,
1816    ) -> bool {
1817        let mut skipped = false;
1818        // C++ tnm_{bgm,koe,pcm,pcmch}_wait_proc consumes only a completed
1819        // VK_EX_DECIDE down-up for WAIT_KEY. It releases the wait but does not
1820        // stop the sound. CANCEL and arbitrary key-down events are ignored.
1821        if result == 1 && self.audio_key_skip {
1822            if self.audio.take().is_some() {
1823                if self.audio_return_value {
1824                    self.pending_value = Some(Value::Int(1));
1825                }
1826                skipped = true;
1827            }
1828            self.audio_key_skip = false;
1829            self.audio_return_value = false;
1830        }
1831        if self.skip_time_on_key && matches!(result, 1 | -1) {
1832            anim_skip_trace(format!(
1833                "notify_movie_down_up skipped TIMEWAIT_KEY pending={}",
1834                result
1835            ));
1836            self.until = None;
1837            self.skip_time_on_key = false;
1838            self.pending_value = Some(Value::Int(result));
1839            skipped = true;
1840        }
1841        if result == 1 && self.quake_key_skip {
1842            if let Some(wait) = self.quake.take() {
1843                stop_waited_quake(globals, wait);
1844                skipped = true;
1845            }
1846            self.quake_key_skip = false;
1847        }
1848        if result == 1 && self.event_key_skip {
1849            if let Some(w) = self.event.take() {
1850                anim_skip_trace(format!(
1851                    "notify_movie_down_up skip event result={} event={:?} return_value={}",
1852                    result, w, self.event_return_value
1853                ));
1854                finish_event_wait_by_key(&w, globals, ids);
1855                if self.event_return_value {
1856                    self.pending_value = Some(Value::Int(1));
1857                }
1858                skipped = true;
1859            } else {
1860                anim_skip_trace(format!(
1861                    "notify_movie_down_up event_key_skip without event result={}",
1862                    result
1863                ));
1864            }
1865            self.event_key_skip = false;
1866            self.event_return_value = false;
1867        }
1868        if self.global_movie && self.global_movie_key_skip {
1869            // C++ tnm_mov_wait_proc returns first, then C_elm_mov::close() tears down
1870            // the native movie.  Do not clear audio_id here; RuntimeContext must still
1871            // see it and stop the Rust movie audio handle.
1872            globals.mov.playing = false;
1873            if self.global_movie_return_value {
1874                self.pending_value = Some(Value::Int(result));
1875            }
1876            self.global_movie = false;
1877            self.global_movie_key_skip = false;
1878            self.global_movie_return_value = false;
1879            skipped = true;
1880        }
1881        // OBJECT movie wait in C++ only consumes VK_EX_DECIDE down-up.
1882        // VK_EX_CANCEL is handled only by GLOBAL MOV_WAIT_KEY.
1883        if self.movie_key_skip && result == 1 {
1884            if let Some(w) = self.movie.take() {
1885                if w.return_value_flag {
1886                    self.pending_value = Some(Value::Int(1));
1887                }
1888                self.movie_skip_info = Some(w);
1889                skipped = true;
1890            }
1891            self.movie_key_skip = false;
1892        }
1893
1894        // OBJECT.EMOTE_WAIT_PLAYING_KEY consumes DECIDE only, returns 1 and
1895        // calls Pass() on the player; CANCEL does not release the wait.
1896        if self.emote_key_skip && result == 1 {
1897            if let Some(w) = self.emote.take() {
1898                if let Some(obj) = object_active_by_runtime_slot_mut(
1899                    globals,
1900                    w.stage_form_id,
1901                    w.stage_idx,
1902                    w.runtime_slot,
1903                ) && let Some(runtime) = obj.emote.runtime.as_mut()
1904                    && let Err(err) = runtime.pass()
1905                {
1906                    log::error!("EMOTE_WAIT_PLAYING_KEY Pass failed: {err:#}");
1907                }
1908                if w.return_value_flag {
1909                    self.pending_value = Some(Value::Int(1));
1910                }
1911                skipped = true;
1912            }
1913            self.emote_key_skip = false;
1914        }
1915        if skipped {
1916            self.waiting_for_key = false;
1917        }
1918        skipped
1919    }
1920
1921    /// If the current wait was skipped via key input, returns the skipped movie wait info.
1922    pub fn take_movie_skip(&mut self) -> Option<MovieWait> {
1923        self.movie_skip_info.take()
1924    }
1925
1926    pub fn clear(&mut self) {
1927        self.selbtn = false;
1928        self.group_selection = None;
1929        self.until = None;
1930        self.mwnd_animation_wait = false;
1931        self.waiting_for_key = false;
1932        self.generic_key_wait = false;
1933        self.generic_key_wait_skip_disabled = false;
1934        self.message_reveal = false;
1935        self.message_key_after_reveal = false;
1936        self.message_key_wait = false;
1937        self.skip_time_on_key = false;
1938        self.audio = None;
1939        self.audio_key_skip = false;
1940        self.audio_return_value = false;
1941        self.event = None;
1942        self.event_key_skip = false;
1943        self.event_return_value = false;
1944        self.movie = None;
1945        self.movie_key_skip = false;
1946        self.global_movie = false;
1947        self.global_movie_key_skip = false;
1948        self.global_movie_return_value = false;
1949        self.movie_skip_info = None;
1950        self.quake = None;
1951        self.quake_key_skip = false;
1952        self.pending_value = None;
1953        self.system_modal = false;
1954        self.wipe = false;
1955        self.wipe_key_skip = false;
1956        self.wipe_return_value = false;
1957    }
1958}
1959
1960#[cfg(test)]
1961mod audio_wait_parity_tests {
1962    use super::*;
1963    use std::path::PathBuf;
1964
1965    fn engines() -> (BgmEngine, KoeEngine, SeEngine, PcmEngine) {
1966        let root = PathBuf::from(".");
1967        (
1968            BgmEngine::new(root.clone()),
1969            KoeEngine::new(root.clone()),
1970            SeEngine::new(root.clone()),
1971            PcmEngine::new(root),
1972        )
1973    }
1974
1975    #[test]
1976    fn audio_wait_key_ignores_generic_input_and_cancel_but_decide_returns_one() {
1977        let mut wait = VmWait::default();
1978        let mut globals = GlobalState::default();
1979        let ids = RuntimeConstants::default();
1980
1981        wait.wait_audio_with_return(AudioWait::KoeAny, true, true);
1982        assert!(wait.audio.is_some());
1983        assert!(wait.audio_key_skip);
1984        assert!(!wait.waiting_for_key);
1985
1986        // C++ tnm_koe_wait_proc does not consume arbitrary key-down input.
1987        assert!(!wait.notify_key(&mut globals, &ids));
1988        assert!(wait.audio.is_some());
1989        assert!(wait.pending_value.is_none());
1990
1991        // KOE/PCM/BGM WAIT_KEY consumes VK_EX_DECIDE only, not CANCEL.
1992        assert!(!wait.notify_movie_down_up(&mut globals, &ids, -1));
1993        assert!(wait.audio.is_some());
1994        assert!(wait.pending_value.is_none());
1995
1996        assert!(wait.notify_movie_down_up(&mut globals, &ids, 1));
1997        assert!(wait.audio.is_none());
1998        assert!(!wait.audio_key_skip);
1999        assert_eq!(wait.pending_value.take().and_then(|v| v.as_i64()), Some(1));
2000    }
2001
2002    #[test]
2003    fn audio_wait_natural_finish_returns_zero_without_leaving_generic_key_wait() {
2004        let mut wait = VmWait::default();
2005        let (mut bgm, mut koe, mut se, mut pcm) = engines();
2006        let mut globals = GlobalState::default();
2007        let ids = RuntimeConstants::default();
2008
2009        wait.wait_audio_with_return(AudioWait::KoeAny, true, true);
2010        assert!(!wait.is_blocked(
2011            &mut bgm,
2012            &mut koe,
2013            &mut se,
2014            &mut pcm,
2015            &mut globals,
2016            &ids,
2017            false,
2018        ));
2019        assert!(wait.audio.is_none());
2020        assert!(!wait.audio_key_skip);
2021        assert!(!wait.waiting_for_key);
2022        assert_eq!(wait.pending_value.take().and_then(|v| v.as_i64()), Some(0));
2023    }
2024
2025    #[test]
2026    fn message_wait_preserves_cpp_proc_stack_order() {
2027        let mut wait = VmWait::default();
2028
2029        wait.wait_message_reveal_then_key();
2030        assert!(wait.needs_continuous_frame());
2031        assert!(wait.message_reveal_waiting());
2032        assert!(!wait.message_key_waiting());
2033        assert!(!wait.waiting_for_key());
2034
2035        assert!(wait.finish_message_reveal());
2036        assert!(wait.needs_runtime_poll());
2037        assert!(!wait.needs_continuous_frame());
2038        assert!(!wait.message_reveal_waiting());
2039        assert!(wait.message_key_waiting());
2040        assert!(wait.waiting_for_key());
2041
2042        wait.finish_message_key_wait();
2043        assert!(!wait.message_key_waiting());
2044        assert!(!wait.waiting_for_key());
2045    }
2046
2047    #[test]
2048    fn selbtn_wait_ignores_generic_key_release_and_preserves_second_choice() {
2049        let mut wait = VmWait::default();
2050        let (mut bgm, mut koe, mut se, mut pcm) = engines();
2051        let mut globals = GlobalState::default();
2052        let ids = RuntimeConstants::default();
2053        let mut stack = Vec::new();
2054
2055        globals.selbtn.sync_type = 0;
2056        globals.selbtn.processing_flag_0 = true;
2057        wait.wait_selbtn();
2058        wait.set_selbtn_result(1);
2059
2060        // Enter-up/message wait notifications are unrelated to SEL_BTN and
2061        // must not turn the pending second choice into the default FM_INT=0.
2062        assert!(!wait.notify_key(&mut globals, &ids));
2063        assert!(wait.poll(
2064            &mut stack,
2065            &mut bgm,
2066            &mut koe,
2067            &mut se,
2068            &mut pcm,
2069            &mut globals,
2070            &ids,
2071            false,
2072        ));
2073        assert!(stack.is_empty());
2074
2075        globals.selbtn.processing_flag_0 = false;
2076        assert!(!wait.poll(
2077            &mut stack,
2078            &mut bgm,
2079            &mut koe,
2080            &mut se,
2081            &mut pcm,
2082            &mut globals,
2083            &ids,
2084            false,
2085        ));
2086        assert_eq!(stack.pop().and_then(|v| v.as_i64()), Some(1));
2087    }
2088
2089    #[test]
2090    fn selbtn_sync_type_two_releases_at_decision() {
2091        let mut wait = VmWait::default();
2092        let (mut bgm, mut koe, mut se, mut pcm) = engines();
2093        let mut globals = GlobalState::default();
2094        let ids = RuntimeConstants::default();
2095        let mut stack = Vec::new();
2096
2097        globals.selbtn.sync_type = 2;
2098        globals.selbtn.processing_flag_2 = true;
2099        wait.wait_selbtn();
2100        assert!(wait.poll(
2101            &mut stack,
2102            &mut bgm,
2103            &mut koe,
2104            &mut se,
2105            &mut pcm,
2106            &mut globals,
2107            &ids,
2108            false,
2109        ));
2110
2111        wait.set_selbtn_result(1);
2112        globals.selbtn.processing_flag_2 = false;
2113        assert!(!wait.poll(
2114            &mut stack,
2115            &mut bgm,
2116            &mut koe,
2117            &mut se,
2118            &mut pcm,
2119            &mut globals,
2120            &ids,
2121            false,
2122        ));
2123        assert_eq!(stack.pop().and_then(|v| v.as_i64()), Some(1));
2124    }
2125
2126    #[test]
2127    fn pure_key_wait_does_not_request_idle_frames() {
2128        let mut wait = VmWait::default();
2129
2130        wait.wait_input_key(false);
2131
2132        assert!(wait.needs_runtime_poll());
2133        assert!(!wait.needs_continuous_frame());
2134    }
2135
2136    #[test]
2137    fn keylist_wait_obeys_skip_but_wait_force_does_not() {
2138        let (mut bgm, mut koe, mut se, mut pcm) = engines();
2139        let mut globals = GlobalState::default();
2140        let ids = RuntimeConstants::default();
2141
2142        let mut wait = VmWait::default();
2143        wait.wait_input_key(false);
2144        assert!(!wait.is_blocked(
2145            &mut bgm,
2146            &mut koe,
2147            &mut se,
2148            &mut pcm,
2149            &mut globals,
2150            &ids,
2151            true,
2152        ));
2153
2154        let mut forced = VmWait::default();
2155        forced.wait_input_key(true);
2156        assert!(forced.is_blocked(
2157            &mut bgm,
2158            &mut koe,
2159            &mut se,
2160            &mut pcm,
2161            &mut globals,
2162            &ids,
2163            true,
2164        ));
2165    }
2166
2167    #[test]
2168    fn skipping_releases_audio_wait_with_zero_without_stopping_player_state() {
2169        let mut wait = VmWait::default();
2170        let (mut bgm, mut koe, mut se, mut pcm) = engines();
2171        let mut globals = GlobalState::default();
2172        let ids = RuntimeConstants::default();
2173
2174        wait.wait_audio_with_return(AudioWait::KoeAny, true, true);
2175        assert!(!wait.is_blocked(
2176            &mut bgm,
2177            &mut koe,
2178            &mut se,
2179            &mut pcm,
2180            &mut globals,
2181            &ids,
2182            true,
2183        ));
2184        assert!(wait.audio.is_none());
2185        assert!(!wait.audio_key_skip);
2186        assert_eq!(wait.pending_value.take().and_then(|v| v.as_i64()), Some(0));
2187    }
2188}