diff --git a/examples/games/Engine.hs b/examples/games/Engine.hs
new file mode 100644
--- /dev/null
+++ b/examples/games/Engine.hs
@@ -0,0 +1,183 @@
+{-# LANGUAGE ScopedTypeVariables, EmptyDataDecls #-}
+module Engine where
+
+import FRP.Sodium
+
+import Control.Applicative
+import Control.Monad
+import Data.List
+import Graphics.Rendering.OpenGL as GL hiding (Triangle, Rect, translate)
+import qualified Graphics.Rendering.OpenGL as GL
+import qualified Graphics.UI.GLUT as GLUT hiding (Rect, translate)
+import Data.IORef
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Maybe
+import System.Time
+import Debug.Trace
+
+import Image
+
+frameRate :: Num a => a
+frameRate = 40
+
+type Coord = Double
+type Point = (Coord, Coord)
+type Vector = (Coord, Coord)
+type Rect = (Point, Vector)   -- Central point and size from centre to edge
+type Sprite = (Rect, String)
+
+data MouseEvent = MouseDown Point | MouseMove Point | MouseUp Point
+    deriving Show
+
+plus :: Point -> Vector -> Point
+plus (x0, y0) (x1, y1) = (x0 + x1, y0 + y1)
+
+minus :: Point -> Point -> Vector
+minus (x0, y0) (x1, y1) = (x0 - x1, y0 - y1)
+
+-- | True if the point is inside the rectangle
+inside :: Point -> Rect -> Bool
+inside (x, y) ((ox, oy), (wx, wy)) =
+    x >= ox - wx && x <= ox + wx &&
+    y >= oy - wy && y <= oy + wy
+
+-- | True if the two rectangles overlap
+overlaps :: Rect -> Rect -> Bool
+overlaps ((x0, y0), (w0, h0)) ((x1, y1), (w1, h1)) =
+    let ax0 = x0 - w0
+        ay0 = y0 - h0
+        ax1 = x0 + w0
+        ay1 = y0 + h0
+        bx0 = x1 - w1
+        by0 = y1 - h1
+        bx1 = x1 + w1
+        by1 = y1 + h1
+    in ax1 > bx0 &&
+       ay1 > by0 &&
+       ax0 < bx1 &&
+       ay0 < by1
+
+-- | Get system time in seconds since the start of the Unix epoch
+-- (1 Jan 1970).
+getTime :: IO Double
+getTime = do
+    (TOD sec pico) <- getClockTime
+    return $!
+        (fromIntegral sec) +
+        (fromIntegral pico) / 1000000000000
+
+-- | Game, which takes mouse event and time as input, and a list of sprites to draw
+-- as output. Time is updated once per animation frame.
+type Game = Event MouseEvent -> Behaviour Double -> Reactive (Behaviour [Sprite])
+
+runGame :: String -> Game -> IO ()
+runGame title game = do
+
+    (eMouse, pushMouse) <- sync newEvent
+    (eTime, pushTime) <- sync newEvent
+    spritesRef <- newIORef []
+    _ <- sync $ do
+        time <- hold 0 eTime
+        sprites <- game eMouse time
+        listen (values sprites) (writeIORef spritesRef)
+
+    _ <- GLUT.getArgsAndInitialize
+    GLUT.initialDisplayMode $= [GLUT.DoubleBuffered]
+    GLUT.createWindow title
+    GLUT.windowSize $= GLUT.Size 700 500
+    blend $= Enabled
+    blendFunc $= (SrcAlpha, OneMinusSrcAlpha)
+    multisample $= Enabled
+    shadeModel $= Smooth
+    polygonSmooth $= Enabled
+    hint PolygonSmooth $= Nicest
+    hint LineSmooth $= Nicest
+    normalize $= Enabled
+
+    texturesRef <- newIORef M.empty
+    t0 <- getTime
+    GLUT.displayCallback $= display texturesRef t0 pushTime spritesRef
+    let motion (GLUT.Position x y) = do
+            pt <- toScreen x y
+            sync $ pushMouse (MouseMove pt)
+    GLUT.motionCallback $= Just motion
+    GLUT.passiveMotionCallback $= Just motion
+    GLUT.keyboardMouseCallback $= Just (\key keyState mods pos -> do
+            case (key, keyState, pos) of
+                (GLUT.MouseButton GLUT.LeftButton, GLUT.Down, GLUT.Position x y) ->
+                    sync . pushMouse . MouseDown =<< toScreen x y
+                (GLUT.MouseButton GLUT.LeftButton, GLUT.Up,   GLUT.Position x y) ->
+                    sync . pushMouse . MouseUp   =<< toScreen x y
+                _ -> return ()
+        )
+    GLUT.addTimerCallback (1000 `div` frameRate) $ repaint
+    GLUT.mainLoop
+  where
+    toScreen :: GLint -> GLint -> IO (Coord, Coord)
+    toScreen x y = do
+        (_, Size w h) <- get viewport
+        let aspect = fromIntegral w / fromIntegral h
+            sx = 0.001/aspect
+            sy = 0.001
+            xx = 2 * ((fromIntegral x / fromIntegral w) - 0.5) / sx
+            yy = 2 * (0.5 - (fromIntegral y / fromIntegral h)) / sy
+        return (xx, yy)
+    repaint = do
+        GLUT.postRedisplay Nothing
+        GLUT.addTimerCallback (1000 `div` frameRate) $ repaint
+
+    period = 1 / frameRate
+
+    display :: IORef (Map String (TextureImage, TextureObject))
+            -> Double
+            -> (Double -> Reactive ())
+            -> IORef [Sprite]
+            -> IO ()
+    display texturesRef t0 pushTime spritesRef = do
+
+        t <- subtract t0 <$> getTime
+        sync $ pushTime t
+
+        sprites <- readIORef spritesRef
+
+        clearColor $= Color4 0 0 0 (1 :: GLclampf)
+        --clearColor $= Color4 0.1 0.1 0.15 (1 :: GLclampf)
+        clear [ColorBuffer{-, DepthBuffer-}]
+        loadIdentity
+
+        (_, Size w h) <- get viewport
+        let aspect = fromIntegral w / fromIntegral h
+        scale (0.001/aspect) 0.001 (0.001 :: GLfloat)
+
+        forM_ sprites $ \(((posX, posY),(sizeX, sizeY)),imgFile) -> do
+            textures <- readIORef texturesRef
+            (TextureImage iWidth iHeight pWidth pHeight _ _, to) <- case imgFile `M.lookup` textures of
+                Just (ti, to) -> return (ti, to)
+                Nothing       -> do
+                    ti <- loadTexture imgFile False
+                    to <- createTexture ti
+                    modifyIORef texturesRef (M.insert imgFile (ti, to))
+                    return $ (ti, to)
+
+            preservingMatrix $ do
+                texture Texture2D $= Enabled
+                textureBinding Texture2D $= Just to
+                GL.translate $ Vector3 (realToFrac posX) (realToFrac posY) (0 :: GLdouble)
+                let w2 = realToFrac sizeX :: GLdouble
+                    h2 = realToFrac sizeY :: GLdouble
+                    cx = realToFrac pWidth / realToFrac iWidth :: GLfloat
+                    cy = realToFrac pHeight / realToFrac iHeight :: GLfloat
+                renderPrimitive Polygon $ do
+                    texCoord $ TexCoord2 0 cy
+                    vertex $ Vertex2 (-w2) (-h2)
+                    texCoord $ TexCoord2 cx cy
+                    vertex $ Vertex2 w2 (-h2)
+                    texCoord $ TexCoord2 cx 0
+                    vertex $ Vertex2 w2 h2
+                    texCoord $ TexCoord2 0 (0 :: GLfloat)
+                    vertex $ Vertex2 (-w2) h2
+                texture Texture2D $= Disabled
+            --translate $ Vector3 0 0 (0.001 :: GLdouble)
+
+        GLUT.swapBuffers
diff --git a/examples/games/Image.hs b/examples/games/Image.hs
new file mode 100644
--- /dev/null
+++ b/examples/games/Image.hs
@@ -0,0 +1,134 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+module Image where
+
+import Data.ByteString (ByteString)     
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Internal as B
+import qualified Data.ByteString.Unsafe as B
+import Graphics.Rendering.OpenGL as GL hiding (RGB, RGBA)
+import qualified Graphics.Rendering.OpenGL as GL
+import Codec.Image.STB
+import Data.Bitmap
+import Data.Char
+import Control.Exception
+import Control.Monad
+import Data.Typeable
+import Data.Word
+import Foreign
+import Foreign.C
+
+
+data Format = RGB | RGBA deriving (Eq, Show)
+
+data TextureImage_ d = TextureImage !Int !Int !Int !Int Format d deriving Show
+type TextureImage = TextureImage_ ByteString
+data ImageException = ImageException String
+    deriving Typeable
+instance Exception ImageException
+instance Show ImageException where
+    showsPrec _ (ImageException err) = ("ImageException: "++) . (err++)
+
+loadTexture :: FilePath
+            -> Bool               -- ^ True to invert it
+            -> IO TextureImage
+loadTexture path invert = do
+        eImg <- loadImage path
+        img <- case eImg of
+            Left err -> throwIO $ ImageException $
+                "Failed to load image file "++path++": "++err
+            Right img -> return img
+        withBitmap img $ \(width, height) components padding imgData0 -> do
+            fmt <- case components of
+                3 -> return RGB
+                4 -> return RGBA
+                _ -> throwIO $ ImageException $
+                    "Failed to load image file "++path++": we only support RGB images"
+            let len = width * height * components
+            imgData <- mallocBytes len
+            B.memcpy imgData imgData0 (fromIntegral len)
+            when invert $ do
+                let row = width * bytesPerPixel fmt
+                    row_sz = fromIntegral row
+                allocaBytes row $ \tempRow -> do
+                    forM_ [0..height `div` 2] $ \y -> do
+                        let r0 = row * y
+                            s0 = row * (height - 1 - y)
+                        B.memcpy tempRow (imgData `plusPtr` r0) row_sz
+                        B.memcpy (imgData `plusPtr` r0) (imgData `plusPtr` s0) row_sz
+                        B.memcpy (imgData `plusPtr` s0) tempRow row_sz
+            let tex = TextureImage width height width height fmt imgData
+            transparent <- hasTransparency tex
+            tex' <- if transparent then return tex else removeAlphaChannel tex
+            unsafeTextureToBS tex'
+
+-- | Converts a buffer-based Texture to to a ByteString-based one.  Frees
+-- the input byte buffer.
+unsafeTextureToBS :: TextureImage_ (Ptr Word8) -> IO TextureImage
+unsafeTextureToBS (TextureImage iWidth iHeight pWidth pHeight fmt buf) = do
+    let bpp = bytesPerPixel fmt
+        sz = bpp * iWidth * iHeight
+    bytes <- peekArray sz buf
+    free buf
+    return $ TextureImage iWidth iHeight pWidth pHeight fmt (B.pack bytes)
+
+bytesPerPixel :: Format -> Int
+bytesPerPixel RGB = 3
+bytesPerPixel RGBA = 4
+
+hasTransparency :: TextureImage_ (Ptr Word8) -> IO Bool
+hasTransparency (TextureImage _ _ _ _ RGB _) = return False
+hasTransparency (TextureImage width height _ _ fmt@RGBA imgData) = h 0
+  where
+    limit = width * height
+    bpp = bytesPerPixel fmt
+    h ix | ix >= limit = return False
+    h ix = do
+        alpha <- peekByteOff imgData (ix * bpp + 3) :: IO Word8
+        if alpha < 255
+            then return True
+            else h (ix + 1)
+
+removeAlphaChannel :: TextureImage_ (Ptr Word8) -> IO (TextureImage_ (Ptr Word8))
+removeAlphaChannel tex@(TextureImage _ _ _ _ RGB _) = return tex
+removeAlphaChannel (TextureImage iWidth iHeight pWidth pHeight fmt@RGBA imgData) = do
+    forM_ [1..limit-1] $ \ix -> do
+        r <- peekByteOff imgData (ix * bpp) :: IO Word8
+        pokeByteOff imgData (ix * 3) r
+        g <- peekByteOff imgData (ix * bpp + 1) :: IO Word8
+        pokeByteOff imgData (ix * 3 + 1) g
+        b <- peekByteOff imgData (ix * bpp + 2) :: IO Word8
+        pokeByteOff imgData (ix * 3 + 2) b
+    return (TextureImage iWidth iHeight pWidth pHeight RGB imgData)
+  where
+    limit = iWidth * iHeight
+    bpp = bytesPerPixel fmt
+
+-- | Convert texture data into an OpenGL handle
+createTexture :: TextureImage
+              -> IO TextureObject
+createTexture (TextureImage iWid iHt _ _ fmt imgBS) = do
+    [texName] <- genObjectNames 1  -- generate our texture.
+    --rowAlignment  Unpack $= 1
+    textureBinding Texture2D $= Just texName  -- make our new texture the current texture.
+    generateMipmap Texture2D $= Enabled
+    B.unsafeUseAsCStringLen imgBS $ \(buf, len) -> do
+        let glFmt = case fmt of
+                RGB  -> GL.RGB
+                RGBA -> GL.RGBA
+            pixels = PixelData glFmt UnsignedByte buf
+        build2DMipmaps Texture2D RGBA' (fromIntegral iWid) (fromIntegral iHt) pixels
+    GL.textureFilter GL.Texture2D $= ((GL.Linear', Nothing), GL.Linear')
+    return texName
+
+-- | Ensure this label can be a valid filename
+cleanLabel :: String -> String
+cleanLabel =
+    scrunch .
+    map (\c -> if (c >= 'a' && c <= 'z') ||
+                  (c >= 'A' && c <= 'Z') ||
+                  (c >= '0' && c <= '9') then c else '_')
+  where
+    scrunch ('_':'_':cs) = scrunch ('_':cs)
+    scrunch (c:cs) = c:scrunch cs
+    scrunch [] = []
+
diff --git a/examples/games/cards/b1fh.png b/examples/games/cards/b1fh.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b1fh.png differ
diff --git a/examples/games/cards/b1fv.png b/examples/games/cards/b1fv.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b1fv.png differ
diff --git a/examples/games/cards/b1pb.png b/examples/games/cards/b1pb.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b1pb.png differ
diff --git a/examples/games/cards/b1pl.png b/examples/games/cards/b1pl.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b1pl.png differ
diff --git a/examples/games/cards/b1pr.png b/examples/games/cards/b1pr.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b1pr.png differ
diff --git a/examples/games/cards/b1pt.png b/examples/games/cards/b1pt.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b1pt.png differ
diff --git a/examples/games/cards/b2fh.png b/examples/games/cards/b2fh.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b2fh.png differ
diff --git a/examples/games/cards/b2fv.png b/examples/games/cards/b2fv.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b2fv.png differ
diff --git a/examples/games/cards/b2pb.png b/examples/games/cards/b2pb.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b2pb.png differ
diff --git a/examples/games/cards/b2pl.png b/examples/games/cards/b2pl.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b2pl.png differ
diff --git a/examples/games/cards/b2pr.png b/examples/games/cards/b2pr.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b2pr.png differ
diff --git a/examples/games/cards/b2pt.png b/examples/games/cards/b2pt.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/b2pt.png differ
diff --git a/examples/games/cards/c1.png b/examples/games/cards/c1.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c1.png differ
diff --git a/examples/games/cards/c10.png b/examples/games/cards/c10.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c10.png differ
diff --git a/examples/games/cards/c2.png b/examples/games/cards/c2.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c2.png differ
diff --git a/examples/games/cards/c3.png b/examples/games/cards/c3.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c3.png differ
diff --git a/examples/games/cards/c4.png b/examples/games/cards/c4.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c4.png differ
diff --git a/examples/games/cards/c5.png b/examples/games/cards/c5.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c5.png differ
diff --git a/examples/games/cards/c6.png b/examples/games/cards/c6.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c6.png differ
diff --git a/examples/games/cards/c7.png b/examples/games/cards/c7.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c7.png differ
diff --git a/examples/games/cards/c8.png b/examples/games/cards/c8.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c8.png differ
diff --git a/examples/games/cards/c9.png b/examples/games/cards/c9.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/c9.png differ
diff --git a/examples/games/cards/cj.png b/examples/games/cards/cj.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/cj.png differ
diff --git a/examples/games/cards/ck.png b/examples/games/cards/ck.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/ck.png differ
diff --git a/examples/games/cards/cq.png b/examples/games/cards/cq.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/cq.png differ
diff --git a/examples/games/cards/d1.png b/examples/games/cards/d1.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d1.png differ
diff --git a/examples/games/cards/d10.png b/examples/games/cards/d10.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d10.png differ
diff --git a/examples/games/cards/d2.png b/examples/games/cards/d2.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d2.png differ
diff --git a/examples/games/cards/d3.png b/examples/games/cards/d3.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d3.png differ
diff --git a/examples/games/cards/d4.png b/examples/games/cards/d4.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d4.png differ
diff --git a/examples/games/cards/d5.png b/examples/games/cards/d5.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d5.png differ
diff --git a/examples/games/cards/d6.png b/examples/games/cards/d6.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d6.png differ
diff --git a/examples/games/cards/d7.png b/examples/games/cards/d7.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d7.png differ
diff --git a/examples/games/cards/d8.png b/examples/games/cards/d8.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d8.png differ
diff --git a/examples/games/cards/d9.png b/examples/games/cards/d9.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/d9.png differ
diff --git a/examples/games/cards/dj.png b/examples/games/cards/dj.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/dj.png differ
diff --git a/examples/games/cards/dk.png b/examples/games/cards/dk.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/dk.png differ
diff --git a/examples/games/cards/dq.png b/examples/games/cards/dq.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/dq.png differ
diff --git a/examples/games/cards/ec.png b/examples/games/cards/ec.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/ec.png differ
diff --git a/examples/games/cards/empty-space.png b/examples/games/cards/empty-space.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/empty-space.png differ
diff --git a/examples/games/cards/h1.png b/examples/games/cards/h1.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h1.png differ
diff --git a/examples/games/cards/h10.png b/examples/games/cards/h10.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h10.png differ
diff --git a/examples/games/cards/h2.png b/examples/games/cards/h2.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h2.png differ
diff --git a/examples/games/cards/h3.png b/examples/games/cards/h3.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h3.png differ
diff --git a/examples/games/cards/h4.png b/examples/games/cards/h4.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h4.png differ
diff --git a/examples/games/cards/h5.png b/examples/games/cards/h5.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h5.png differ
diff --git a/examples/games/cards/h6.png b/examples/games/cards/h6.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h6.png differ
diff --git a/examples/games/cards/h7.png b/examples/games/cards/h7.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h7.png differ
diff --git a/examples/games/cards/h8.png b/examples/games/cards/h8.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h8.png differ
diff --git a/examples/games/cards/h9.png b/examples/games/cards/h9.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/h9.png differ
diff --git a/examples/games/cards/hj.png b/examples/games/cards/hj.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/hj.png differ
diff --git a/examples/games/cards/hk.png b/examples/games/cards/hk.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/hk.png differ
diff --git a/examples/games/cards/hq.png b/examples/games/cards/hq.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/hq.png differ
diff --git a/examples/games/cards/index.html b/examples/games/cards/index.html
new file mode 100644
--- /dev/null
+++ b/examples/games/cards/index.html
@@ -0,0 +1,108 @@
+<HTML>
+<HEAD>
+<META NAME="description" CONTENT="A full deck of Playing Card Icons">
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1">
+<META NAME="keywords" CONTENT="Playing Cards, deck of cards, deck, cards, icons, images">
+<TITLE>Playing Cards</TITLE>
+</HEAD>
+<BODY BGCOLOR=#006633  TEXT=#e1ffd7 LINK=#FFFFFF VLINK=#FFFFFF>
+<CENTER>
+<TABLE BORDER=0 CELLSPACING=10 CELLPADDING=10>
+<TR>
+<TD>
+<IMG SRC="b1pt.png"><BR>
+<IMG SRC="b1fh.png"><BR>
+<IMG SRC="b1pb.png"><BR>
+</TD>
+<TD>
+<IMG SRC="b1pl.png">
+<IMG SRC="b1fv.png">
+<IMG SRC="b1pr.png">
+</TD>
+<TD><IMG SRC="jb.png"></TD>
+<TD VALIGN=CENTER><STRONG>Playing Cards</STRONG></TD>
+<TD><IMG SRC="jr.png"></TD>
+<TD>
+<IMG SRC="b2pl.png">
+<IMG SRC="b2fv.png">
+<IMG SRC="b2pr.png">
+</TD>
+<TD>
+<IMG SRC="b2pt.png"><BR>
+<IMG SRC="b2fh.png"><BR>
+<IMG SRC="b2pb.png"><BR>
+</TD>
+</TR>
+<TR></TR>
+</TABLE>
+<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=0>
+<TR>
+<TD ALIGN=CENTER><IMG SRC="c1.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c2.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c3.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c4.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c5.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c6.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c7.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c8.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c9.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="c10.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="cj.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="cq.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="ck.png"></TD>
+</TR>
+<TR>
+<TD ALIGN=CENTER><IMG SRC="h1.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h2.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h3.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h4.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h5.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h6.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h7.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h8.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h9.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="h10.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="hj.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="hq.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="hk.png"></TD>
+</TR>
+<TR>
+<TD ALIGN=CENTER><IMG SRC="s1.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s2.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s3.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s4.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s5.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s6.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s7.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s8.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s9.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="s10.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="sj.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="sq.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="sk.png"></TD>
+</TR>
+<TR>
+<TD ALIGN=CENTER><IMG SRC="d1.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d2.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d3.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d4.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d5.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d6.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d7.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d8.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d9.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="d10.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="dj.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="dq.png"></TD>
+<TD ALIGN=CENTER><IMG SRC="dk.png"></TD>
+</TR>
+</TABLE>
+<TABLE BORDER=0 CELLSPACING=10>
+<TR>
+<TD ALIGN=CENTER><FONT SIZE=2><BR>
+These images were created using <A HREF="http://www.mindworkshop.com/alchemy/alchemy.html">GIFCon</A>.
+</FONT></TD>
+</TR>
+</TABLE>
+</BODY>
+</HTML>
diff --git a/examples/games/cards/jb.png b/examples/games/cards/jb.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/jb.png differ
diff --git a/examples/games/cards/jr.png b/examples/games/cards/jr.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/jr.png differ
diff --git a/examples/games/cards/licence.txt b/examples/games/cards/licence.txt
new file mode 100644
--- /dev/null
+++ b/examples/games/cards/licence.txt
@@ -0,0 +1,16 @@
+http://www.jfitz.com/cards/
+
+
+These images were created using GIFCon, XnView and Paint Shop Pro.
+Feel free to use for personal or professional purposes, subject to the following:
+Additional Copyright information:
+Larry Ewing <lewing@isc.tamu.edu> created Tux using GIMP.
+Marshall Kirk McKusick <mckusick@mckusick.com> is the copyright holder and creator of the BSD Daemon image.
+The "Windows" cards were originally designed by Susan Kare for Microsoft.
+To the best of my knowledge, the images used in any "standard" French/British 52 card deck are public domain.
+The top Jokers are derived from an image I found of a John Waddington design.
+The bottom Jokers were created by me as quick placeholders for a design I never got around to implementing.
+I guess it's possible that Microsoft could claim copyright on the Windows Ace of Spades.
+If they did, I'd suggest filling in or changing the white pattern on the Spade.
+
+Please click here if you have problems viewing the graphics on this page. 
diff --git a/examples/games/cards/s1.png b/examples/games/cards/s1.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s1.png differ
diff --git a/examples/games/cards/s10.png b/examples/games/cards/s10.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s10.png differ
diff --git a/examples/games/cards/s2.png b/examples/games/cards/s2.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s2.png differ
diff --git a/examples/games/cards/s3.png b/examples/games/cards/s3.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s3.png differ
diff --git a/examples/games/cards/s4.png b/examples/games/cards/s4.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s4.png differ
diff --git a/examples/games/cards/s5.png b/examples/games/cards/s5.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s5.png differ
diff --git a/examples/games/cards/s6.png b/examples/games/cards/s6.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s6.png differ
diff --git a/examples/games/cards/s7.png b/examples/games/cards/s7.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s7.png differ
diff --git a/examples/games/cards/s8.png b/examples/games/cards/s8.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s8.png differ
diff --git a/examples/games/cards/s9.png b/examples/games/cards/s9.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/s9.png differ
diff --git a/examples/games/cards/sj.png b/examples/games/cards/sj.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/sj.png differ
diff --git a/examples/games/cards/sk.png b/examples/games/cards/sk.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/sk.png differ
diff --git a/examples/games/cards/sq.png b/examples/games/cards/sq.png
new file mode 100644
Binary files /dev/null and b/examples/games/cards/sq.png differ
diff --git a/examples/games/freecell.hs b/examples/games/freecell.hs
new file mode 100644
--- /dev/null
+++ b/examples/games/freecell.hs
@@ -0,0 +1,338 @@
+{-# LANGUAGE DoRec #-}
+-- Package dependencies:
+--     random
+--     stb-image
+--     OpenGL
+--     GLUT
+import FRP.Sodium
+import Control.Applicative
+import Control.Monad
+import Data.Traversable (sequenceA)
+import Data.List
+import Data.Maybe
+import Engine
+import System.Random
+import System.FilePath
+import Data.Array.IArray as A
+import Data.Array.ST
+
+data Suit  = Spades | Clubs | Diamonds | Hearts
+             deriving (Eq, Ord, Show, Enum, Bounded)
+data Value = Ace | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King
+             deriving (Eq, Ord, Show, Enum, Bounded)
+data Card  = Card Value Suit
+             deriving (Eq, Ord, Show)
+
+instance Enum Card where
+    fromEnum (Card v s) = fromEnum v + fromEnum s * 13
+    toEnum i = Card (toEnum v) (toEnum s)
+      where
+        (s, v) = divMod i 13
+
+instance Bounded Card where
+    minBound = Card minBound minBound
+    maxBound = Card maxBound maxBound 
+
+noOfStacks :: Int
+noOfStacks = 8
+
+noOfCells :: Int
+noOfCells = 4
+
+cardSize :: Vector
+cardSize = (100,150)
+
+overlapY :: Double
+overlapY = 90
+
+data Location = Stack Int | Cell Int | Grave deriving (Eq, Show)
+
+data Bunch = Bunch {
+        buInitOrig     :: Point,
+        buInitMousePos :: Point,
+        buCards        :: [Card],
+        buOrigin       :: Location
+    }
+    deriving Show
+
+data Destination = Destination {
+        deLocation :: Location,
+        deDropZone :: Rect,
+        deMayDrop  :: [Card] -> Bool
+    }
+
+validSequence :: [Card] -> Bool
+validSequence xs = and $ zipWith follows xs (drop 1 xs)
+
+follows :: Card -> Card -> Bool
+follows one@(Card v1 _) two@(Card v2 _) = isRed one /= isRed two && (v1 /= Ace && pred v1 == v2)
+  where
+    isRed :: Card -> Bool
+    isRed (Card _ suit) = suit == Hearts || suit == Diamonds
+
+cardSpacing :: Double
+cardSpacing = (2000-cardWidth) / fromIntegral (noOfStacks-1)
+  where
+    (cardWidth, _) = cardSize
+
+cardSpacingNarrow :: Double
+cardSpacingNarrow = cardSpacing * 0.9
+
+topRow :: Double
+topRow = 1000 - 50 - cardHeight
+  where
+    (cardWidth, cardHeight) = cardSize
+
+draw :: Point -> Card -> Sprite
+draw pt (Card v s) = ((pt, cardSize), "cards" ++ [pathSeparator] ++ suitName s ++ valueName v ++ ".png")
+  where
+    suitName Spades = "s"
+    suitName Clubs = "c"
+    suitName Diamonds = "d"
+    suitName Hearts = "h"
+    valueName Ace = "1"
+    valueName Two = "2"
+    valueName Three = "3"
+    valueName Four = "4"
+    valueName Five = "5"
+    valueName Six = "6"
+    valueName Seven = "7"
+    valueName Eight = "8"
+    valueName Nine = "9"
+    valueName Ten = "10"
+    valueName Jack = "j"
+    valueName Queen = "q"
+    valueName King = "k"
+
+emptySpace :: Point -> Sprite
+emptySpace pt = ((pt, cardSize), "cards" ++ [pathSeparator] ++ "empty-space.png") 
+
+-- | The vertical stacks of cards, where cards can only be added if they're
+-- descending numbers and alternating red-black.
+stack :: Event MouseEvent -> [Card] -> Location -> Behavior Int -> Event [Card]
+      -> Reactive (Behavior [Sprite], Behavior Destination, Event Bunch)
+stack eMouse initCards loc@(Stack ix) freeSpaces eDrop = do
+    let (cardWidth, cardHeight) = cardSize
+        orig@(origX, origY) = (
+                (-1000) + cardWidth*0.5 + fromIntegral ix * cardSpacing,
+                300
+            )
+        positions = iterate (\(x, y) -> (x, y-overlapY)) orig
+    rec
+        cards <- hold initCards (eRemoveCards `merge` eAddCards)
+        let eAddCards = snapshotWith (\newCards cards -> cards ++ newCards) eDrop cards
+            eMouseSelection = filterJust $ snapshotWith (\mev cards ->
+                    case mev of
+                        MouseDown pt@(x, y) | x >= origX - cardWidth && x <= origX + cardWidth ->
+                            let n = length cards
+                                bottomY = (origY - cardHeight) - overlapY * fromIntegral (n-1) 
+                                ix = (length cards - 1) `min` floor (((origY + cardHeight) - y) / overlapY)
+                                (left, taken) = splitAt ix cards
+                            in  if ix >= 0 && y >= bottomY
+                                    then Just (left, Bunch (positions !! ix) pt taken loc)
+                                    else Nothing
+                        _ -> Nothing
+                ) eMouse cards
+            eRemoveCards = fst <$> eMouseSelection   -- Cards left over when we drag
+            eDrag        = snd <$> eMouseSelection   -- Cards removed when we drag
+    let sprites = map (uncurry draw) . zip positions <$> cards
+        dest = (\cards freeSpaces -> Destination {
+                    deLocation = loc,
+                    deDropZone = (orig `minus` (0, fromIntegral (length cards) * overlapY), cardSize),
+                    deMayDrop = \newCards ->
+                        validSequence newCards &&
+                        -- You get one card for free, but there must be free cells for any
+                        -- more than that.
+                        (length newCards - 1) <= freeSpaces &&
+                        case cards of
+                            [] -> True
+                            _  -> last cards `follows` head newCards
+                }
+            ) <$> cards <*> freeSpaces
+    return (sprites, dest, eDrag)
+
+-- | The "free cells" where cards can be temporarily put.
+cell :: Event MouseEvent -> Location -> Event [Card]
+     -> Reactive (Behavior [Sprite], Behavior Destination, Event Bunch, Behavior Int)
+cell eMouse loc@(Cell ix) eDrop = do
+    let (cardWidth, cardHeight) = cardSize
+        orig = ((-1000) + cardWidth*0.5 + fromIntegral ix * cardSpacingNarrow, topRow)
+        rect = (orig, cardSize)
+    rec
+        mCard <- hold Nothing $ eRemove `merge` (Just . head <$> eDrop)
+        let eMouseSelection = filterJust $ snapshotWith (\mev mCard ->
+                    case (mev, mCard) of
+                        (MouseDown pt, Just card) | pt `inside` rect ->
+                            Just (Nothing, Bunch (fst rect) pt [card] loc)
+                        _ -> Nothing
+                ) eMouse mCard
+            eRemove = fst <$> eMouseSelection
+            eDrag = snd <$> eMouseSelection
+    let sprites = ((:[]) . maybe (emptySpace orig) (draw orig)) <$> mCard
+        dest = (\mCard -> Destination {
+                deLocation = loc,
+                deDropZone = rect,
+                deMayDrop = \newCards -> length newCards == 1 && isNothing mCard
+            }) <$> mCard
+        emptySpaces = (\c -> if isNothing c then 1 else 0) <$> mCard
+    return (sprites, dest, eDrag, emptySpaces)
+
+-- | The place where the cards end up at the top right, aces first.
+grave :: Event MouseEvent -> Event [Card]
+      -> Reactive (Behavior [Sprite], Behavior Destination, Event Bunch)
+grave eMouse eDrop = do
+    let xOf ix = 1000 - cardWidth*0.5 - cardSpacingNarrow * fromIntegral (3-ix)
+        positions = map (\ix -> (xOf ix, topRow)) [0..3]
+        areas = zip positions (repeat cardSize)
+        (cardWidth, cardHeight) = cardSize
+        wholeRect = (((xOf 0 + xOf 3) * 0.5, topRow), ((cardSpacingNarrow * 3 + cardWidth*2) * 0.5, cardHeight))    
+    rec
+        let eDropModify = snapshotWith (\newCards slots ->
+                    let newCard@(Card _ suit) = head newCards
+                        ix = fromEnum suit
+                    in  take ix slots ++ [Just newCard] ++ drop (ix+1) slots 
+                ) eDrop slots
+        slots <- hold [Nothing, Nothing, Nothing, Nothing] (eDropModify `merge` eRemove)
+        let eMouseSelection = filterJust $ snapshotWith (\mev slots ->
+                    case mev of
+                        MouseDown pt ->
+                            let isIn = map (pt `inside`) areas
+                            in  case trueIxOf isIn of
+                                    Just ix ->
+                                        case slots !! ix of
+                                            Just card@(Card value suit) ->
+                                                let prevCard = if value == Ace then Nothing
+                                                                               else Just (Card (pred value) suit)
+                                                    slots' = take ix slots ++ [prevCard] ++ drop (ix+1) slots
+                                                in  Just (slots', Bunch (positions !! ix) pt [card] Grave)
+                                            Nothing -> Nothing
+                                    Nothing -> Nothing
+                        _ -> Nothing
+                ) eMouse slots
+            eRemove = fst <$> eMouseSelection
+            eDrag = snd <$> eMouseSelection
+    let sprites = zipWith (\pos mSlot ->
+                maybe (emptySpace pos) (draw pos) mSlot
+            ) positions <$> slots
+        dest = (\slots -> Destination {
+                deLocation = Grave,
+                deDropZone = wholeRect,
+                deMayDrop = \newCards -> case newCards of
+                    [card@(Card value suit)] ->
+                        let ix = fromEnum suit
+                        in  case slots !! ix of
+                                Just (Card topValue _) -> value == succ topValue
+                                Nothing                -> value == Ace 
+                    _                    -> False
+            }) <$> slots
+    return (sprites, dest, eDrag)
+  where
+    -- Index of first true item in the list
+    trueIxOf items = doit items 0
+      where
+        doit [] _ = Nothing
+        doit (x:xs) ix = if x then Just ix
+                              else doit xs (ix+1)
+
+-- | Draw the cards while they're being dragged.
+dragger :: Event MouseEvent -> Event Bunch -> Reactive (Behavior [Sprite], Event (Point, Bunch))
+dragger eMouse eStartDrag = do
+    dragPos <- hold (0,0) $ flip fmap eMouse $ \mev ->
+        case mev of
+            MouseUp   pt -> pt
+            MouseMove pt -> pt
+            MouseDown pt -> pt
+    rec
+        dragging <- hold Nothing $ (const Nothing <$> eDrop) `merge` (Just <$> eStartDrag)
+        let eDrop = filterJust $ snapshotWith (\mev mDragging ->
+                    case (mev, mDragging) of
+                        -- If the mouse is released, and we are dragging...
+                        (MouseUp pt, Just dragging) -> Just (cardPos pt dragging, dragging)
+                        _                           -> Nothing
+                ) eMouse dragging
+    let sprites = drawDraggedCards <$> dragPos <*> dragging
+          where
+            drawDraggedCards pt (Just bunch) =
+                let cpos = cardPos pt bunch
+                    positions = iterate (\(x, y) -> (x, y-overlapY)) cpos
+                in  zipWith draw positions (buCards bunch) 
+            drawDraggedCards _ Nothing = []
+    return (sprites, eDrop)
+  where
+    cardPos pt bunch = (pt `minus` buInitMousePos bunch) `plus` buInitOrig bunch
+
+-- | Determine where dropped cards are routed to.
+dropper :: Event (Point, Bunch) -> Behavior [Destination] -> Event (Location, [Card])
+dropper eDrop dests =
+    snapshotWith (\(pt, bunch) dests ->
+                -- If none of the destinations will accept the dropped cards, then send them
+                -- back where they originated from.
+                let findDest [] = (buOrigin bunch, buCards bunch)
+                    findDest (dest:rem) =
+                        if pt `inside` deDropZone dest && deMayDrop dest (buCards bunch)
+                            then (deLocation dest, buCards bunch)
+                            else findDest rem
+                in  findDest dests
+            ) eDrop dests
+
+distributeTo :: Event (Location, [Card]) -> [Location] -> [Event [Card]]
+distributeTo eWhere locations = flip map locations $ \thisLoc ->
+    filterJust $ (\(loc, cards) ->
+            if loc == thisLoc
+                then Just cards
+                else Nothing
+        ) <$> eWhere
+
+freecell :: [[Card]] -> Game
+freecell stackCards eMouse time = do
+    let stLocs = map Stack [0..noOfStacks-1]
+        ceLocs = map Cell [0..noOfCells-1]
+    rec
+        let eWhere = dropper eDrop (sequenceA (stDests ++ ceDests ++ [grDest]))
+            stDrops = eWhere `distributeTo` stLocs
+            ceDrops = eWhere `distributeTo` ceLocs
+            grDrops = eWhere `distributeTo` [Grave]
+        (stSprites, stDests, stDrags) <- unzip3 <$> forM (zip3 stLocs stackCards stDrops) (\(loc, cards, drop) ->
+            stack eMouse cards loc emptySpaces drop)
+        (ceSprites, ceDests, ceDrags, ceEmptySpaces) <- unzip4 <$> forM (zip ceLocs ceDrops) (\(loc, drop) ->
+            cell eMouse loc drop)
+        (grSprites, grDest, grDrag) <- grave eMouse (head grDrops)
+        -- The total number of empty spaces available in cells - 0 to 4. We need to
+        -- know this when we drop a stack of cards, because (the rules of the game say)
+        -- this is equivalent to temporarily putting all but one of them in cells.
+        let emptySpaces = foldr1 (\x y -> (+) <$> x <*> y) ceEmptySpaces
+        (drSprites, eDrop) <- dragger eMouse (foldr1 merge (stDrags ++ ceDrags ++ [grDrag]))
+    return $ concat <$> sequenceA (stSprites ++ ceSprites ++ [grSprites] ++ [drSprites])
+
+shuffle :: StdGen -> [Card] -> ([Card], StdGen)
+shuffle rng cards =
+    let n = length cards
+        (rng', ixes) = mapAccumL (\rng () ->
+                let (ix, rng') = randomR (0, n-1) rng
+                in  (rng', ix)) rng (replicate n ())
+        ary = runSTArray $ do
+            ary <- newListArray (0, n-1) cards
+            forM_ (zip [0..n-1] ixes) $ \(ix1, ix2) -> do
+                when (ix1 /= ix2) $ do
+                    one <- readArray ary ix1
+                    two <- readArray ary ix2
+                    writeArray ary ix1 two
+                    writeArray ary ix2 one
+            return ary
+    in  (A.elems ary, rng')
+
+toStacks :: Int -> [Card] -> [[Card]]
+toStacks noOfStacks cards = foldl (\stacks layer ->
+        zipWith (++) (map (:[]) layer ++ repeat []) stacks
+    ) (replicate noOfStacks []) (layerize cards)
+  where
+    layerize :: [Card] -> [[Card]]
+    layerize cards = case splitAt noOfStacks cards of
+        ([], _) -> []
+        (layer, rem) -> layer : layerize rem
+
+main = do
+    rng <- newStdGen
+    let (cards, rng') = shuffle rng [minBound..maxBound]
+    runGame "Freecell" (freecell (toStacks noOfStacks cards))
+
diff --git a/examples/games/poodle.hs b/examples/games/poodle.hs
new file mode 100644
--- /dev/null
+++ b/examples/games/poodle.hs
@@ -0,0 +1,109 @@
+{-# LANGUAGE DoRec, GeneralizedNewtypeDeriving #-}
+-- Package dependencies:
+--     random
+--     stb-image
+--     OpenGL
+--     GLUT
+import FRP.Sodium
+import Control.Applicative
+import Control.Monad.Trans
+import Data.Maybe
+import Engine
+import System.Random
+
+poodleSprite :: Point -> Sprite
+poodleSprite pt = ((pt,(120,120)), "poodle.png")
+
+-- | Active poodle logic (which could be made much more interesting).
+poodle :: PoodleID
+       -> Point
+       -> Event MouseEvent
+       -> Behaviour Double
+       -> Reactive (Behaviour (PoodleID, Sprite))
+poodle iD pos@(x0,y0) eMouse time = do
+    t0 <- sample time
+    let dt = subtract t0 <$> time
+        bounce dt =
+            let t = snd $ properFraction dt
+            in  800 * t + (-800) * t^2
+        sprite = (\dt -> (iD, poodleSprite (x0, y0 + bounce dt))) <$> dt
+    return sprite
+
+-- | Peel a new item off the list each time the event fires.
+peelList :: Event x -> [a] -> Reactive (Behaviour a)
+peelList ev xs0 =
+    hold (head xs0)
+            =<< collectE (\_ (x:xs) -> (x, xs)) (tail xs0) ev
+
+-- | Generate events at random intervals.
+randomTimes :: StdGen -> Behaviour Double -> Reactive (Event Double)
+randomTimes rng time = do
+    -- Infinite list of random intervals from 0.25 to 1.2 seconds.
+    let intervals = randomRs (0.25, 1.2) rng
+    rec
+        tLast <- hold 0 eAppear
+        interval <- peelList eAppear intervals
+        let eTime = values time
+            eAppear = filterJust $ snapshotWith (\t (tLast, interval) ->
+                    if t >= tLast + interval then Just t else Nothing
+                ) eTime ((,) <$> tLast <*> interval)
+    return eAppear
+
+newtype PoodleID = PoodleID Int deriving (Eq, Enum, Show)
+data Action = Create PoodleID (Behaviour (PoodleID, Sprite)) | Destroy PoodleID
+
+poodleGame :: StdGen -> Game
+poodleGame rng eMouse time = do
+
+    -- Random times for appearance of new poodles
+    let (rng1, rng2_) = split rng
+        (rng2, rng3) = split rng2_
+    eAppear <- do
+        randomTimes rng1 time
+
+    -- Pick a position for each new poodle
+    eNewPosition <- do
+        -- Infinite list of random poodle positions
+        let xs = randomRs (-900, 900) rng2
+            ys = randomRs (-900, 500) rng3
+        let idsAndPoses = zip [PoodleID 1..] (zip xs ys)
+        -- Peel an item off the list for each new poodle
+        collectE (\_ ((iD, pos):xs) -> ((iD, pos), xs)) idsAndPoses eAppear
+
+    -- Construct a new active poodle for each new position
+    let eCreations = execute $ (\(iD, pos) -> do
+                beh <- poodle iD pos eMouse time
+                return $ Create iD beh
+            ) <$> eNewPosition
+
+    rec
+        -- Destroy poodles that are clicked on
+        let eDestructions = filterJust $ snapshotWith (\mev poodles ->
+                    case mev of
+                        MouseDown clickPos -> listToMaybe
+                            [ Destroy iD | (iD, (rect, _)) <- poodles,
+                                   clickPos `inside` rect]
+                        _ -> Nothing
+                ) eMouse poodles
+
+        -- Handle creations and destructions giving a behaviour containing a
+        -- list of poodle behaviours.
+        poodleBehs <- hold [] =<< collectE (\change poodles ->
+                let poodles' = case change of
+                        Create iD beh -> (iD, beh) : poodles
+                        Destroy iD    -> filter (\(thisID, _) -> iD /= thisID) poodles
+                in  (map snd poodles', poodles')
+            ) [] (eCreations `merge` eDestructions)
+
+        -- Convert list of behaviours into a behaviour containing a list,
+        -- then flatten behaviour within behaviour down to a single behaviour of
+        -- poodle sprites.
+        poodles <- switch $ foldr (\ba bt -> (:) <$> ba <*> bt) (pure []) <$> poodleBehs
+
+    -- Return poodle sprites without their ids
+    return (map snd <$> poodles)
+
+main = do
+    rng <- newStdGen
+    runGame "Poodle invasion - click the poodles to keep them under control" (poodleGame rng)
+
diff --git a/examples/games/poodle.png b/examples/games/poodle.png
new file mode 100644
Binary files /dev/null and b/examples/games/poodle.png differ
diff --git a/examples/poodle/Engine.hs b/examples/poodle/Engine.hs
deleted file mode 100644
--- a/examples/poodle/Engine.hs
+++ /dev/null
@@ -1,177 +0,0 @@
-{-# LANGUAGE ScopedTypeVariables, EmptyDataDecls #-}
-module Engine where
-
-import FRP.Sodium
-
-import Control.Applicative
-import Control.Monad
-import Data.List
-import Graphics.Rendering.OpenGL as GL hiding (Triangle, Rect, translate)
-import qualified Graphics.Rendering.OpenGL as GL
-import qualified Graphics.UI.GLUT as GLUT hiding (Rect, translate)
-import Data.IORef
-import Data.Map (Map)
-import qualified Data.Map as M
-import Data.Maybe
-import System.Time
-import Debug.Trace
-
-import Image
-
-frameRate :: Num a => a
-frameRate = 40
-
-type Coord = Double
-type Point = (Coord, Coord)
-type Vector = (Coord, Coord)
-type Rect = (Point, Vector)   -- Central point and size from centre to edge
-type Sprite = (Rect, String)
-
-data MouseEvent = MouseDown Point | MouseMove Point | MouseUp Point
-    deriving Show
-
--- | True if the point is inside the rectangle
-inside :: Point -> Rect -> Bool
-inside (x, y) ((ox, oy), (wx, wy)) =
-    x >= ox - wx && x <= ox + wx &&
-    y >= oy - wy && y <= oy + wy
-
--- | True if the two rectangles overlap
-overlaps :: Rect -> Rect -> Bool
-overlaps ((x0, y0), (w0, h0)) ((x1, y1), (w1, h1)) =
-    let ax0 = x0 - w0
-        ay0 = y0 - h0
-        ax1 = x0 + w0
-        ay1 = y0 + h0
-        bx0 = x1 - w1
-        by0 = y1 - h1
-        bx1 = x1 + w1
-        by1 = y1 + h1
-    in ax1 > bx0 &&
-       ay1 > by0 &&
-       ax0 < bx1 &&
-       ay0 < by1
-
--- | Get system time in seconds since the start of the Unix epoch
--- (1 Jan 1970).
-getTime :: IO Double
-getTime = do
-    (TOD sec pico) <- getClockTime
-    return $!
-        (fromIntegral sec) +
-        (fromIntegral pico) / 1000000000000
-
--- | Game, which takes mouse event and time as input, and a list of sprites to draw
--- as output. Time is updated once per animation frame.
-type Game = Event MouseEvent -> Behaviour Double -> Reactive (Behaviour [Sprite])
-
-runGame :: String -> Game -> IO ()
-runGame title game = do
-
-    (eMouse, pushMouse) <- sync newEvent
-    (eTime, pushTime) <- sync newEvent
-    spritesRef <- newIORef []
-    _ <- sync $ do
-        time <- hold 0 eTime
-        sprites <- game eMouse time
-        listenValue sprites (writeIORef spritesRef)
-
-    _ <- GLUT.getArgsAndInitialize
-    GLUT.initialDisplayMode $= [GLUT.DoubleBuffered]
-    GLUT.createWindow title
-    GLUT.windowSize $= GLUT.Size 700 500
-    blend $= Enabled
-    blendFunc $= (SrcAlpha, OneMinusSrcAlpha)
-    multisample $= Enabled
-    shadeModel $= Smooth
-    polygonSmooth $= Enabled
-    hint PolygonSmooth $= Nicest
-    hint LineSmooth $= Nicest
-    normalize $= Enabled
-
-    texturesRef <- newIORef M.empty
-    t0 <- getTime
-    GLUT.displayCallback $= display texturesRef t0 pushTime spritesRef
-    let motion (GLUT.Position x y) = do
-            pt <- toScreen x y
-            sync $ pushMouse (MouseMove pt)
-    GLUT.motionCallback $= Just motion
-    GLUT.passiveMotionCallback $= Just motion
-    GLUT.keyboardMouseCallback $= Just (\key keyState mods pos -> do
-            case (key, keyState, pos) of
-                (GLUT.MouseButton GLUT.LeftButton, GLUT.Down, GLUT.Position x y) ->
-                    sync . pushMouse . MouseDown =<< toScreen x y
-                (GLUT.MouseButton GLUT.LeftButton, GLUT.Up,   GLUT.Position x y) ->
-                    sync . pushMouse . MouseUp   =<< toScreen x y
-                _ -> return ()
-        )
-    GLUT.addTimerCallback (1000 `div` frameRate) $ repaint
-    GLUT.mainLoop
-  where
-    toScreen :: GLint -> GLint -> IO (Coord, Coord)
-    toScreen x y = do
-        (_, Size w h) <- get viewport
-        let aspect = fromIntegral w / fromIntegral h
-            sx = 0.001/aspect
-            sy = 0.001
-            xx = 2 * ((fromIntegral x / fromIntegral w) - 0.5) / sx
-            yy = 2 * (0.5 - (fromIntegral y / fromIntegral h)) / sy
-        return (xx, yy)
-    repaint = do
-        GLUT.postRedisplay Nothing
-        GLUT.addTimerCallback (1000 `div` frameRate) $ repaint
-
-    period = 1 / frameRate
-
-    display :: IORef (Map String (TextureImage, TextureObject))
-            -> Double
-            -> (Double -> Reactive ())
-            -> IORef [Sprite]
-            -> IO ()
-    display texturesRef t0 pushTime spritesRef = do
-
-        t <- subtract t0 <$> getTime
-        sync $ pushTime t
-
-        sprites <- readIORef spritesRef
-
-        clearColor $= Color4 0 0 0 (1 :: GLclampf)
-        --clearColor $= Color4 0.1 0.1 0.15 (1 :: GLclampf)
-        clear [ColorBuffer{-, DepthBuffer-}]
-        loadIdentity
-
-        (_, Size w h) <- get viewport
-        let aspect = fromIntegral w / fromIntegral h
-        scale (0.001/aspect) 0.001 (0.001 :: GLfloat)
-
-        forM_ sprites $ \(((posX, posY),(sizeX, sizeY)),imgFile) -> do
-            textures <- readIORef texturesRef
-            (TextureImage iWidth iHeight pWidth pHeight _ _, to) <- case imgFile `M.lookup` textures of
-                Just (ti, to) -> return (ti, to)
-                Nothing       -> do
-                    ti <- loadTexture imgFile False
-                    to <- createTexture ti
-                    modifyIORef texturesRef (M.insert imgFile (ti, to))
-                    return $ (ti, to)
-
-            preservingMatrix $ do
-                texture Texture2D $= Enabled
-                textureBinding Texture2D $= Just to
-                GL.translate $ Vector3 (realToFrac posX) (realToFrac posY) (0 :: GLdouble)
-                let w2 = realToFrac sizeX :: GLdouble
-                    h2 = realToFrac sizeY :: GLdouble
-                    cx = realToFrac pWidth / realToFrac iWidth :: GLfloat
-                    cy = realToFrac pHeight / realToFrac iHeight :: GLfloat
-                renderPrimitive Polygon $ do
-                    texCoord $ TexCoord2 0 cy
-                    vertex $ Vertex2 (-w2) (-h2)
-                    texCoord $ TexCoord2 cx cy
-                    vertex $ Vertex2 w2 (-h2)
-                    texCoord $ TexCoord2 cx 0
-                    vertex $ Vertex2 w2 h2
-                    texCoord $ TexCoord2 0 (0 :: GLfloat)
-                    vertex $ Vertex2 (-w2) h2
-                texture Texture2D $= Disabled
-            --translate $ Vector3 0 0 (0.001 :: GLdouble)
-
-        GLUT.swapBuffers
diff --git a/examples/poodle/Image.hs b/examples/poodle/Image.hs
deleted file mode 100644
--- a/examples/poodle/Image.hs
+++ /dev/null
@@ -1,134 +0,0 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-module Image where
-
-import Data.ByteString (ByteString)     
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Internal as B
-import qualified Data.ByteString.Unsafe as B
-import Graphics.Rendering.OpenGL as GL hiding (RGB, RGBA)
-import qualified Graphics.Rendering.OpenGL as GL
-import Codec.Image.STB
-import Data.Bitmap.IO
-import Data.Char
-import Control.Exception
-import Control.Monad
-import Data.Typeable
-import Data.Word
-import Foreign
-import Foreign.C
-
-
-data Format = RGB | RGBA deriving (Eq, Show)
-
-data TextureImage_ d = TextureImage !Int !Int !Int !Int Format d deriving Show
-type TextureImage = TextureImage_ ByteString
-data ImageException = ImageException String
-    deriving Typeable
-instance Exception ImageException
-instance Show ImageException where
-    showsPrec _ (ImageException err) = ("ImageException: "++) . (err++)
-
-loadTexture :: FilePath
-            -> Bool               -- ^ True to invert it
-            -> IO TextureImage
-loadTexture path invert = do
-        eImg <- loadImage path
-        img <- case eImg of
-            Left err -> throwIO $ ImageException $
-                "Failed to load image file "++path++": "++err
-            Right img -> return img
-        withBitmap img $ \(width, height) components padding imgData0 -> do
-            fmt <- case components of
-                3 -> return RGB
-                4 -> return RGBA
-                _ -> throwIO $ ImageException $
-                    "Failed to load image file "++path++": we only support RGB images"
-            let len = width * height * components
-            imgData <- mallocBytes len
-            B.memcpy imgData imgData0 (fromIntegral len)
-            when invert $ do
-                let row = width * bytesPerPixel fmt
-                    row_sz = fromIntegral row
-                allocaBytes row $ \tempRow -> do
-                    forM_ [0..height `div` 2] $ \y -> do
-                        let r0 = row * y
-                            s0 = row * (height - 1 - y)
-                        B.memcpy tempRow (imgData `plusPtr` r0) row_sz
-                        B.memcpy (imgData `plusPtr` r0) (imgData `plusPtr` s0) row_sz
-                        B.memcpy (imgData `plusPtr` s0) tempRow row_sz
-            let tex = TextureImage width height width height fmt imgData
-            transparent <- hasTransparency tex
-            tex' <- if transparent then return tex else removeAlphaChannel tex
-            unsafeTextureToBS tex'
-
--- | Converts a buffer-based Texture to to a ByteString-based one.  Frees
--- the input byte buffer.
-unsafeTextureToBS :: TextureImage_ (Ptr Word8) -> IO TextureImage
-unsafeTextureToBS (TextureImage iWidth iHeight pWidth pHeight fmt buf) = do
-    let bpp = bytesPerPixel fmt
-        sz = bpp * iWidth * iHeight
-    bytes <- peekArray sz buf
-    free buf
-    return $ TextureImage iWidth iHeight pWidth pHeight fmt (B.pack bytes)
-
-bytesPerPixel :: Format -> Int
-bytesPerPixel RGB = 3
-bytesPerPixel RGBA = 4
-
-hasTransparency :: TextureImage_ (Ptr Word8) -> IO Bool
-hasTransparency (TextureImage _ _ _ _ RGB _) = return False
-hasTransparency (TextureImage width height _ _ fmt@RGBA imgData) = h 0
-  where
-    limit = width * height
-    bpp = bytesPerPixel fmt
-    h ix | ix >= limit = return False
-    h ix = do
-        alpha <- peekByteOff imgData (ix * bpp + 3) :: IO Word8
-        if alpha < 255
-            then return True
-            else h (ix + 1)
-
-removeAlphaChannel :: TextureImage_ (Ptr Word8) -> IO (TextureImage_ (Ptr Word8))
-removeAlphaChannel tex@(TextureImage _ _ _ _ RGB _) = return tex
-removeAlphaChannel (TextureImage iWidth iHeight pWidth pHeight fmt@RGBA imgData) = do
-    forM_ [1..limit-1] $ \ix -> do
-        r <- peekByteOff imgData (ix * bpp) :: IO Word8
-        pokeByteOff imgData (ix * 3) r
-        g <- peekByteOff imgData (ix * bpp + 1) :: IO Word8
-        pokeByteOff imgData (ix * 3 + 1) g
-        b <- peekByteOff imgData (ix * bpp + 2) :: IO Word8
-        pokeByteOff imgData (ix * 3 + 2) b
-    return (TextureImage iWidth iHeight pWidth pHeight RGB imgData)
-  where
-    limit = iWidth * iHeight
-    bpp = bytesPerPixel fmt
-
--- | Convert texture data into an OpenGL handle
-createTexture :: TextureImage
-              -> IO TextureObject
-createTexture (TextureImage iWid iHt _ _ fmt imgBS) = do
-    [texName] <- genObjectNames 1  -- generate our texture.
-    --rowAlignment  Unpack $= 1
-    textureBinding Texture2D $= Just texName  -- make our new texture the current texture.
-    generateMipmap Texture2D $= Enabled
-    B.unsafeUseAsCStringLen imgBS $ \(buf, len) -> do
-        let glFmt = case fmt of
-                RGB  -> GL.RGB
-                RGBA -> GL.RGBA
-            pixels = PixelData glFmt UnsignedByte buf
-        build2DMipmaps Texture2D RGBA' (fromIntegral iWid) (fromIntegral iHt) pixels
-    GL.textureFilter GL.Texture2D $= ((GL.Linear', Nothing), GL.Linear')
-    return texName
-
--- | Ensure this label can be a valid filename
-cleanLabel :: String -> String
-cleanLabel =
-    scrunch .
-    map (\c -> if (c >= 'a' && c <= 'z') ||
-                  (c >= 'A' && c <= 'Z') ||
-                  (c >= '0' && c <= '9') then c else '_')
-  where
-    scrunch ('_':'_':cs) = scrunch ('_':cs)
-    scrunch (c:cs) = c:scrunch cs
-    scrunch [] = []
-
diff --git a/examples/poodle/poodle.hs b/examples/poodle/poodle.hs
deleted file mode 100644
--- a/examples/poodle/poodle.hs
+++ /dev/null
@@ -1,109 +0,0 @@
-{-# LANGUAGE DoRec, GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
--- Package dependencies:
---     random
---     stb-image
---     OpenGL
---     GLUT
-import FRP.Sodium
-import Control.Applicative
-import Control.Monad.Trans
-import Data.Maybe
-import Engine
-import System.Random
-
-poodleSprite :: Point -> Sprite
-poodleSprite pt = ((pt,(120,120)), "poodle.png")
-
--- | Active poodle logic (which could be made much more interesting).
-poodle :: PoodleID
-       -> Point
-       -> Event MouseEvent
-       -> Behaviour Double
-       -> Reactive (Behaviour (PoodleID, Sprite))
-poodle iD pos@(x0,y0) eMouse time = do
-    t0 <- sample time
-    let dt = subtract t0 <$> time
-        bounce dt =
-            let t = snd $ properFraction dt
-            in  800 * t + (-800) * t^2
-        sprite = (\dt -> (iD, poodleSprite (x0, y0 + bounce dt))) <$> dt
-    return sprite
-
--- | Peel a new item off the list each time the event fires.
-peelList :: Event x -> [a] -> Reactive (Behaviour a)
-peelList ev xs0 =
-    hold (head xs0)
-            =<< collectE (\_ (x:xs) -> (x, xs)) (tail xs0) ev
-
--- | Generate events at random intervals.
-randomTimes :: StdGen -> Behaviour Double -> Reactive (Event Double)
-randomTimes rng time = do
-    -- Infinite list of random intervals from 0.25 to 1.2 seconds.
-    let intervals = randomRs (0.25, 1.2) rng
-    rec
-        tLast <- hold 0 eAppear
-        interval <- peelList eAppear intervals
-        let eTime = values time
-            eAppear = filterJust $ snapshotWith (\t (tLast, interval) ->
-                    if t >= tLast + interval then Just t else Nothing
-                ) eTime ((,) <$> tLast <*> interval)
-    return eAppear
-
-newtype PoodleID = PoodleID Int deriving (Eq, Enum, Show)
-data Action = Create PoodleID (Behaviour (PoodleID, Sprite)) | Destroy PoodleID
-
-poodleGame :: StdGen -> Game
-poodleGame rng eMouse time = do
-
-    -- Random times for appearance of new poodles
-    let (rng1, rng2_) = split rng
-        (rng2, rng3) = split rng2_
-    eAppear <- do
-        randomTimes rng1 time
-
-    -- Pick a position for each new poodle
-    eNewPosition <- do
-        -- Infinite list of random poodle positions
-        let xs = randomRs (-900, 900) rng2
-            ys = randomRs (-900, 500) rng3
-        let idsAndPoses = zip [PoodleID 1..] (zip xs ys)
-        -- Peel an item off the list for each new poodle
-        collectE (\_ ((iD, pos):xs) -> ((iD, pos), xs)) idsAndPoses eAppear
-
-    -- Construct a new active poodle for each new position
-    let eCreations = execute $ (\(iD, pos) -> do
-                beh <- poodle iD pos eMouse time
-                return $ Create iD beh
-            ) <$> eNewPosition
-
-    rec
-        -- Destroy poodles that are clicked on
-        let eDestructions = filterJust $ snapshotWith (\mev poodles ->
-                    case mev of
-                        MouseDown clickPos -> listToMaybe
-                            [ Destroy iD | (iD, (rect, _)) <- poodles,
-                                   clickPos `inside` rect]
-                        _ -> Nothing
-                ) eMouse poodles
-
-        -- Handle creations and destructions giving a behaviour containing a
-        -- list of poodle behaviours.
-        poodleBehs <- hold [] =<< collectE (\change poodles ->
-                let poodles' = case change of
-                        Create iD beh -> (iD, beh) : poodles
-                        Destroy iD    -> filter (\(thisID, _) -> iD /= thisID) poodles
-                in  (map snd poodles', poodles')
-            ) [] (eCreations `merge` eDestructions)
-
-        -- Convert list of behaviours into a behaviour containing a list,
-        -- then flatten behaviour within behaviour down to a single behaviour of
-        -- poodle sprites.
-        poodles <- switch $ foldr (\ba bt -> (:) <$> ba <*> bt) (pure []) <$> poodleBehs
-
-    -- Return poodle sprites without their ids
-    return (map snd <$> poodles)
-
-main = do
-    rng <- newStdGen
-    runGame "Poodle invasion - click the poodles to keep them under control" (poodleGame rng)
-
diff --git a/examples/poodle/poodle.png b/examples/poodle/poodle.png
deleted file mode 100644
Binary files a/examples/poodle/poodle.png and /dev/null differ
diff --git a/examples/tests.hs b/examples/tests.hs
--- a/examples/tests.hs
+++ b/examples/tests.hs
@@ -76,11 +76,24 @@
         push '3'
     assertEqual "filterE1" "23" =<< readIORef outRef
 
+gate1 = TestCase $ do
+    (c, pushc) <- sync newEvent
+    (pred, pushPred) <- sync $ newBehavior True
+    outRef <- newIORef []
+    unlisten <- sync $ listen (gate c pred) $ \a -> modifyIORef outRef (++ [a])
+    sync $ pushc 'H'
+    sync $ pushPred False
+    sync $ pushc 'O'
+    sync $ pushPred True
+    sync $ pushc 'I'
+    unlisten
+    assertEqual "gate1" "HI" =<< readIORef outRef
+
 beh1 = TestCase $ do
     outRef <- newIORef []
     (push, unlisten) <- sync $ do
         (beh, push) <- newBehavior "init"
-        unlisten <- listenValue beh $ \a -> modifyIORef outRef (++ [a])
+        unlisten <- listen (values beh) $ \a -> modifyIORef outRef (++ [a])
         return (push, unlisten)
     sync $ do
         push "next"
@@ -91,7 +104,7 @@
     outRef <- newIORef []
     (push, unlisten) <- sync $ do
         (beh, push) <- newBehavior "init"
-        unlisten <- listenValue beh $ \a -> modifyIORef outRef (++ [a])
+        unlisten <- listen (values beh) $ \a -> modifyIORef outRef (++ [a])
         return (push, unlisten)
     unlisten
     sync $ do
@@ -102,7 +115,7 @@
     outRef <- newIORef []
     (push, unlisten) <- sync $ do
         (beh, push) <- newBehavior "init"
-        unlisten <- listenValue beh $ \a -> modifyIORef outRef (++ [a])
+        unlisten <- listen (values beh) $ \a -> modifyIORef outRef (++ [a])
         return (push, unlisten)
     sync $ do
         push "first"
@@ -116,7 +129,7 @@
     outRef <- newIORef []
     (push, unlisten) <- sync $ do
         (beh, push) <- newBehavior "init"
-        unlisten <- listenValue beh $ \a -> modifyIORef outRef (++ [a])
+        unlisten <- listen (values beh) $ \a -> modifyIORef outRef (++ [a])
         push "other"
         return (push, unlisten)
     sync $ do
@@ -139,21 +152,137 @@
     unlisten
     assertEqual "beh5" ["OTHER", "SECOND"] =<< readIORef outRef
 
-appl1 = TestCase $ do
-    (ea, pusha) <- sync newEvent
-    ba <- sync $ hold 0 ea
-    (eb, pushb) <- sync newEvent
-    bb <- sync $ hold 0 eb
-    let esum = (+) <$> ba <*> bb
+behConstant = TestCase $ do
     outRef <- newIORef []
-    unlisten <- sync $ listenValue esum $ \sum -> modifyIORef outRef (++ [sum])
-    sync $ pusha 5
-    sync $ pushb 100
-    sync $ pusha 10 >> pushb 200
+    unlisten <- sync $ listen (values $ pure 'X') $ \a -> modifyIORef outRef (++ [a])
     unlisten
-    assertEqual "appl1" [0, 5, 105, 210] =<< readIORef outRef
+    assertEqual "behConstant" ['X'] =<< readIORef outRef
 
-appl2 = TestCase $ do  -- variant that uses listen (valueEvent esum) instead of listenValue
+valuesThenMap = TestCase $ do
+    (b, push) <- sync $ newBehavior 9
+    outRef <- newIORef []
+    unlisten <- sync $ listen (values . fmap (+100) $ b) $ \a -> modifyIORef outRef (++ [a])
+    sync $ push (2 :: Int)
+    sync $ push 7
+    unlisten
+    assertEqual "valuesThenMap" [109,102,107] =<< readIORef outRef 
+
+-- | This is used for tests where values() produces a single initial value on listen,
+-- and then we double that up by causing that single initial event to be repeated.
+-- This needs testing separately, because the code must be done carefully to achieve
+doubleUp :: Event a -> Event a
+doubleUp e = merge e e
+
+valuesTwiceThenMap = TestCase $ do
+    (b, push) <- sync $ newBehavior 9
+    outRef <- newIORef []
+    unlisten <- sync $ listen (doubleUp . values . fmap (+100) $ b) $ \a -> modifyIORef outRef (++ [a])
+    sync $ push (2 :: Int)
+    sync $ push 7
+    unlisten
+    assertEqual "valuesThenMap" [109,109,102,102,107,107] =<< readIORef outRef 
+    
+valuesThenCoalesce = TestCase $ do
+    (b, push) <- sync $ newBehavior 9
+    outRef <- newIORef []
+    unlisten <- sync $ listen (coalesce (\_ x -> x) . values $ b) $ \a -> modifyIORef outRef (++ [a])
+    sync $ push 2
+    sync $ push 7
+    unlisten
+    assertEqual "valuesThenCoalesce" [9,2,7] =<< readIORef outRef
+
+valuesTwiceThenCoalesce = TestCase $ do
+    (b, push) <- sync $ newBehavior 9
+    outRef <- newIORef []
+    unlisten <- sync $ listen (coalesce (+) . doubleUp. values $ b) $ \a -> modifyIORef outRef (++ [a])
+    sync $ push 2
+    sync $ push 7
+    unlisten
+    assertEqual "valuesThenCoalesce" [18,4,14] =<< readIORef outRef
+
+valuesThenSnapshot = TestCase $ do
+    (bi, pushi) <- sync $ newBehavior (9 :: Int)
+    (bc, pushc) <- sync $ newBehavior 'a'
+    outRef <- newIORef []
+    unlisten <- sync $ listen (flip snapshot bc . values $ bi) $ \a -> modifyIORef outRef (++ [a])
+    sync $ pushc 'b'
+    sync $ pushi 2
+    sync $ pushc 'c'
+    sync $ pushi 7
+    unlisten
+    assertEqual "valuesThenSnapshot" ['a','b','c'] =<< readIORef outRef
+
+valuesTwiceThenSnapshot = TestCase $ do
+    (bi, pushi) <- sync $ newBehavior (9 :: Int)
+    (bc, pushc) <- sync $ newBehavior 'a'
+    outRef <- newIORef []
+    unlisten <- sync $ listen (flip snapshot bc . doubleUp . values $ bi) $ \a -> modifyIORef outRef (++ [a])
+    sync $ pushc 'b'
+    sync $ pushi 2
+    sync $ pushc 'c'
+    sync $ pushi 7
+    unlisten
+    assertEqual "valuesThenSnapshot" ['a','a','b','b','c','c'] =<< readIORef outRef
+
+valuesThenMerge = TestCase $ do
+    (bi, pushi) <- sync $ newBehavior (9 :: Int)
+    (bj, pushj) <- sync $ newBehavior (2 :: Int)
+    outRef <- newIORef []
+    unlisten <- sync $ listen (mergeWith (+) (values bi) (values bj)) $ \a -> modifyIORef outRef (++ [a])
+    sync $ pushi 1
+    sync $ pushj 4
+    unlisten
+    assertEqual "valuesThenMerge" [11,1,4] =<< readIORef outRef 
+
+valuesThenFilter = TestCase $ do
+    (b, push) <- sync $ newBehavior (9 :: Int)
+    outRef <- newIORef []
+    unlisten <- sync $ listen (filterE (const True) . values $ b) $ \a -> modifyIORef outRef (++ [a])
+    sync $ push 2
+    sync $ push 7
+    unlisten
+    assertEqual "valuesThenFilter" [9,2,7] =<< readIORef outRef
+
+valuesTwiceThenFilter = TestCase $ do
+    (b, push) <- sync $ newBehavior (9 :: Int)
+    outRef <- newIORef []
+    unlisten <- sync $ listen (filterE (const True) . doubleUp . values $ b) $ \a -> modifyIORef outRef (++ [a])
+    sync $ push 2
+    sync $ push 7
+    unlisten
+    assertEqual "valuesThenFilter" [9,9,2,2,7,7] =<< readIORef outRef
+
+valuesThenOnce = TestCase $ do
+    (b, push) <- sync $ newBehavior (9 :: Int)
+    outRef <- newIORef []
+    unlisten <- sync $ listen (once . values $ b) $ \a -> modifyIORef outRef (++ [a])
+    sync $ push 2
+    sync $ push 7
+    unlisten
+    assertEqual "valuesThenOnce" [9] =<< readIORef outRef
+
+valuesTwiceThenOnce = TestCase $ do
+    (b, push) <- sync $ newBehavior (9 :: Int)
+    outRef <- newIORef []
+    unlisten <- sync $ listen (once . doubleUp . values $ b) $ \a -> modifyIORef outRef (++ [a])
+    sync $ push 2
+    sync $ push 7
+    unlisten
+    assertEqual "valuesThenOnce" [9] =<< readIORef outRef
+    
+-- | Test values being "executed" before listen. Somewhat redundant since this is
+-- Haskell and "values b" is pure.
+valuesLateListen = TestCase $ do
+    (b, push) <- sync $ newBehavior (9 :: Int)
+    outRef <- newIORef []
+    let bv = values b
+    sync $ push 8
+    unlisten <- sync $ listen bv $ \a -> modifyIORef outRef (++ [a])
+    sync $ push 2
+    unlisten
+    assertEqual "valuesLateListen" [8,2] =<< readIORef outRef
+
+appl1 = TestCase $ do
     (ea, pusha) <- sync newEvent
     ba <- sync $ hold 0 ea
     (eb, pushb) <- sync newEvent
@@ -165,7 +294,7 @@
     sync $ pushb 100
     sync $ pusha 10 >> pushb 200
     unlisten
-    assertEqual "appl2" [0, 5, 105, 210] =<< readIORef outRef
+    assertEqual "appl1" [0, 5, 105, 210] =<< readIORef outRef
     
 snapshot1 = TestCase $ do
     (ea, pusha) <- sync newEvent
@@ -182,6 +311,17 @@
     unlisten
     assertEqual "snapshot1" [('A',0),('B',50),('C',50),('D',60)] =<< readIORef outRef
 
+holdIsDelayed = TestCase $ do
+    (e, push) <- sync newEvent
+    h <- sync $ hold (0 :: Int) e
+    let pair = snapshotWith (\a b -> show a ++ " " ++ show b) e h
+    outRef <- newIORef []
+    unlisten <- sync $ listen pair $ \a -> modifyIORef outRef (++ [a])
+    sync $ push 2
+    sync $ push 3
+    unlisten
+    assertEqual "holdIsDelayed" ["2 0", "3 2"] =<< readIORef outRef
+
 count1 = TestCase $ do
     (ea, push) <- sync newEvent
     outRef <- newIORef []
@@ -200,7 +340,7 @@
     unlisten <- sync $ do
         ba <- hold 100 ea
         sum <- collect (\a s -> (a+s, a+s)) 0 ba
-        listenValue sum $ \sum -> modifyIORef outRef (++ [sum])
+        listen (values sum) $ \sum -> modifyIORef outRef (++ [sum])
     sync $ push 5
     sync $ push 7
     sync $ push 1
@@ -216,7 +356,7 @@
         (ba, push) <- newBehavior 100
         sum <- collect (\a s -> (a + s, a + s)) 0 ba
         push 5
-        unlisten <- listenValue sum $ \sum -> modifyIORef outRef (++ [sum])
+        unlisten <- listen (values sum) $ \sum -> modifyIORef outRef (++ [sum])
         return (unlisten, push)
     sync $ push 7
     sync $ push 1
@@ -276,17 +416,14 @@
     assertEqual "switchE1" "ABCdeFGhI" =<< readIORef outRef
 
 switch1 = TestCase $ do
-    (ea, pusha) <- sync newEvent
-    (eb, pushb) <- sync newEvent
-    (esw, pushsw) <- sync newEvent
     outRef <- newIORef []
-    (ba, bb, unlisten) <- sync $ do
-        ba <- hold 'A' ea
-        bb <- hold 'a' eb
-        bsw <- hold ba esw
+    (ba, bb, pusha, pushb, pushsw, unlisten) <- sync $ do
+        (ba, pusha) <- newBehavior 'A'
+        (bb, pushb) <- newBehavior 'a'
+        (bsw, pushsw) <- newBehavior ba
         bo <- switch bsw
-        unlisten <- listenValue bo $ \o -> modifyIORef outRef (++ [o])
-        return (ba, bb, unlisten)
+        unlisten <- listen (values bo) $ \o -> modifyIORef outRef (++ [o])
+        return (ba, bb, pusha, pushb, pushsw, unlisten)
     sync $ pusha 'B' >> pushb 'b'
     sync $ pushsw bb >> pusha 'C' >> pushb 'c'
     sync $ pusha 'D' >> pushb 'd'
@@ -304,8 +441,7 @@
     (ea, pusha) <- sync newEvent
     outRef <- newIORef []
     unlisten <- sync $ do
-        oea <- once ea
-        listen oea $ \a -> modifyIORef outRef (++ [a])
+        listen (once ea) $ \a -> modifyIORef outRef (++ [a])
     sync $ pusha 'A'
     sync $ pusha 'B'
     sync $ pusha 'C'
@@ -316,59 +452,13 @@
     (ea, pusha) <- sync newEvent
     outRef <- newIORef []
     unlisten <- sync $ do
-        oea <- once ea
         pusha 'A'
-        listen oea $ \a -> modifyIORef outRef (++ [a])
+        listen (once ea) $ \a -> modifyIORef outRef (++ [a])
     sync $ pusha 'B'
     sync $ pusha 'C'
     unlisten
     assertEqual "switch1" "A" =<< readIORef outRef
 
-{-
-crossE1 = TestCase $ do
-    outRef <- newIORef []
-    (ema :: Event Plain Char, push) <- newEvent
-    (ena :: Event N Char) <- sync $ crossE ema
-    unlisten <- sync $ listen ena $ \a -> modifyIORef outRef (++ [a])
-    sync $ push 'A'
-    sync $ push 'M'
-    sync $ push 'T'
-    -- Flush processing on partition N before unlistening
-    sync (return () :: Reactive N ())
-    unlisten
-    assertEqual "crossE1" "AMT" =<< readIORef outRef
-
-cross1 = TestCase $ do
-    outRef <- newIORef []
-    (ema :: Event Plain Char, push) <- newEvent
-    bma <- sync $ hold 'A' ema
-    sync $ push 'B'
-    (bna :: Behavior N Char) <- sync $ cross bma
-    unlisten <- sync $ listenValue bna $ \a -> modifyIORef outRef (++ [a])
-    sync $ push 'C'
-    sync $ push 'D'
-    sync $ push 'E'
-    -- Flush processing on partition N before unlistening
-    sync (return () :: Reactive N ())
-    unlisten
-    assertEqual "cross1" "BCDE" =<< readIORef outRef
-
-cross2 = TestCase $ do
-    outRef <- newIORef []
-    (ema :: Event Plain Char, push) <- newEvent
-    bma <- sync $ hold 'A' ema
-    (bna :: Behavior N Char) <- sync $ cross bma
-    unlisten <- sync $ listenValue bna $ \a -> modifyIORef outRef (++ [a])
-    sync $ push 'B'
-    sync $ push 'C'
-    sync $ push 'D'
-    sync $ push 'E'
-    -- Flush processing on partition N before unlistening
-    sync (return () :: Reactive N ())
-    unlisten
-    assertEqual "cross1" "ABCDE" =<< readIORef outRef
--}
-
 data Page = Page { unPage :: Reactive (Char, Event Page) }
 
 cycle1 = TestCase $ do
@@ -380,7 +470,7 @@
             bPair <- hold initPair ePage
             let ePage = execute $ unPage <$> switchE (snd <$> bPair)
         return (fst <$> bPair)
-    unlisten <- sync $ listenValue bo $ \o -> modifyIORef outRef (++ [o])
+    unlisten <- sync $ listen (values bo) $ \o -> modifyIORef outRef (++ [o])
     sync $ push (Page $ return ('b', ep))
     sync $ push (Page $ return ('c', ep))
     unlisten
@@ -436,9 +526,12 @@
     unlisten
     assertEqual "coalesce1" [2, 11] =<< readIORef outRef
 
-tests = test [ event1, fmap1, merge1, filterJust1, filterE1, beh1, beh2, beh3, beh4, beh5,
-    appl1, appl2, snapshot1, count1, collect1, collect2, collectE1, collectE2, switchE1,
-    switch1, once1, once2, {-crossE1, cross1, cross2,-} cycle1, mergeWith1, mergeWith2, mergeWith3,
+tests = test [ event1, fmap1, merge1, filterJust1, filterE1, gate1, beh1, beh2, beh3, beh4, beh5,
+    behConstant, valuesThenMap, valuesTwiceThenMap, valuesThenCoalesce, valuesTwiceThenCoalesce,
+    valuesThenSnapshot, valuesTwiceThenSnapshot, valuesThenMerge, valuesThenFilter,
+    valuesTwiceThenFilter, valuesThenOnce, valuesTwiceThenOnce, valuesLateListen,
+    holdIsDelayed, appl1, snapshot1, count1, collect1, collect2, collectE1, collectE2, switchE1,
+    switch1, once1, once2, cycle1, mergeWith1, mergeWith2, mergeWith3,
     coalesce1 ]
 
 main = {-forever $-} runTestTT tests
diff --git a/sodium.cabal b/sodium.cabal
--- a/sodium.cabal
+++ b/sodium.cabal
@@ -1,5 +1,5 @@
 name:                sodium
-version:             0.4.0.0
+version:             0.5.0.0
 synopsis:            Sodium Reactive Programming (FRP) System
 description:         
   A general purpose Reactive Programming (FRP) system. This is part of a project to
@@ -22,7 +22,8 @@
   .
   Changes: 0.2.0.0 fix some value recursion deadlocks and improve docs;
            0.3.0.0 add mergeWith, make cross asynchronous;
-           0.4.0.0 API revamp to remove an excess type variable. Parallelism stuff to be rethought.
+           0.4.0.0 API revamp to remove an excess type variable. Parallelism stuff to be rethought;
+           0.5.0.0 Improved tests cases + add Freecell example, API tweaks.
 license:             BSD3
 license-file:        LICENSE
 author:              Stephen Blackheath
@@ -32,10 +33,81 @@
 build-type:          Simple
 cabal-version:       >=1.8
 extra-source-files:  examples/tests.hs
-                     examples/poodle/poodle.hs
-                     examples/poodle/Engine.hs
-                     examples/poodle/Image.hs
-                     examples/poodle/poodle.png
+                     examples/games/poodle.hs
+                     examples/games/freecell.hs
+                     examples/games/Engine.hs
+                     examples/games/Image.hs
+                     examples/games/poodle.png
+                     examples/games/cards/b1fh.png
+                     examples/games/cards/b1fv.png
+                     examples/games/cards/b1pb.png
+                     examples/games/cards/b1pl.png
+                     examples/games/cards/b1pr.png
+                     examples/games/cards/b1pt.png
+                     examples/games/cards/b2fh.png
+                     examples/games/cards/b2fv.png
+                     examples/games/cards/b2pb.png
+                     examples/games/cards/b2pl.png
+                     examples/games/cards/b2pr.png
+                     examples/games/cards/b2pt.png
+                     examples/games/cards/c10.png
+                     examples/games/cards/c1.png
+                     examples/games/cards/c2.png
+                     examples/games/cards/c3.png
+                     examples/games/cards/c4.png
+                     examples/games/cards/c5.png
+                     examples/games/cards/c6.png
+                     examples/games/cards/c7.png
+                     examples/games/cards/c8.png
+                     examples/games/cards/c9.png
+                     examples/games/cards/cj.png
+                     examples/games/cards/ck.png
+                     examples/games/cards/cq.png
+                     examples/games/cards/d10.png
+                     examples/games/cards/d1.png
+                     examples/games/cards/d2.png
+                     examples/games/cards/d3.png
+                     examples/games/cards/d4.png
+                     examples/games/cards/d5.png
+                     examples/games/cards/d6.png
+                     examples/games/cards/d7.png
+                     examples/games/cards/d8.png
+                     examples/games/cards/d9.png
+                     examples/games/cards/dj.png
+                     examples/games/cards/dk.png
+                     examples/games/cards/dq.png
+                     examples/games/cards/ec.png
+                     examples/games/cards/empty-space.png
+                     examples/games/cards/h10.png
+                     examples/games/cards/h1.png
+                     examples/games/cards/h2.png
+                     examples/games/cards/h3.png
+                     examples/games/cards/h4.png
+                     examples/games/cards/h5.png
+                     examples/games/cards/h6.png
+                     examples/games/cards/h7.png
+                     examples/games/cards/h8.png
+                     examples/games/cards/h9.png
+                     examples/games/cards/hj.png
+                     examples/games/cards/hk.png
+                     examples/games/cards/hq.png
+                     examples/games/cards/index.html
+                     examples/games/cards/jb.png
+                     examples/games/cards/jr.png
+                     examples/games/cards/licence.txt
+                     examples/games/cards/s10.png
+                     examples/games/cards/s1.png
+                     examples/games/cards/s2.png
+                     examples/games/cards/s3.png
+                     examples/games/cards/s4.png
+                     examples/games/cards/s5.png
+                     examples/games/cards/s6.png
+                     examples/games/cards/s7.png
+                     examples/games/cards/s8.png
+                     examples/games/cards/s9.png
+                     examples/games/cards/sj.png
+                     examples/games/cards/sk.png
+                     examples/games/cards/sq.png
 
 source-repository head
   type:     git
diff --git a/src/FRP/Sodium.hs b/src/FRP/Sodium.hs
--- a/src/FRP/Sodium.hs
+++ b/src/FRP/Sodium.hs
@@ -38,7 +38,6 @@
         newEvent,
         newBehavior,
         listen,
-        listenValue,
         -- * FRP core language
         Event,
         Behavior,
@@ -66,7 +65,7 @@
         accum,
         countE,
         count,
-        once,
+        once
     ) where
 
 import FRP.Sodium.Plain
diff --git a/src/FRP/Sodium/Context.hs b/src/FRP/Sodium/Context.hs
--- a/src/FRP/Sodium/Context.hs
+++ b/src/FRP/Sodium/Context.hs
@@ -32,6 +32,8 @@
     newEvent      :: Reactive r (Event r a, a -> Reactive r ())
     -- | Listen for firings of this event. The returned @IO ()@ is an IO action
     -- that unregisters the listener. This is the observer pattern.
+    --
+    -- To listen to a 'Behavior' use @listen (values b) handler@
     listen        :: Event r a -> (a -> IO ()) -> Reactive r (IO ())
     -- | An event that never fires.
     never         :: Event r a
@@ -39,76 +41,75 @@
     --
     -- In the case where two event occurrences are simultaneous (i.e. both
     -- within the same transaction), both will be delivered in the same
-    -- transaction.
-    --
-    -- The order is not defined, because simultaneous events should be considered
-    -- to be order-agnostic.
+    -- transaction. If the event firings are ordered for some reason, then
+    -- their ordering is retained. In many common cases the ordering will
+    -- be undefined.
     merge         :: Event r a -> Event r a -> Event r a
     -- | Unwrap Just values, and discard event occurrences with Nothing values.
     filterJust    :: Event r (Maybe a) -> Event r a
-    -- | Create a behaviour with the specified initial value, that gets updated
-    -- by the values coming through the event. The \'current value\' of the behaviour
+    -- | Create a behavior with the specified initial value, that gets updated
+    -- by the values coming through the event. The \'current value\' of the behavior
     -- is notionally the value as it was 'at the start of the transaction'.
     -- That is, state updates caused by event firings get processed at the end of
     -- the transaction.
     hold          :: a -> Event r a -> Reactive r (Behavior r a)
-    -- | An event that gives the updates for the behaviour. It doesn't do any equality
+    -- | An event that gives the updates for the behavior. It doesn't do any equality
     -- comparison as the name might imply.
     changes       :: Behavior r a -> Event r a
-    -- | An event that is guaranteed to fires once when you listen to it, giving
-    -- the current value of the behaviour, and thereafter behaves like 'changes',
-    -- firing for each update to the behaviour's value.
+    -- | An event that is guaranteed to fire once when you listen to it, giving
+    -- the current value of the behavior, and thereafter behaves like 'changes',
+    -- firing for each update to the behavior's value.
     values        :: Behavior r a -> Event r a
-    -- | Sample the behaviour at the time of the event firing. Note that the 'current value'
-    -- of the behaviour that's sampled is the value as at the start of the transaction
+    -- | Sample the behavior at the time of the event firing. Note that the 'current value'
+    -- of the behavior that's sampled is the value as at the start of the transaction
     -- before any state changes of the current transaction are applied through 'hold's.
     snapshotWith  :: (a -> b -> c) -> Event r a -> Behavior r b -> Event r c
-    -- | Unwrap an event inside a behaviour to give a time-varying event implementation.
+    -- | Unwrap an event inside a behavior to give a time-varying event implementation.
     switchE       :: Behavior r (Event r a) -> Event r a
-    -- | Unwrap a behaviour inside another behaviour to give a time-varying behaviour implementation.
+    -- | Unwrap a behavior inside another behavior to give a time-varying behavior implementation.
     switch        :: Behavior r (Behavior r a) -> Reactive r (Behavior r a)
     -- | Execute the specified 'Reactive' action inside an event.
     execute       :: Event r (Reactive r a) -> Event r a
-    -- | Obtain the current value of a behaviour.
+    -- | Obtain the current value of a behavior.
     sample        :: Behavior r a -> Reactive r a
     -- | If there's more than one firing in a single transaction, combine them into
     -- one using the specified combining function.
+    --
+    -- If the event firings are ordered, then the first will appear at the left
+    -- input of the combining function. In most common cases it's best not to
+    -- make any assumptions about the ordering, and the combining function would
+    -- ideally be commutative.
     coalesce      :: (a -> a -> a) -> Event r a -> Event r a
+    -- | Throw away all event occurrences except for the first one.
+    once          :: Context r => Event r a -> Event r a
 
 newBehavior :: forall r a . Context r =>
-               a  -- ^ Initial behaviour value
+               a  -- ^ Initial behavior value
             -> Reactive r (Behavior r a, a -> Reactive r ())
 newBehavior initA = do
     (ev, push) <- newEvent
     beh <- hold initA ev
     return (beh, push)
 
-listenValue   :: Context r => Behavior r a -> (a -> IO ()) -> Reactive r (IO ())
-listenValue b handler = listen (values b) handler
-
 -- | Merge two streams of events of the same type, combining simultaneous
 -- event occurrences.
 --
 -- In the case where multiple event occurrences are simultaneous (i.e. all
--- within the same transaction), they are combined using the supplied
--- function. The output event is guaranteed not to have more than one
--- event occurrence per transaction.
---
--- The combine function should be commutative, because simultaneous events
--- should be considered to be order-agnostic.
+-- within the same transaction), they are combined using the same logic as
+-- 'coalesce'.
 mergeWith :: Context r => (a -> a -> a) -> Event r a -> Event r a -> Event r a
 mergeWith f ea eb = coalesce f $ merge ea eb
 
--- | Only keep event occurrences for which the predicate is true.
+-- | Only keep event occurrences for which the predicate returns true.
 filterE :: Context r => (a -> Bool) -> Event r a -> Event r a
 filterE pred = filterJust . ((\a -> if pred a then Just a else Nothing) <$>)
 
--- | Variant of snapshotWith that throws away the event's value and captures the behaviour's.
+-- | Variant of snapshotWith that throws away the event's value and captures the behavior's.
 snapshot :: Context r => Event r a -> Behavior r b -> Event r b
 snapshot = snapshotWith (flip const)
 
--- | Let event occurrences through only when the behaviour's value is True.
--- Note that the behaviour's value is as it was at the start of the transaction,
+-- | Let event occurrences through only when the behavior's value is True.
+-- Note that the behavior's value is as it was at the start of the transaction,
 -- that is, no state changes from the current transaction are taken into account.
 gate :: Context r => Event r a -> Behavior r Bool -> Event r a
 gate ea = filterJust . snapshotWith (\a b -> if b then Just a else Nothing) ea
@@ -124,7 +125,7 @@
             es = snd <$> ebs
     return eb
 
--- | Transform a behaviour with a generalized state loop (a mealy machine). The function
+-- | Transform a behavior with a generalized state loop (a mealy machine). The function
 -- is passed the input and the old state and returns the new state and output value.
 collect :: Context r => (a -> s -> (b, s)) -> s -> Behavior r a -> Reactive r (Behavior r b)
 collect f zs bea = do
@@ -155,11 +156,7 @@
 countE :: Context r => Event r a -> Reactive r (Event r Int)
 countE = accumE (+) 0 . (const 1 <$>)
 
--- | Count event occurrences, giving a behaviour that starts with 0 before the first occurrence.
+-- | Count event occurrences, giving a behavior that starts with 0 before the first occurrence.
 count :: Context r => Event r a -> Reactive r (Behavior r Int)
 count = hold 0 <=< countE
-
--- | Throw away all event occurrences except for the first one.
-once :: Context r => Event r a -> Reactive r (Event r a)
-once ea = filterJust <$> collectE (\a active -> (if active then Just a else Nothing, False)) True ea
 
diff --git a/src/FRP/Sodium/Plain.hs b/src/FRP/Sodium/Plain.hs
--- a/src/FRP/Sodium/Plain.hs
+++ b/src/FRP/Sodium/Plain.hs
@@ -70,9 +70,9 @@
         }
 
     data Behavior Plain a = Behavior {
-            -- | Internal: Extract the underlyingEvent event for this behaviour.
+            -- | Internal: Extract the underlyingEvent event for this behavior.
             underlyingEvent :: Event a,
-            -- | Obtain the current value of a behaviour.
+            -- | Obtain the current value of a behavior.
             behSample       :: Reactive a
         }
     sync = sync
@@ -91,6 +91,7 @@
     execute = execute
     sample = sample
     coalesce = coalesce
+    once = once
 
 -- | Execute the specified 'Reactive' within a new transaction, blocking the caller
 -- until all resulting processing is complete and all callbacks have been called.
@@ -136,6 +137,8 @@
 
 -- | Listen for firings of this event. The returned @IO ()@ is an IO action
 -- that unregisters the listener. This is the observer pattern.
+--
+-- To listen to a 'Behavior' use @listen (values b) handler@
 listen        :: Event a -> (a -> IO ()) -> Reactive (IO ())
 listen ev handle = listenTrans ev (ioReactive . handle)
 
@@ -150,10 +153,9 @@
 --
 -- In the case where two event occurrences are simultaneous (i.e. both
 -- within the same transaction), both will be delivered in the same
--- transaction.
---
--- The order is not defined, because simultaneous events should be considered
--- to be order-agnostic.
+-- transaction. If the event firings are ordered for some reason, then
+-- their ordering is retained. In many common cases the ordering will
+-- be undefined.
 merge         :: Event a -> Event a -> Event a
 merge ea eb = Event gl cacheRef
   where
@@ -179,8 +181,8 @@
             Nothing -> return ()
         addCleanup unlistener l'
 
--- | Create a behaviour with the specified initial value, that gets updated
--- by the values coming through the event. The \'current value\' of the behaviour
+-- | Create a behavior with the specified initial value, that gets updated
+-- by the values coming through the event. The \'current value\' of the behavior
 -- is notionally the value as it was 'at the start of the transaction'.
 -- That is, state updates caused by event firings get processed at the end of
 -- the transaction.
@@ -190,7 +192,7 @@
     unlistener <- unlistenize $ listenTrans ea $ \a -> do
         bs <- ioReactive $ readIORef bsRef
         ioReactive $ writeIORef bsRef $ bs { bsUpdate = Just a }
-        when (isNothing (bsUpdate bs)) $ onFinal $ do
+        when (isNothing (bsUpdate bs)) $ scheduleLast $ do
             bs <- readIORef bsRef
             let newCurrent = fromJust (bsUpdate bs)
                 bs' = newCurrent `seq` BehaviorState newCurrent Nothing
@@ -205,19 +207,19 @@
             }
     return beh
 
--- | An event that gives the updates for the behaviour. It doesn't do any equality
+-- | An event that gives the updates for the behavior. It doesn't do any equality
 -- comparison as the name might imply.
 changes       :: Behavior a -> Event a
 changes = underlyingEvent
 
 -- | An event that is guaranteed to fires once when you listen to it, giving
--- the current value of the behaviour, and thereafter behaves like 'changes',
--- firing for each update to the behaviour's value.
+-- the current value of the behavior, and thereafter behaves like 'changes',
+-- firing for each update to the behavior's value.
 values        :: Behavior a -> Event a
 values = eventify . linkedListenValue
 
--- | Sample the behaviour at the time of the event firing. Note that the 'current value'
--- of the behaviour that's sampled is the value as at the start of the transaction
+-- | Sample the behavior at the time of the event firing. Note that the 'current value'
+-- of the behavior that's sampled is the value as at the start of the transaction
 -- before any state changes of the current transaction are applied through 'hold's.
 snapshotWith  :: (a -> b -> c) -> Event a -> Behavior b -> Event c
 snapshotWith f ea bb = Event gl cacheRef
@@ -230,7 +232,7 @@
             push (f a b)
         addCleanup unlistener l
 
--- | Unwrap an event inside a behaviour to give a time-varying event implementation.
+-- | Unwrap an event inside a behavior to give a time-varying event implementation.
 switchE       :: Behavior (Event a) -> Event a
 switchE bea = Event gl cacheRef
   where
@@ -252,7 +254,7 @@
             ioReactive $ modifyIORef unlistensRef (M.insert iD unlisten2)
         addCleanup unlistener1 l
 
--- | Unwrap a behaviour inside another behaviour to give a time-varying behaviour implementation.
+-- | Unwrap a behavior inside another behavior to give a time-varying behavior implementation.
 switch        :: Behavior (Behavior a) -> Reactive (Behavior a)
 switch bba = do
     ba <- sample bba
@@ -284,12 +286,17 @@
             runListen l (Just nodeRef) $ \action -> action >>= push
         addCleanup unlistener l'
 
--- | Obtain the current value of a behaviour.
+-- | Obtain the current value of a behavior.
 sample        :: Behavior a -> Reactive a
 sample = behSample
 
 -- | If there's more than one firing in a single transaction, combine them into
 -- one using the specified combining function.
+--
+-- If the event firings are ordered, then the first will appear at the left
+-- input of the combining function. In most common cases it's best not to
+-- make any assumptions about the ordering, and the combining function would
+-- ideally be commutative.
 coalesce      :: (a -> a -> a) -> Event a -> Event a
 coalesce combine e = Event gl cacheRef
   where
@@ -303,19 +310,36 @@
             ioReactive $ modifyIORef outRef $ \ma -> Just $ case ma of
                 Just a0 -> a0 `combine` a
                 Nothing -> a
-            when first $ scheduleLast (Just nodeRef) $ do
+            when first $ schedulePrioritized (Just nodeRef) $ do
                 Just out <- ioReactive $ readIORef outRef
                 ioReactive $ writeIORef outRef Nothing
                 push out
         addCleanup unlistener l
 
-newBehavior :: a  -- ^ Initial behaviour value
+-- | Throw away all event occurrences except for the first one.
+once :: Event a -> Event a
+once e = Event gl cacheRef
+  where
+    cacheRef = unsafePerformIO $ newIORef Nothing
+    gl = do
+        l1 <- getListen e
+        (l, push, nodeRef) <- ioReactive newEventImpl
+        aliveRef <- ioReactive $ newIORef True
+        unlistener <- unlistenize $ do
+            rec
+                unlisten <- runListen l1 (Just nodeRef) $ \a -> do
+                    alive <- ioReactive $ readIORef aliveRef
+                    when alive $ do
+                        ioReactive $ writeIORef aliveRef False
+                        scheduleLast unlisten
+                        push a
+            return unlisten
+        addCleanup unlistener l
+
+newBehavior :: a  -- ^ Initial behavior value
             -> Reactive (Behavior a, a -> Reactive ())
 newBehavior = R.newBehavior
 
-listenValue   :: Behavior a -> (a -> IO ()) -> Reactive (IO ())
-listenValue = R.listenValue
-
 -- | Merge two streams of events of the same type, combining simultaneous
 -- event occurrences.
 --
@@ -333,12 +357,12 @@
 filterE :: (a -> Bool) -> Event a -> Event a
 filterE = R.filterE
 
--- | Variant of 'snapshotWith' that throws away the event's value and captures the behaviour's.
+-- | Variant of 'snapshotWith' that throws away the event's value and captures the behavior's.
 snapshot :: Event a -> Behavior b -> Event b
 snapshot = R.snapshot
 
--- | Let event occurrences through only when the behaviour's value is True.
--- Note that the behaviour's value is as it was at the start of the transaction,
+-- | Let event occurrences through only when the behavior's value is True.
+-- Note that the behavior's value is as it was at the start of the transaction,
 -- that is, no state changes from the current transaction are taken into account.
 gate :: Event a -> Behavior Bool -> Event a
 gate = R.gate
@@ -348,7 +372,7 @@
 collectE :: (a -> s -> (b, s)) -> s -> Event a -> Reactive (Event b)
 collectE = R.collectE
 
--- | Transform a behaviour with a generalized state loop (a mealy machine). The function
+-- | Transform a behavior with a generalized state loop (a mealy machine). The function
 -- is passed the input and the old state and returns the new state and output value.
 collect :: (a -> s -> (b, s)) -> s -> Behavior a -> Reactive (Behavior b)
 collect = R.collect
@@ -365,14 +389,10 @@
 countE :: Event a -> Reactive (Event Int)
 countE = R.countE
 
--- | Count event occurrences, giving a behaviour that starts with 0 before the first occurrence.
+-- | Count event occurrences, giving a behavior that starts with 0 before the first occurrence.
 count :: Event a -> Reactive (Behavior Int)
 count = R.count
 
--- | Throw away all event occurrences except for the first one.
-once :: Event a -> Reactive (Event a)
-once = R.once
-
 type ID = Int64
 
 data ReactiveState = ReactiveState {
@@ -411,11 +431,11 @@
     }
 
 -- | Queue the specified atomic to run at the end of the priority 1 queue
-schedulePrioritized :: Reactive () -> Reactive ()
-schedulePrioritized task = Reactive $ modify $ \as -> as { asQueue1 = asQueue1 as |> task }
+scheduleEarly :: Reactive () -> Reactive ()
+scheduleEarly task = Reactive $ modify $ \as -> as { asQueue1 = asQueue1 as |> task }
 
-onFinal :: IO () -> Reactive ()
-onFinal task = Reactive $ modify $ \as -> as { asFinal = asFinal as >> task }
+scheduleLast :: IO () -> Reactive ()
+scheduleLast task = Reactive $ modify $ \as -> as { asFinal = asFinal as >> task }
 
 data Listen a = Listen { runListen_ :: Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ()) }
 
@@ -530,7 +550,7 @@
             ob <- ioReactive $ modifyMVar mvObs $ \ob -> return $
                 (ob { obFirings = a : obFirings ob }, ob)
             -- If this is the first firing...
-            when (null (obFirings ob)) $ onFinal $ do
+            when (null (obFirings ob)) $ scheduleLast $ do
                 modifyMVar_ mvObs $ \ob -> return $ ob { obFirings = [] }
             let seqa = seq a a
             mapM_ ($ seqa) (M.elems . obListeners $ ob)
@@ -589,12 +609,12 @@
 
 newtype Unlistener = Unlistener (MVar (Maybe (IO ())))
 
--- | Listen to an input event/behaviour and return an 'Unlistener' that can be
+-- | Listen to an input event/behavior and return an 'Unlistener' that can be
 -- attached to an output event using 'addCleanup'.
 unlistenize :: Reactive (IO ()) -> Reactive Unlistener
 unlistenize doListen = do
     unlistener@(Unlistener ref) <- newUnlistener
-    schedulePrioritized $ do
+    scheduleEarly $ do
         mOldUnlisten <- ioReactive $ takeMVar ref
         case mOldUnlisten of
             Just _ -> do
@@ -614,7 +634,7 @@
     fromMaybe (return ()) mUnlisten
     putMVar ref Nothing
 
--- | Listen to the value of this behaviour with an initial callback giving
+-- | Listen to the value of this behavior with an initial callback giving
 -- the current value. Can get multiple values per transaction, the last of
 -- which is considered valid. You would normally want to use 'listenValue',
 -- which removes the extra unwanted values.
@@ -625,10 +645,10 @@
     linkedListen (underlyingEvent ba) mNodeRef handle
 
 -- | Queue the specified atomic to run at the end of the priority 2 queue
-scheduleLast :: Maybe (IORef Node)
+schedulePrioritized :: Maybe (IORef Node)
                   -> Reactive ()
                   -> Reactive ()
-scheduleLast mNodeRef task = do
+schedulePrioritized mNodeRef task = do
     mNode <- case mNodeRef of
         Just nodeRef -> Just <$> ioReactive (readIORef nodeRef)
         Nothing -> pure Nothing
@@ -648,12 +668,12 @@
     listen mNodeRef $ \a -> do
         ma <- ioReactive $ readIORef aRef
         ioReactive $ writeIORef aRef (Just a)
-        when (isNothing ma) $ scheduleLast mNodeRef $ do
+        when (isNothing ma) $ schedulePrioritized mNodeRef $ do
             Just a <- ioReactive $ readIORef aRef
             ioReactive $ writeIORef aRef Nothing
             handle a
 
--- | Listen to the value of this behaviour with a guaranteed initial callback
+-- | Listen to the value of this behavior with a guaranteed initial callback
 -- giving the current value, followed by callbacks for any updates. 
 linkedListenValue :: Behavior a -> Maybe (IORef Node) -> (a -> Reactive ()) -> Reactive (IO ())
 linkedListenValue ba = tidy (listenValueRaw ba)
@@ -720,11 +740,11 @@
     unlisten <- listen epa $ async . push
     return $ finalizeEvent ev unlisten
 
--- | Cross the specified behaviour over to a different partition.
+-- | Cross the specified behavior over to a different partition.
 cross :: (Typeable p, Typeable q) => Behavior a -> Reactive (Behavior q a)
 cross bpa = do
     a <- sample bpa
     ea <- crossE (underlyingEvent bpa)
-    ioReactive $ sync $ hold a ea
+    ioReactive $ sync $ 3 a ea
 -}
 
