open util/ordering[Gnome] as Lineup

  -- Disable ordering on KS for debugging as needed
  open util/ordering[KnowledgeState] as KS

  -- Gnomes are partitioned into 2 separate rooms
  -- and, in each room, lined up in (arbitrary) order
  -- AN INSTANCE HAS A FIXED ROOM SETUP
  sig Gnome {
    room: Room
  }
  abstract sig Room {}
  one sig LeftRoom  extends Room {}
  one sig RightRoom extends Room {}

  -- Hats are either red or blue
  abstract sig HatColor {}
  one sig Red  extends HatColor {}
  one sig Blue extends HatColor {}

  -- A possible world is an allocation of hats
  sig World {
    hats: Gnome -> one HatColor
  }
  -- no two worlds have the same hat allocation
  fact canonicalWorlds {
    all disj w1, w2: World |
      some g: Gnome | w1.hats[g] != w2.hats[g]
  }
  fact halfRedHalfBlue {
    all w: World |
      #{g: Gnome | w.hats[g] = Blue} =
      #{g: Gnome | w.hats[g] = Red}
  }

  -- Before every question, gnomes have a certain knowledge state
  -- "State" and "World" are *different concepts*
  sig KnowledgeState {
    -- Read as: poss[g][w1][w2] means
    --    "If g is in w1, they believe w2 is possible."
    poss: Gnome -> World -> World,
    justAsked: lone Gnome
  } {
    this = KS/first implies no justAsked
    this != KS/first implies some justAsked
    -- no repeats (note: still safe even for KS/first)
    no ks: KnowledgeState - this |
      ks.@justAsked = justAsked
  }

  -- Who can the gnomes see in this instance?
  fun canSee[g: Gnome]: set Gnome {
  	-- FILL
  }

  pred initialKnowledge[s: KnowledgeState] {
    -- Each gnome can see those later in their ordering
    -- So some gnomes know that /some/ worlds are impossible right away

    -- FILL
  }

  -- You'll need to add a fact that describes what's learned over time, too.
  -- I suggest writing initialKnowledge, constraining the initial state to obey it,
  --   and then doing some sanity-checking before moving on to what gets learned over time.