drain object queues after each object
This commit is contained in:
@@ -46,7 +46,8 @@ fn bumping_a_transporter_drops_you_on_its_far_side() {
|
||||
game.run_init();
|
||||
|
||||
// Walk east into the transporter: it's solid so the player doesn't step onto
|
||||
// it, but the bump queues a teleport that the next tick applies.
|
||||
// it, but the bump fires a teleport that resolves within this try_move. The
|
||||
// trailing tick just advances the transporter's idle animation.
|
||||
game.try_move(Direction::East);
|
||||
game.tick(Duration::from_secs_f64(0.1));
|
||||
|
||||
|
||||
@@ -295,8 +295,8 @@ fn scroll_opens_on_player_bump() {
|
||||
)]),
|
||||
);
|
||||
game.run_init();
|
||||
// The bump resolves within try_move now, so the scroll is open immediately.
|
||||
game.try_move(Direction::East);
|
||||
game.tick(Duration::from_millis(16));
|
||||
|
||||
let scroll = game
|
||||
.active_scroll
|
||||
@@ -318,11 +318,11 @@ fn handle_scroll_without_choice_clears_it() {
|
||||
scripts_from(&[("s", r#"fn bump(m,dir) { scroll(["Hello"]); }"#)]),
|
||||
);
|
||||
game.run_init();
|
||||
// The bump resolves within try_move, so the scroll is open right away.
|
||||
game.try_move(Direction::East);
|
||||
game.tick(Duration::from_millis(16));
|
||||
assert!(game.active_scroll.is_some());
|
||||
|
||||
// No choice set — next tick clears the scroll without dispatching.
|
||||
// No choice set — a tick clears the scroll without dispatching.
|
||||
game.tick(Duration::from_millis(16));
|
||||
assert!(game.active_scroll.is_none());
|
||||
}
|
||||
@@ -343,8 +343,8 @@ fn handle_scroll_with_choice_dispatches_send_to_source() {
|
||||
)]),
|
||||
);
|
||||
game.run_init();
|
||||
// The bump resolves within try_move, so the scroll is open right away.
|
||||
game.try_move(Direction::East);
|
||||
game.tick(Duration::from_millis(16));
|
||||
assert!(game.active_scroll.is_some());
|
||||
|
||||
// Set the choice, then tick — handle_scroll dispatches "eat" and resolve picks up the log.
|
||||
@@ -356,3 +356,53 @@ fn handle_scroll_with_choice_dispatches_send_to_source() {
|
||||
"eat() should have logged"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_later_object_sees_an_earlier_objects_move_this_tick() {
|
||||
// The core of the epic: each object's queued actions apply immediately, before
|
||||
// the next (higher-id) object runs its hook. Object A (id 1) at (0,0) moves East
|
||||
// onto (1,0); object B (id 2) at (1,1) then checks the cell to its North (1,0).
|
||||
// Because A already moved there this tick, B observes it as blocked. Under the
|
||||
// old collect-all-then-apply model B would have seen (1,0) still empty.
|
||||
let a = scripted_object(0, 0, "a");
|
||||
let b = scripted_object(1, 1, "b");
|
||||
let board = open_board(3, 2, (2, 1), vec![a, b]);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[
|
||||
("a", "fn tick(m,dt) { if m.queue.length == 0 { move(East); } }"),
|
||||
("b", r#"fn tick(m,dt) { log(if m.blocked(North) { "blocked" } else { "clear" }); }"#),
|
||||
]),
|
||||
);
|
||||
game.run_init();
|
||||
|
||||
game.tick(Duration::from_millis(16));
|
||||
|
||||
// A moved onto (1,0), and B saw it there the same tick.
|
||||
assert_eq!((game.board().objects[&1].x, game.board().objects[&1].y), (1, 0));
|
||||
assert_eq!(log_texts(&game), vec!["blocked"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_send_cycle_terminates_via_the_called_guard() {
|
||||
// Two objects send "go" to each other in a cycle. Without the per-invocation
|
||||
// "already-called" guard this would recurse forever; with it, each (object, fn,
|
||||
// args) fires at most once, so the cascade settles after one round-trip. That the
|
||||
// call returns at all — and logs exactly one "B" then one "A" — proves it.
|
||||
let a = scripted_object(0, 0, "a");
|
||||
let b = scripted_object(1, 0, "b");
|
||||
let board = open_board(3, 1, (2, 0), vec![a, b]);
|
||||
let mut game = GameState::with_scripts(
|
||||
board,
|
||||
scripts_from(&[
|
||||
("a", r#"fn init(m) { send(2, "poke"); } fn poke(m) { log("A"); send(2, "poke"); }"#),
|
||||
("b", r#"fn poke(m) { log("B"); send(1, "poke"); }"#),
|
||||
]),
|
||||
);
|
||||
|
||||
game.run_init();
|
||||
|
||||
// B.poke fires once (from A.init's send), then A.poke once (from B.poke's send);
|
||||
// A.poke's re-send to B.poke is a repeat key and is skipped, so the cascade stops.
|
||||
assert_eq!(log_texts(&game), vec!["B", "A"]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user