I already decided how I want to change the move event visibility rules the other day, but I need to actually implement these changes in the code. I start by updating the existing test cases so they assert on the new desired behavior.

I also add a new test case to check that PCLeftHexEvent events are added to the events log before any PCEnteredHexEvent events for the same round.

defmodule Minotaur.GameEngine.Session.EndRoundPlayerMoveTest do
    # …

  describe "multiple players move from one hex, some to the same destination, another to different" do
    # …

    test "left hex events are created before all entered hex events for the same round", ctx do
      events = Enum.map(ctx.game.events_log.events, fn {_, event} -> event end)
      left_events = Enum.filter(events, fn event -> is_struct(event, PCLeftHexEvent) end)
      entered_events = Enum.filter(events, fn event -> is_struct(event, PCEnteredHexEvent) end)

      assert Enum.all?(left_events, fn left_event ->
               Enum.all?(entered_events, fn entered_event -> left_event.id < entered_event.id end)
             end)
    end
  end
end

I replace private function responsible for mapping global events to player event lists. The new function applies the updated player visibility rules for move events.

defmodule Minotaur.GameEngine.Session do
  # ...

  defp get_visibility_entered_hex_event(%{to: destination}, post_move_world) do
    post_move_world
    |> Worlds.get_pcs_at_coord(destination)
    |> Enum.map(& &1.player_id)
  end

  defp get_visibility_left_hex_event(event, pre_move_world, post_move_world) do
    pc_ids_in_destination =
      post_move_world
      |> Worlds.get_pcs_at_coord(event.to)
      |> Enum.map(& &1.player_id)

    pre_move_world
    |> Worlds.get_pcs_at_coord(event.from)
    |> Enum.map(& &1.player_id)
    |> Enum.reject(fn player_id -> Enum.member?(pc_ids_in_destination, player_id) end)
  end

  # Move event player visibility rules:
  # - PCEnteredHexEvent seen by players in destination hex post-move
  # - PCLeftHexEvent seen by players started in origin hex pre-move, excluding players in the destination hex post-move
  defp update_events_log_with_moves(log, entered_hex_events, pre_move_world, post_move_world) do
    left_events_with_visibility =
      entered_hex_events
      |> Enum.map(fn event ->
        left_hex_event = struct(PCLeftHexEvent, Map.from_struct(event))

        {left_hex_event, get_visibility_left_hex_event(event, pre_move_world, post_move_world)}
      end)
      |> Enum.reject(fn {_, visibility_list} -> visibility_list == [] end)

    entered_events_with_visibility =
      Enum.map(entered_hex_events, fn event ->
        {event, get_visibility_entered_hex_event(event, post_move_world)}
      end)

    # Add all PCLeftHexEvents to log before PCEnteredHexEvents
    Enum.concat(left_events_with_visibility, entered_events_with_visibility)
    |> Enum.reduce(log, fn {event, visibility_list}, log ->
      add_event_with_visibility(log, event, visibility_list)
    end)
  end
end

With these changes, the updated tests and the new test for event ordering are all passing.

The next feature I’ve been thinking about adding is to add a separator between events in the UI log to show which events happened on the same round.