diff --git a/README.md b/README.md
index 236374a..7f1bdd4 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,89 @@
## Retro Rampage
-This repository contains a code snapshot for the [Retro Rampage tutorial series](https://github.com/nicklockwood/RetroRampage) by Nick Lockwood.
+
+
+### About
+
+Retro Rampage is a tutorial series in which you will learn how to build a Wolfenstein-like game from scratch, in Swift. Initially the game will be targeting iPhone and iPad, but the engine should work on any platform that can run Swift code.
+
+Modern shooters have moved on a bit from Wolfenstein's grid-based 2.5D world, but we're going to stick with that template for a few reasons:
+
+* It's feasible to build Wolfenstein's 3D engine from scratch, without a lot of complicated math and without needing to know anything about GPUs or shaders.
+
+* It's simple to create and visualize maps that are constructed on a 2D grid, avoiding the complexities of 3D modeling and animation tools.
+
+* Tile grids are an excellent way to prototype techniques such as procedural map generation, pathfinding and line-of-sight calculations, which can then be applied to more complex worlds.
+
+### Background
+
+Ever since I first played Wolfenstein 3D on a friend's battered old 386 back in 1993 (the Mac version wasn't released until several years later) I was hooked on the *First-Person Shooter*.
+
+As an aspiring programmer, I wanted to recreate what I had seen. But armed only with 7th grade math and a rudimentary knowledge of BASIC, recreating the state-of-the-art in modern PC 3D graphics was hopelessly beyond my reach.
+
+More than two decades later, a few things have changed:
+
+* Apple has given us (well, sold us) the iPhone - a mobile computer many hundreds of times more powerful than a DOS-era desktop PC.
+
+* Chris Lattner has given us Swift - a simple, powerful programming language with which to write apps and games.
+
+* John Carmack has given us the Wolfenstein source code, and the wizardry behind it has been thoroughly demystified.
+
+I guess now is as good a time as any to scratch that quarter-century itch and build an FPS!
+
+### Tutorials
+
+The tutorials below are designed to be completed in order, and each step builds on the code from the previous one. If you decide to skip ahead, project snapshots for each step are available [here](https://github.com/nicklockwood/RetroRampage/releases).
+
+The tutorials are written with the assumption that you are already familiar with Xcode and are comfortable setting up an iOS project and adding new files to it. No prior knowledge of the Swift language is assumed, so it's fine if you've only used Objective-C or other C-like languages.
+
+[Part 1 - Separation of Concerns](Tutorial/Part1.md)
+
+Unlike most apps, games are (or should be) highly independent of any given platform. Swift has already been ported to many platforms outside of the Apple ecosystem, including Android, Ubuntu, Windows and even Raspberry Pi. In part one we'll see how to set up our project to minimize dependencies with iOS and provide a solid foundation for writing a fully portable game engine.
+
+[Part 2 - Mazes and Motion](Tutorial/Part2.md)
+
+Wolfenstein 3D - despite the name - is really a 2D game projected into the third dimension. The game mechanics work exactly the same as for a top-down 2D shooter, and to prove that we'll start by building the game from a top-down 2D perspective before we make the shift to first-person 3D.
+
+[Part 3 - Ray Casting](Tutorial/Part3.md)
+
+Long before hardware accelerated 3D graphics, some of the greatest game programmers of our generation were creating incredible 3D worlds armed only with a 16-bit processor. We'll follow in their footsteps and bring our game into the third dimension with an old-school graphics hack called *ray casting*.
+
+[Part 4 - Texture Mapping](Tutorial/Part4.md)
+
+In this chapter we'll spruce up the bare walls and floor with *texture mapping*. Texture mapping is the process of painting or *wall-papering* a 3D object with a 2D image, helping to provide the appearance of intricate detail in an otherwise featureless surface.
+
+[Part 5 - Sprites](Tutorial/Part5.md)
+
+It's time to introduce some other characters to keep our player company, and display them using *sprites* - a popular technique used to add engaging content to 3D games in the days before it was possible to render textured polygonal models in real-time with sufficient detail.
+
+[Part 6 - Enemy Action](Tutorial/Part6.md)
+
+Right now the monsters standing in the maze are little more than gruesome scenery. In this part we'll bring those passive monsters to life with collision detection, animations, and artificial intelligence so they can hunt and attack the player.
+
+[Part 7 - Death and Pixels](Tutorial/Part7.md)
+
+In this part we'll implement player damage, giving the monsters the ability to hurt and eventually kill the game's protagonist. We'll explore a variety of damage effects and techniques, including a cool Wolfenstein 3D death animation called *fizzlefade*.
+
+More to follow!
+
+### Acknowledgments
+
+I'd like to thank [Nat Brown](https://github.com/natbro) and [PJ Cook](https://github.com/pjcook) for their invaluable feedback on the first draft of these tutorials.
+
+Thanks also to [Lode Vandevenne](https://github.com/lvandeve) and [Fabien Sanglard](https://github.com/fabiensanglard/), whom I've never actually spoken to, but whose brilliant explanations of ray casting and the Wolfenstein engine formed both the basis and inspiration for this tutorial series.
+
+### Further Reading
+
+If you've completed the tutorials and are eager to learn more, here are some resources you might find useful:
+
+* [Lode's Raycasting Tutorial](https://lodev.org/cgtutor/raycasting.html#Introduction) - A great tutorial on ray casting, implemented in C++.
+* [Game Engine Black Book: Wolfenstein 3D](https://www.amazon.co.uk/gp/product/1727646703/ref=as_li_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1727646703&linkCode=as2&tag=charcoaldesig-21&linkId=aab5d43499c96f7417b7aa0a7b3e587d) - Fabien Sanglard's excellent book about the Wolfenstein 3D game engine.
+* [Swiftenstein](https://github.com/nicklockwood/Swiftenstein) - A more complete but less polished implementation of the ideas covered in this tutorial.
+* [Handmade Hero](https://handmadehero.org) - A video series in which games industry veteran [Casey Muratori](https://github.com/cmuratori) builds a game from scratch in C.
+
+### Tip Jar
+
+I started this tutorial series thinking it would take just a few days. Nearly two months later, with no end in sight, I realize I may have been a bit naive. If you've found it interesting, please consider donating to my caffeine fund.
+
+[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CR6YX6DLRNJTY&source=url)
+
diff --git a/Tutorial/Images/Architecture.png b/Tutorial/Images/Architecture.png
new file mode 100644
index 0000000..0ea84e4
Binary files /dev/null and b/Tutorial/Images/Architecture.png differ
diff --git a/Tutorial/Images/AttackAnimation.png b/Tutorial/Images/AttackAnimation.png
new file mode 100644
index 0000000..5afa3aa
Binary files /dev/null and b/Tutorial/Images/AttackAnimation.png differ
diff --git a/Tutorial/Images/BigPixelFizzle.png b/Tutorial/Images/BigPixelFizzle.png
new file mode 100644
index 0000000..baa12c9
Binary files /dev/null and b/Tutorial/Images/BigPixelFizzle.png differ
diff --git a/Tutorial/Images/BlackSpriteBackground.png b/Tutorial/Images/BlackSpriteBackground.png
new file mode 100644
index 0000000..0fc18a7
Binary files /dev/null and b/Tutorial/Images/BlackSpriteBackground.png differ
diff --git a/Tutorial/Images/BlendColorCrash.png b/Tutorial/Images/BlendColorCrash.png
new file mode 100644
index 0000000..50b2937
Binary files /dev/null and b/Tutorial/Images/BlendColorCrash.png differ
diff --git a/Tutorial/Images/BlurryBluePixel.png b/Tutorial/Images/BlurryBluePixel.png
new file mode 100644
index 0000000..d0aef51
Binary files /dev/null and b/Tutorial/Images/BlurryBluePixel.png differ
diff --git a/Tutorial/Images/ClearSpriteBackground.png b/Tutorial/Images/ClearSpriteBackground.png
new file mode 100644
index 0000000..60b9970
Binary files /dev/null and b/Tutorial/Images/ClearSpriteBackground.png differ
diff --git a/Tutorial/Images/CollisionLoop.png b/Tutorial/Images/CollisionLoop.png
new file mode 100644
index 0000000..6929cc5
Binary files /dev/null and b/Tutorial/Images/CollisionLoop.png differ
diff --git a/Tutorial/Images/CollisionResponse.png b/Tutorial/Images/CollisionResponse.png
new file mode 100644
index 0000000..433ef9f
Binary files /dev/null and b/Tutorial/Images/CollisionResponse.png differ
diff --git a/Tutorial/Images/CurvedWalls.png b/Tutorial/Images/CurvedWalls.png
new file mode 100644
index 0000000..f69b869
Binary files /dev/null and b/Tutorial/Images/CurvedWalls.png differ
diff --git a/Tutorial/Images/DeathEffectTimeline.png b/Tutorial/Images/DeathEffectTimeline.png
new file mode 100644
index 0000000..2b2f810
Binary files /dev/null and b/Tutorial/Images/DeathEffectTimeline.png differ
diff --git a/Tutorial/Images/DraggableJoystick.png b/Tutorial/Images/DraggableJoystick.png
new file mode 100644
index 0000000..83c38bc
Binary files /dev/null and b/Tutorial/Images/DraggableJoystick.png differ
diff --git a/Tutorial/Images/EasingCurves.png b/Tutorial/Images/EasingCurves.png
new file mode 100644
index 0000000..329f1f2
Binary files /dev/null and b/Tutorial/Images/EasingCurves.png differ
diff --git a/Tutorial/Images/FieldOfView.png b/Tutorial/Images/FieldOfView.png
new file mode 100644
index 0000000..6fcb9e2
Binary files /dev/null and b/Tutorial/Images/FieldOfView.png differ
diff --git a/Tutorial/Images/FizzleFade.png b/Tutorial/Images/FizzleFade.png
new file mode 100644
index 0000000..5dd1953
Binary files /dev/null and b/Tutorial/Images/FizzleFade.png differ
diff --git a/Tutorial/Images/FloatingJoystick.png b/Tutorial/Images/FloatingJoystick.png
new file mode 100644
index 0000000..cc307ab
Binary files /dev/null and b/Tutorial/Images/FloatingJoystick.png differ
diff --git a/Tutorial/Images/ImprovedTextureTrace.png b/Tutorial/Images/ImprovedTextureTrace.png
new file mode 100644
index 0000000..c700a26
Binary files /dev/null and b/Tutorial/Images/ImprovedTextureTrace.png differ
diff --git a/Tutorial/Images/Lighting.png b/Tutorial/Images/Lighting.png
new file mode 100644
index 0000000..8d011ff
Binary files /dev/null and b/Tutorial/Images/Lighting.png differ
diff --git a/Tutorial/Images/LineDrawing.png b/Tutorial/Images/LineDrawing.png
new file mode 100644
index 0000000..73e2dfe
Binary files /dev/null and b/Tutorial/Images/LineDrawing.png differ
diff --git a/Tutorial/Images/LineOfSight.png b/Tutorial/Images/LineOfSight.png
new file mode 100644
index 0000000..4b813ba
Binary files /dev/null and b/Tutorial/Images/LineOfSight.png differ
diff --git a/Tutorial/Images/MonsterInWall.png b/Tutorial/Images/MonsterInWall.png
new file mode 100644
index 0000000..47090e8
Binary files /dev/null and b/Tutorial/Images/MonsterInWall.png differ
diff --git a/Tutorial/Images/MonsterInWall2.png b/Tutorial/Images/MonsterInWall2.png
new file mode 100644
index 0000000..ecb5d28
Binary files /dev/null and b/Tutorial/Images/MonsterInWall2.png differ
diff --git a/Tutorial/Images/MonsterMob.png b/Tutorial/Images/MonsterMob.png
new file mode 100644
index 0000000..44a689c
Binary files /dev/null and b/Tutorial/Images/MonsterMob.png differ
diff --git a/Tutorial/Images/MonsterSprite.png b/Tutorial/Images/MonsterSprite.png
new file mode 100644
index 0000000..5c82252
Binary files /dev/null and b/Tutorial/Images/MonsterSprite.png differ
diff --git a/Tutorial/Images/MonstersAttacking.png b/Tutorial/Images/MonstersAttacking.png
new file mode 100644
index 0000000..6e6e0af
Binary files /dev/null and b/Tutorial/Images/MonstersAttacking.png differ
diff --git a/Tutorial/Images/PerspectiveView.png b/Tutorial/Images/PerspectiveView.png
new file mode 100644
index 0000000..f3e4af1
Binary files /dev/null and b/Tutorial/Images/PerspectiveView.png differ
diff --git a/Tutorial/Images/ProjectStructure.png b/Tutorial/Images/ProjectStructure.png
new file mode 100644
index 0000000..ae37689
Binary files /dev/null and b/Tutorial/Images/ProjectStructure.png differ
diff --git a/Tutorial/Images/Pythagoras.png b/Tutorial/Images/Pythagoras.png
new file mode 100644
index 0000000..321ee98
Binary files /dev/null and b/Tutorial/Images/Pythagoras.png differ
diff --git a/Tutorial/Images/RayFan.png b/Tutorial/Images/RayFan.png
new file mode 100644
index 0000000..e32db58
Binary files /dev/null and b/Tutorial/Images/RayFan.png differ
diff --git a/Tutorial/Images/RayLengths.png b/Tutorial/Images/RayLengths.png
new file mode 100644
index 0000000..3708d3a
Binary files /dev/null and b/Tutorial/Images/RayLengths.png differ
diff --git a/Tutorial/Images/RayTileIntersection.png b/Tutorial/Images/RayTileIntersection.png
new file mode 100644
index 0000000..bb6c653
Binary files /dev/null and b/Tutorial/Images/RayTileIntersection.png differ
diff --git a/Tutorial/Images/RedFlashEffect.png b/Tutorial/Images/RedFlashEffect.png
new file mode 100644
index 0000000..693d089
Binary files /dev/null and b/Tutorial/Images/RedFlashEffect.png differ
diff --git a/Tutorial/Images/RedSpriteBackground.png b/Tutorial/Images/RedSpriteBackground.png
new file mode 100644
index 0000000..9ec22ea
Binary files /dev/null and b/Tutorial/Images/RedSpriteBackground.png differ
diff --git a/Tutorial/Images/ReleaseMode.png b/Tutorial/Images/ReleaseMode.png
new file mode 100644
index 0000000..5e0c197
Binary files /dev/null and b/Tutorial/Images/ReleaseMode.png differ
diff --git a/Tutorial/Images/RoundingErrors.png b/Tutorial/Images/RoundingErrors.png
new file mode 100644
index 0000000..e7e5f17
Binary files /dev/null and b/Tutorial/Images/RoundingErrors.png differ
diff --git a/Tutorial/Images/SharpBluePixel.png b/Tutorial/Images/SharpBluePixel.png
new file mode 100644
index 0000000..56648a5
Binary files /dev/null and b/Tutorial/Images/SharpBluePixel.png differ
diff --git a/Tutorial/Images/SlopeIntercept.png b/Tutorial/Images/SlopeIntercept.png
new file mode 100644
index 0000000..f0da3bd
Binary files /dev/null and b/Tutorial/Images/SlopeIntercept.png differ
diff --git a/Tutorial/Images/SmoothedDeathEffect.png b/Tutorial/Images/SmoothedDeathEffect.png
new file mode 100644
index 0000000..f1f08d2
Binary files /dev/null and b/Tutorial/Images/SmoothedDeathEffect.png differ
diff --git a/Tutorial/Images/SolidFloorColor.png b/Tutorial/Images/SolidFloorColor.png
new file mode 100644
index 0000000..af6abee
Binary files /dev/null and b/Tutorial/Images/SolidFloorColor.png differ
diff --git a/Tutorial/Images/SortedSprites.png b/Tutorial/Images/SortedSprites.png
new file mode 100644
index 0000000..9f96b2c
Binary files /dev/null and b/Tutorial/Images/SortedSprites.png differ
diff --git a/Tutorial/Images/SpriteBehindPlayer.png b/Tutorial/Images/SpriteBehindPlayer.png
new file mode 100644
index 0000000..19de0d5
Binary files /dev/null and b/Tutorial/Images/SpriteBehindPlayer.png differ
diff --git a/Tutorial/Images/SpriteLines.png b/Tutorial/Images/SpriteLines.png
new file mode 100644
index 0000000..22d09fc
Binary files /dev/null and b/Tutorial/Images/SpriteLines.png differ
diff --git a/Tutorial/Images/SpriteOrderBug.png b/Tutorial/Images/SpriteOrderBug.png
new file mode 100644
index 0000000..8725353
Binary files /dev/null and b/Tutorial/Images/SpriteOrderBug.png differ
diff --git a/Tutorial/Images/SpriteRadius.png b/Tutorial/Images/SpriteRadius.png
new file mode 100644
index 0000000..bc1256f
Binary files /dev/null and b/Tutorial/Images/SpriteRadius.png differ
diff --git a/Tutorial/Images/SpriteRayBug.png b/Tutorial/Images/SpriteRayBug.png
new file mode 100644
index 0000000..4acc73d
Binary files /dev/null and b/Tutorial/Images/SpriteRayBug.png differ
diff --git a/Tutorial/Images/SpriteRayIntersection.png b/Tutorial/Images/SpriteRayIntersection.png
new file mode 100644
index 0000000..0de084a
Binary files /dev/null and b/Tutorial/Images/SpriteRayIntersection.png differ
diff --git a/Tutorial/Images/StagedResponse.png b/Tutorial/Images/StagedResponse.png
new file mode 100644
index 0000000..8f533e0
Binary files /dev/null and b/Tutorial/Images/StagedResponse.png differ
diff --git a/Tutorial/Images/StateMachine.png b/Tutorial/Images/StateMachine.png
new file mode 100644
index 0000000..d147b2c
Binary files /dev/null and b/Tutorial/Images/StateMachine.png differ
diff --git a/Tutorial/Images/StraightWalls.png b/Tutorial/Images/StraightWalls.png
new file mode 100644
index 0000000..0ac05b8
Binary files /dev/null and b/Tutorial/Images/StraightWalls.png differ
diff --git a/Tutorial/Images/StretchedWalls.png b/Tutorial/Images/StretchedWalls.png
new file mode 100644
index 0000000..4bc9c66
Binary files /dev/null and b/Tutorial/Images/StretchedWalls.png differ
diff --git a/Tutorial/Images/TextureLookupTrace.png b/Tutorial/Images/TextureLookupTrace.png
new file mode 100644
index 0000000..77c74aa
Binary files /dev/null and b/Tutorial/Images/TextureLookupTrace.png differ
diff --git a/Tutorial/Images/TextureMapping.png b/Tutorial/Images/TextureMapping.png
new file mode 100644
index 0000000..cd9c5cb
Binary files /dev/null and b/Tutorial/Images/TextureMapping.png differ
diff --git a/Tutorial/Images/TextureSmearing.png b/Tutorial/Images/TextureSmearing.png
new file mode 100644
index 0000000..813f44b
Binary files /dev/null and b/Tutorial/Images/TextureSmearing.png differ
diff --git a/Tutorial/Images/TextureVariants.png b/Tutorial/Images/TextureVariants.png
new file mode 100644
index 0000000..7b7453f
Binary files /dev/null and b/Tutorial/Images/TextureVariants.png differ
diff --git a/Tutorial/Images/TexturedCeiling.png b/Tutorial/Images/TexturedCeiling.png
new file mode 100644
index 0000000..596cd60
Binary files /dev/null and b/Tutorial/Images/TexturedCeiling.png differ
diff --git a/Tutorial/Images/TexturedFloor.png b/Tutorial/Images/TexturedFloor.png
new file mode 100644
index 0000000..39bfd10
Binary files /dev/null and b/Tutorial/Images/TexturedFloor.png differ
diff --git a/Tutorial/Images/TexturesAndLighting.png b/Tutorial/Images/TexturesAndLighting.png
new file mode 100644
index 0000000..c9c1df2
Binary files /dev/null and b/Tutorial/Images/TexturesAndLighting.png differ
diff --git a/Tutorial/Images/TileEdgeHit.png b/Tutorial/Images/TileEdgeHit.png
new file mode 100644
index 0000000..2b18e86
Binary files /dev/null and b/Tutorial/Images/TileEdgeHit.png differ
diff --git a/Tutorial/Images/TileSteps.png b/Tutorial/Images/TileSteps.png
new file mode 100644
index 0000000..213a27f
Binary files /dev/null and b/Tutorial/Images/TileSteps.png differ
diff --git a/Tutorial/Images/Tilemap.png b/Tutorial/Images/Tilemap.png
new file mode 100644
index 0000000..0ea275d
Binary files /dev/null and b/Tutorial/Images/Tilemap.png differ
diff --git a/Tutorial/Images/VariedTextures.png b/Tutorial/Images/VariedTextures.png
new file mode 100644
index 0000000..217604c
Binary files /dev/null and b/Tutorial/Images/VariedTextures.png differ
diff --git a/Tutorial/Images/ViewFrustum.png b/Tutorial/Images/ViewFrustum.png
new file mode 100644
index 0000000..e6973d7
Binary files /dev/null and b/Tutorial/Images/ViewFrustum.png differ
diff --git a/Tutorial/Images/ViewPlane.png b/Tutorial/Images/ViewPlane.png
new file mode 100644
index 0000000..6dac921
Binary files /dev/null and b/Tutorial/Images/ViewPlane.png differ
diff --git a/Tutorial/Images/WalkingFrames.png b/Tutorial/Images/WalkingFrames.png
new file mode 100644
index 0000000..3c74fef
Binary files /dev/null and b/Tutorial/Images/WalkingFrames.png differ
diff --git a/Tutorial/Images/WallCollisions.png b/Tutorial/Images/WallCollisions.png
new file mode 100644
index 0000000..08d7049
Binary files /dev/null and b/Tutorial/Images/WallCollisions.png differ
diff --git a/Tutorial/Images/WallDistance.png b/Tutorial/Images/WallDistance.png
new file mode 100644
index 0000000..286d34c
Binary files /dev/null and b/Tutorial/Images/WallDistance.png differ
diff --git a/Tutorial/Images/WallHit.png b/Tutorial/Images/WallHit.png
new file mode 100644
index 0000000..96af45b
Binary files /dev/null and b/Tutorial/Images/WallHit.png differ
diff --git a/Tutorial/Images/WallTexture.png b/Tutorial/Images/WallTexture.png
new file mode 100644
index 0000000..87c3e13
Binary files /dev/null and b/Tutorial/Images/WallTexture.png differ
diff --git a/Tutorial/Images/WolfensteinSprites.png b/Tutorial/Images/WolfensteinSprites.png
new file mode 100644
index 0000000..ec1ae04
Binary files /dev/null and b/Tutorial/Images/WolfensteinSprites.png differ
diff --git a/Tutorial/Part1.md b/Tutorial/Part1.md
new file mode 100644
index 0000000..e0a7cd2
--- /dev/null
+++ b/Tutorial/Part1.md
@@ -0,0 +1,744 @@
+## Part 1: Separation of Concerns
+
+In a traditional app, it is common practice to split the logic into three layers - the Model, View and Controller[[1]](#footnote1). Games can also be divided into three similar layers:
+
+* Platform Layer - Platform-specific code to integrate with the host operating system.
+* Engine - Platform-independent game functions like asset loaders, rendering, physics, pathfinding AI, etc.
+* Game Logic - Gameplay and graphics specific to this game. Tightly coupled to a particular engine.
+
+
+
+Apple would ideally like you to build your game or engine using frameworks like GameKit, SpriteKit and SceneKit. These are powerful and easy-to-use, but doing so shackles your game completely to Apple's platforms.
+
+In practice, few game developers do this because Apple alone doesn't represent a large enough share of the games market. A popular alternative is to use a 3rd party framework like Unity or Unreal that provide a cross-platform engine, and have platform layers for many different systems. This frees you from Apple exclusivity, but ties you into a 3rd party ecosystem instead.
+
+In this tutorial we are choosing a third path: We'll create a very minimal platform layer for iOS using UIKit, then build both the game and engine from scratch using pure Swift, with no external dependencies.
+
+### Project Setup
+
+Modern games require a fairly complex interface with their host operating system in order to implement hardware accelerated graphics, sound and user input. But fortunately, *we aren't building a modern game*.
+
+In the era of Wolfenstein 3D, computers were mainly intended as business machines, and did almost nothing for you as a game developer. We're going to build this game using a similar approach to what they used in the MSDOS days (while taking advantage of modern advancements such as fast floating-point math and 32-bit color).
+
+Start by creating a new *Single View iOS App* project in Xcode. To keep ourselves honest, we're going to put the engine code in a separate module, creating an "air gap" between the game code and the code that interacts with iOS.
+
+To create the Engine module, go to the Targets tab and add a new target of type *Cocoa Touch Framework* called "Engine". Xcode will automatically link this in to your main app. It will also create a C header file in the Engine folder called `Engine.h` which imports UIKit. We aren't going to be using UIKit in the engine, so you can go ahead and delete `Engine.h`.
+
+If you aren't confident creating a project with a nested framework (or if you just don't feel like setting up a project from scratch) you can download the base project from [here](https://github.com/nicklockwood/RetroRampage/archive/Start.zip).
+
+Here is how the project structure should look:
+
+
+
+### View Setup
+
+Open `ViewController.swift` and replace its contents with the following:
+
+```swift
+import UIKit
+
+class ViewController: UIViewController {
+ private let imageView = UIImageView()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setUpImageView()
+ }
+
+ func setUpImageView() {
+ view.addSubview(imageView)
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
+ imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
+ imageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
+ imageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
+ imageView.contentMode = .scaleAspectFit
+ imageView.backgroundColor = .black
+ }
+}
+```
+
+Now run the app. If all has gone well you should see the black background of the `UIImageView` filling the whole screen.
+
+### Pixel Pushing
+
+Apps display views. Games typically deal with sprites or polygons. We are going to be working directly with *pixels*. Everything you see on screen is made up of pixels, and on a modern system, pixels are usually stored as 4 bytes[[2]](#footnote2) - one byte for each of the red, green, blue and alpha channels.
+
+UIKit provides the `UIColor` class, which treats those channels as floating-point values in the range 0.0 to 1.0. This is a convenient abstraction, but it's an expensive way to store colors and it couples us to UIKit, so we'll create our own `Color` type instead. Create a new file called `Color.swift` in the Engine module with the following contents[[3]](#footnote3):
+
+```swift
+public struct Color {
+ public var r, g, b, a: UInt8
+
+ public init(r: UInt8, g: UInt8, b: UInt8, a: UInt8 = 255) {
+ self.r = r
+ self.g = g
+ self.b = b
+ self.a = a
+ }
+}
+```
+
+Since we're mostly going to be dealing with opaque colors, we've defaulted the alpha value to 255 (no transparency) in the initializer. In the same file, add some common color constants, which will be useful later[[4]](#footnote4):
+
+```swift
+public extension Color {
+ static let clear = Color(r: 0, g: 0, b: 0, a: 0)
+ static let black = Color(r: 0, g: 0, b: 0)
+ static let white = Color(r: 255, g: 255, b: 255)
+ static let gray = Color(r: 192, g: 192, b: 192)
+ static let red = Color(r: 255, g: 0, b: 0)
+ static let green = Color(r: 0, g: 255, b: 0)
+ static let blue = Color(r: 0, g: 0, b: 255)
+}
+```
+
+Now that the engine has a way to represent a pixel color, we need a type to represent an entire image. We'll call this `Bitmap` to avoid confusion with `UIImage`. Create a new file called `Bitmap.swift` in the Engine module with the following contents:
+
+```swift
+public struct Bitmap {
+ public private(set) var pixels: [Color]
+ public let width: Int
+
+ public init(width: Int, pixels: [Color]) {
+ self.width = width
+ self.pixels = pixels
+ }
+}
+```
+
+You'll notice that `Bitmap` stores its pixels in a flat array instead of a 2D matrix as you might expect. This is mainly for efficiency - allocating a flat array like this means that the pixels will be stored contiguously in a single block of memory instead of each row or column being allocated separately on the heap.
+
+In performance-critical code, consideration of memory layout is important to avoid cache-misses, which occur when the program is forced to page in new data from main memory instead of the much-faster [processor cache](https://en.wikipedia.org/wiki/CPU_cache).
+
+Accessing a 2D image using a one-dimensional index (and having to compute the row offsets each time) isn't very ergonomic though, so lets add a 2D subscript. While we're at it, let's also add a computed property for the image height, and a convenience initializer for creating an empty image:
+
+```swift
+public extension Bitmap {
+ var height: Int {
+ return pixels.count / width
+ }
+
+ subscript(x: Int, y: Int) -> Color {
+ get { return pixels[y * width + x] }
+ set { pixels[y * width + x] = newValue }
+ }
+
+ init(width: Int, height: Int, color: Color) {
+ self.pixels = Array(repeating: color, count: width * height)
+ self.width = width
+ }
+}
+```
+
+The subscript takes an X and Y coordinate within the image and does some math to compute the offset into the flat `pixels` array. The code assumes that the pixels are stored in row order rather than column order (which is the convention on most platforms, including iOS), but this assumption is not exposed in the public interface, so we can change it later if necessary.
+
+Add a new file to the main app target called `UIImage+Bitmap.swift` with the following contents:
+
+```swift
+import UIKit
+import Engine
+
+extension UIImage {
+ convenience init?(bitmap: Bitmap) {
+ let alphaInfo = CGImageAlphaInfo.premultipliedLast
+ let bytesPerPixel = MemoryLayout.size
+ let bytesPerRow = bitmap.width * bytesPerPixel
+
+ guard let providerRef = CGDataProvider(data: Data(
+ bytes: bitmap.pixels, count: bitmap.height * bytesPerRow
+ ) as CFData) else {
+ return nil
+ }
+
+ guard let cgImage = CGImage(
+ width: bitmap.width,
+ height: bitmap.height,
+ bitsPerComponent: 8,
+ bitsPerPixel: bytesPerPixel * 8,
+ bytesPerRow: bytesPerRow,
+ space: CGColorSpaceCreateDeviceRGB(),
+ bitmapInfo: CGBitmapInfo(rawValue: alphaInfo.rawValue),
+ provider: providerRef,
+ decode: nil,
+ shouldInterpolate: true,
+ intent: .defaultIntent
+ ) else {
+ return nil
+ }
+
+ self.init(cgImage: cgImage)
+ }
+}
+```
+
+Notice that the file imports both UIKit and the Engine module - that's a good clue as to its purpose, which is to act as a bridge between UIKit and the game engine. Specifically, it provides a way to take a `Bitmap` and turn it into a `UIImage` for display on screen.
+
+We want this conversion process to be as cheap as possible since we're repeating it every frame. The `pixels` array already has the correct memory layout to be consumed directly by `CGImage`, so it's a simple copy without any need for per-pixel manipulation.
+
+Pay particular attention to this line:
+
+```swift
+let alphaInfo = CGImageAlphaInfo.premultipliedLast
+```
+
+Usually in Swift, the order in which properties are declared in a struct doesn't matter very much. In this case though, we are relying on the order and alignment of the `r`, `g`, `b` and `a` properties of `Color` to match the format we specified for `alphaInfo`.
+
+The constant `premultipliedLast` specifies that the alpha value is stored *after* the RGB channels. It is common practice on iOS to use `premultipliedFirst` instead, and if we used that we would have to change the property order in our `Color` struct to `a, r, g, b` or our colors would be messed up.
+
+Now we have all the pieces we need to display a `Bitmap` on the screen. Add the following code to `viewDidLoad()` in `ViewController.swift` (note that you need to add `import Engine` at the top of the file as well since the `Bitmap` type is defined in the Engine module):
+
+```swift
+import Engine
+
+...
+
+override func viewDidLoad() {
+ super.viewDidLoad()
+ setUpImageView()
+
+ var bitmap = Bitmap(width: 8, height: 8, color: .white)
+ bitmap[0, 0] = .blue
+
+ imageView.image = UIImage(bitmap: bitmap)
+}
+```
+
+This code creates a new 8x8 `Bitmap` filled with white, and then sets the pixel at 0,0 (the top-left corner) to blue. The result looks like this:
+
+
+
+Now I know what you're thinking: *Why is it all blurry?*
+
+What you are seeing here is the modern miracle of [bilinear filtering](https://en.wikipedia.org/wiki/Bilinear_filtering). In the good old days, when computers were slow, when we expanded images on the screen we saw big square pixels[[5]](#footnote5) *and we liked it*. But in the mid-to-late '90s, as 3D graphics cards became popular and math became cheaper, bilinear filtering replaced the so-called [nearest-neighbor](https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) algorithm used previously.
+
+Bilinear filtering combines nearby pixel values, creating results that are interpolated rather than just sampled directly from the original image. When downscaling images this produces dramatically better results, because instead of pixels just disappearing as the output shrinks, their color still contributes to the final image. Unfortunately, when *upscaling* images - especially chunky pixel-art - the results tend to look like blurry garbage.
+
+To solve this, add the following line to the end of `ViewController.setUpImageView()`:
+
+```swift
+imageView.layer.magnificationFilter = .nearest
+```
+
+That tells the `UIImageView` to use the nearest-neighbor algorithm when magnifying an image instead of bilinear filtering (which is the default for both upscaling and downscaling). Run the app again and the blurriness should be gone.
+
+
+
+### Inversion of Control
+
+Right now our drawing logic lives in the platform layer, which is kind of backwards. The game engine should be responsible for the drawing, not the platform. Create a new file called `Renderer.swift` in the Engine module with the following contents:
+
+```swift
+public struct Renderer {
+ public private(set) var bitmap: Bitmap
+
+ public init(width: Int, height: Int) {
+ self.bitmap = Bitmap(width: width, height: height, color: .white)
+ }
+}
+
+public extension Renderer {
+ mutating func draw() {
+ bitmap[0, 0] = .blue
+ }
+}
+
+```
+
+Then in `ViewController.viewDidLoad()` replace the lines:
+
+```swift
+var bitmap = Bitmap(width: 8, height: 8, color: .white)
+bitmap[0, 0] = .blue
+
+imageView.image = UIImage(bitmap: bitmap)
+```
+
+with:
+
+```swift
+var renderer = Renderer(width: 8, height: 8)
+renderer.draw()
+
+imageView.image = UIImage(bitmap: renderer.bitmap)
+```
+
+Run it again, and the result should look the same as before.
+
+### Loop-the-Loop
+
+That's all very well, but one static pixel is hardly a game. In a game, things change over time, even when the player isn't doing anything. This is different from a typical GUI application where the app spends most of its time sitting idle, waiting for user input.
+
+Right now we just draw one screen and then stop. In a real game, everything happens in a loop called the *game loop*. Game loops are usually driven in sync with the display's [refresh rate](https://en.wikipedia.org/wiki/Refresh_rate).
+
+In the early days of home computing, computer monitors used a scanning electron beam to stimulate phosphorescent paint on the inside of the glass screen. The rate at which that beam could scan across the entire screen was called the *refresh rate* - typically 50 Hz (PAL) or 60 Hz (NTSC).
+
+Early graphics cards did little more than expose a pixel buffer into which apps and games could draw their interface. If you timed your drawing at the wrong point in the beam cycle, the image would flicker, which is why it was important for game loops to be synchronized to the refresh rate.
+
+Modern LCD or OLED screens no longer have a scanning beam, but they still have a finite rate at which data can be streamed into and out of video memory. Thanks to [double buffering](https://en.wikipedia.org/wiki/Multiple_buffering#Double_buffering_in_computer_graphics) we no longer have to worry about flickering and tearing, but we still need to synchronize our drawing to the refresh rate in order to achieve smooth animations.
+
+If we redraw the interface too frequently then we'll waste cycles on drawing that will never be seen. If we redraw too slowly then the screen will appear frozen. If we redraw slightly out of step with the refresh, objects will appear to stutter as they move across the screen. Most iOS developers have experienced the *jerky scrolling* that arises when a `UITableView` takes too long to redraw its cells.
+
+In single-threaded, single-process operating systems like DOS, the game loop was an actual `while` loop, repeatedly scanning for user input, running the game logic, then redrawing the screen. Modern operating systems like iOS have a lot of things going on in parallel, and blocking the main thread is a big no-no. So instead of a loop, we use a *timer*, which calls our update logic at a specified frequency, handing back control to the OS while it's waiting.
+
+In iOS, the recommended way to synchronize code execution to the screen refresh is to use `CADisplayLink`. `CADisplayLink` is a special type of timer whose period is always an exact multiple of the refresh rate (typically 60 Hz, although some modern iOS devices can manage 120 Hz).
+
+Unlike `Timer`, `CADisplayLink` doesn't have a Swift-friendly, closure-based API, so we need to use the target/selector pattern. Add the following method to `ViewController` (Note the `@objc` annotation, which is needed for the selector binding to work):
+
+```swift
+@objc func update(_ displayLink: CADisplayLink) {
+ var renderer = Renderer(width: 8, height: 8)
+ renderer.draw()
+
+ imageView.image = UIImage(bitmap: renderer.bitmap)
+}
+```
+
+Then in `viewDidLoad()`, replace the lines:
+
+```swift
+var renderer = Renderer(width: 8, height: 8)
+renderer.draw()
+
+imageView.image = UIImage(bitmap: renderer.bitmap)
+```
+
+with:
+
+```swift
+let displayLink = CADisplayLink(target: self, selector: #selector(update))
+displayLink.add(to: .main, forMode: .common)
+```
+
+This code creates a `CADisplayLink` with the default frequency (60 Hz) and adds it to the main `RunLoop`. A `RunLoop` is essentially the same concept as a game loop - it's a loop that runs continuously on a given thread, monitoring for user-triggered events like touches or key presses, and firing scheduled events like timers. By adding our display link to the `RunLoop` we are telling the system to keep track of it and call our `update()` method as needed until further notice.
+
+Note the `forMode:` argument. The `Runloop.Mode` is a sort of priority system that iOS uses to determine which events need to be updated under which circumstances. The documentation for `CADisplayLink` suggests using `default` for the mode, but events bound to the `default` mode won't fire during *tracking* (e.g. when the user is scrolling, or manipulating a UI control). By using the `common` mode we ensure that our `CADisplayLink` will be given top priority.
+
+A word of caution: `CADisplayLink` is retained by the `RunLoop`, and also retains a strong reference to its `target` (the view controller in this case) while running. We are not keeping a reference to the display link anywhere, so we have no means to stop it, which means it will run forever until the app terminates, and will never release the `ViewController`, even if it moves off-screen.
+
+In this case that doesn't matter because `ViewController` is the root view controller and will never be dismissed, but if you were to build a game that used multiple view controllers for different screens it would be something to watch out for. A simple solution would be to store a reference to the `CADisplayLink` and call its `invalidate()` method when the view controller is dismissed.
+
+### Setting Things in Motion
+
+If everything worked correctly, we should now be redrawing our screen at 60 FPS (Frames Per Second). We're still always drawing the same thing though, so lets add some animation.
+
+Motion is a change of position over time. Position in a 2D world is typically defined as a vector with X (for horizontal position) and Y (for vertical position). We already touched on the idea of X and Y coordinates in the context of accessing pixels in a `Bitmap`, and world coordinates are similar. But unlike pixels, entities in the world won't necessarily be aligned exactly on a grid, so we need to represent them using floating point values.
+
+Let's create a new type to represent a position in the world. Add a new file to the Engine module called `Vector.swift` with the following contents:
+
+```swift
+public struct Vector {
+ public var x, y: Double
+
+ public init(x: Double, y: Double) {
+ self.x = x
+ self.y = y
+ }
+}
+```
+
+Vectors are a very flexible type - they can represent position, velocity, distance, direction and more. We will be making a lot of use of this type throughout the game engine. Our vector type could easily be extended into the 3rd dimension by adding a Z component, but because our game mechanics are primarily 2D, we'll stick with just X and Y as it makes the math much simpler.
+
+While we're here, let's add a few operator overloads[[6]](#footnote6) to make `Vector` easier to work with:
+
+```swift
+public extension Vector {
+ static func + (lhs: Vector, rhs: Vector) -> Vector {
+ return Vector(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
+ }
+
+ static func - (lhs: Vector, rhs: Vector) -> Vector {
+ return Vector(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
+ }
+
+ static func * (lhs: Vector, rhs: Double) -> Vector {
+ return Vector(x: lhs.x * rhs, y: lhs.y * rhs)
+ }
+
+ static func / (lhs: Vector, rhs: Double) -> Vector {
+ return Vector(x: lhs.x / rhs, y: lhs.y / rhs)
+ }
+
+ static func * (lhs: Double, rhs: Vector) -> Vector {
+ return Vector(x: lhs * rhs.x, y: lhs * rhs.y)
+ }
+
+ static func / (lhs: Double, rhs: Vector) -> Vector {
+ return Vector(x: lhs / rhs.x, y: lhs / rhs.y)
+ }
+
+ static func += (lhs: inout Vector, rhs: Vector) {
+ lhs.x += rhs.x
+ lhs.y += rhs.y
+ }
+
+ static func -= (lhs: inout Vector, rhs: Vector) {
+ lhs.x -= rhs.x
+ lhs.y -= rhs.y
+ }
+
+ static func *= (lhs: inout Vector, rhs: Double) {
+ lhs.x *= rhs
+ lhs.y *= rhs
+ }
+
+ static func /= (lhs: inout Vector, rhs: Double) {
+ lhs.x /= rhs
+ lhs.y /= rhs
+ }
+
+ static prefix func - (rhs: Vector) -> Vector {
+ return Vector(x: -rhs.x, y: -rhs.y)
+ }
+}
+```
+
+### Ready Player One
+
+We'll need a *player* object to act as our avatar in the game. Create a new file called `Player.swift` in the Engine module, with the following contents:
+
+```swift
+public struct Player {
+ public var position: Vector
+
+ public init(position: Vector) {
+ self.position = position
+ }
+}
+```
+
+The player's position should persist between frames, so it can't be a local variable. Add a `player` property to the `ViewController` class:
+
+```swift
+class ViewController: UIViewController {
+ private let imageView = UIImageView()
+ private var player = Player(position: Vector(x: 4, y: 4))
+
+ ...
+}
+```
+
+The player is positioned at 4,4 by default which would be the center of a world that had a size of 8x8 units square. We haven't really thought about what those units are or what any of that means yet, but for now let's assume that the world's dimensions match the bitmap that we've been drawing into - i.e. that the units are in pixels.
+
+Now we have a player instance, the renderer will need access to it in order to draw it. In `Renderer.swift` change the `draw()` method to:
+
+```swift
+mutating func draw(_ player: Player) {
+ bitmap[Int(player.position.x), Int(player.position.y)] = .blue
+}
+```
+
+Then in `ViewController.swift` update the line:
+
+```swift
+renderer.draw()
+```
+
+to:
+
+```swift
+renderer.draw(player)
+```
+
+Run the app and and you'll see that we now draw a pixel at the player's position - the middle of the bitmap - instead of the top corner. Next, we'll make that position change over time.
+
+### Need for Speed
+
+A change of position over time is called [velocity](https://en.wikipedia.org/wiki/Velocity). Velocity is a vector that combines both the [speed](https://en.wikipedia.org/wiki/Speed) at which an object is moving, and the direction of movement. Let's add a velocity property to `Player`:
+
+```swift
+public struct Player {
+ public var position: Vector
+ public var velocity: Vector
+
+ public init(position: Vector) {
+ self.position = position
+ self.velocity = Vector(x: 1, y: 1)
+ }
+}
+```
+
+Now that the player has a velocity, we can implement movement by adding the player `velocity` to the player `position` every frame. We don't really want to add movement logic inside the `draw()` method because that violates the [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) principle, so let's add a new method to `Player` called `update()`:
+
+```swift
+public extension Player {
+ mutating func update() {
+ position += velocity
+ }
+}
+```
+
+In `ViewController`'s `update()` method, call `player.update()` before drawing:
+
+```swift
+@objc func update(_ displayLink: CADisplayLink) {
+ player.update()
+
+ var renderer = Renderer(width: 8, height: 8)
+ ...
+}
+```
+
+If we run the app now it crashes almost instantly. The problem is that the player quickly moves outside of visible area, and when we try to draw outside the bitmap we get an out-of-bounds error for the `pixels` array.
+
+This is likely to be a common problem. Rather than having to add tedious bounds checking to every drawing function we write, let's just add a guard inside the subscript to prevent us from accidentally drawing outside the array. In `Bitmap.swift`, replace the line:
+
+```swift
+set { pixels[y * width + x] = newValue }
+```
+
+with:
+
+```swift
+set {
+ guard x >= 0, y >= 0, x < width, y < height else { return }
+ pixels[y * width + x] = newValue
+}
+```
+
+With that protection in place the app won't crash anymore, but the player avatar still vanishes from view almost immediately. To solve that, we can wrap the player position within the bitmap, using the `formTruncatingRemainder()` function from the Swift standard library:
+
+```swift
+mutating func update() {
+ position += velocity
+ position.x.formTruncatingRemainder(dividingBy: 8)
+ position.y.formTruncatingRemainder(dividingBy: 8)
+}
+```
+
+Run the app now and you should see the blue player pixel streaking diagonally across the screen, wrapping back around to the top-left corner whenever it moves off the screen. It seems to be moving much too fast - but how fast is it exactly?
+
+For the player velocity we used `Vector(x: 1, y: 1)`, which means that the player will move one pixel to the right and down every frame. Since our frame rate is 60 FPS, that means it's moving 60 pixels per second, and since the world is only 8 pixels wide that means it flashes across the entire screen 7.5 times per second!
+
+If we want it to move at one unit per *second* instead of one unit per frame, we need to divide the velocity by the frame-rate:
+
+```swift
+func update() {
+ position += velocity / 60
+ ...
+}
+```
+
+That magic number 60 is pretty nasty though because it ties us to 60 FPS, when we might decide that we actually want to run at 30 FPS on older devices (or 120 FPS on newer ones) without it affecting the speed at which objects move.
+
+Instead of hard-coding the time factor as 1/60, let's pass it as a parameter to the update function:
+
+```swift
+mutating func update(timeStep: Double) {
+ position += velocity * timeStep
+ ...
+}
+```
+The value for `timeStep` will need to be provided by the platform layer. Although `CADisplayLink` has a number of time-related properties, it doesn't have exactly the value we need, so we'll need to compute it. To do that we'll add a `lastFrameTime` property to `ViewController`:
+
+```swift
+class ViewController: UIViewController {
+ private let imageView = UIImageView()
+ private var player = Player(position: Vector(x: 4, y: 4))
+ private var lastFrameTime = CACurrentMediaTime()
+
+ ...
+}
+```
+
+Then, in the `update()` method, replace the line:
+
+```swift
+player.update()
+```
+
+with:
+
+```swift
+let timeStep = displayLink.timestamp - lastFrameTime
+player.update(timeStep: timeStep)
+lastFrameTime = displayLink.timestamp
+```
+
+Run the game again and you'll see that the player avatar now moves much more slowly. The frame rate appears much lower too, but that's an illusion - the player avatar *really is* being moved and redrawn ~60 times per second, but its position is rounded down to the nearest whole pixel in the bitmap (which only has 8x8 resolution), and the rounded value only changes once per second.
+
+We can make the movement smoother by increasing the resolution of the bitmap from 8x8 to something much higher, but that will also make the player rectangle smaller and slower because both its size and speed are proportional to the bitmap resolution. Besides, we've hard-coded those 8x8 pixel dimensions in a couple of places already and all these magic numbers are starting to get a bit unwieldy. It's time for a refactor.
+
+### Brave New World
+
+We don't want the dimensions of the world to be coupled to the dimensions of the bitmap that we are drawing into, so let's make a new type to represent the world itself. Create a new file in the Engine module called `World.swift` with the following contents:
+
+```swift
+public struct World {
+ public let size: Vector
+ public var player: Player
+
+ public init() {
+ self.size = Vector(x: 8, y: 8)
+ self.player = Player(position: Vector(x: 4, y: 4))
+ }
+}
+
+public extension World {
+ mutating func update(timeStep: Double) {
+ player.position += player.velocity * timeStep
+ player.position.x.formTruncatingRemainder(dividingBy: size.x)
+ player.position.y.formTruncatingRemainder(dividingBy: size.y)
+ }
+}
+```
+
+And delete the `update()` method from `Player.swift`, as we won't be needing it anymore.
+
+In `ViewController.swift` replace the line:
+
+```swift
+private var player = Player(position: Vector(x: 4, y: 4))
+```
+
+with:
+
+```swift
+private var world = World()
+```
+
+Then in `ViewController.update()` replace:
+
+```swift
+player.update(timeStep: timeStep)
+```
+
+with:
+
+```swift
+world.update(timeStep: timeStep)
+```
+
+And replace:
+
+```swift
+renderer.draw(player)
+```
+
+with:
+
+```swift
+renderer.draw(world.player)
+```
+
+Currently, the world is still the same size as the bitmap (8x8 units), but because the world now has its own coordinate system independent of the bitmap, we are free to make the bitmap resolution higher without affecting the player speed.
+
+If we want to be able to draw the player avatar at different scales, we can no longer just represent it as a single pixel. Let's introduce a rectangle type to represent the player's size on screen. Create a new file in the Engine module called `Rect.swift` with the following contents:
+
+```swift
+public struct Rect {
+ var min, max: Vector
+
+ public init(min: Vector, max: Vector) {
+ self.min = min
+ self.max = max
+ }
+}
+```
+
+We'll also add a convenience method to `Bitmap` to draw a `Rect`:
+
+```swift
+public extension Bitmap {
+ ...
+
+ mutating func fill(rect: Rect, color: Color) {
+ for y in Int(rect.min.y) ..< Int(rect.max.y) {
+ for x in Int(rect.min.x) ..< Int(rect.max.x) {
+ self[x, y] = color
+ }
+ }
+ }
+}
+```
+
+Now that the player avatar is a rectangle rather than a single pixel, we should make the size configurable. Add a `radius` property to `Player`:
+
+```swift
+public struct Player {
+ public let radius: Double = 0.5
+ ...
+}
+```
+
+Radius might seem an odd way to specify the size of a square, and you may be wondering why we don't use a width and height, but a radius value will be easier to work with (as we'll see in just a second). Add a computed property to `Player` to get the bounding `Rect` (in world units):
+
+```swift
+public extension Player {
+ var rect: Rect {
+ let halfSize = Vector(x: radius, y: radius)
+ return Rect(min: position - halfSize, max: position + halfSize)
+ }
+}
+```
+
+Here is why the radius is useful. We want the player rectangle to be *centered* on their position, which means that it needs to extend by half their width/height in every direction. We can use the `radius` value for this directly instead of dividing by two every time.
+
+Finally, we can update `Renderer.draw()` to display the player as a filled rectangle instead of just a single pixel. Since the method now needs to know the world size in order to compute the scale at which to draw, we'll update the method signature to accept the whole world rather than just the player:
+
+```swift
+mutating func draw(_ world: World) {
+ let scale = Double(bitmap.height) / world.size.y
+
+ // Draw player
+ var rect = world.player.rect
+ rect.min *= scale
+ rect.max *= scale
+ bitmap.fill(rect: rect, color: .blue)
+}
+```
+
+Then, in `ViewController`, update the line:
+
+```swift
+renderer.draw(world.player)
+```
+
+to just:
+
+```swift
+renderer.draw(world)
+```
+
+By dividing the bitmap height by the world height[[7]](#footnote7) we get the relative scale between world units and pixels. That value is then used to scale the player rectangle so that its size on screen is independent of the pixel resolution of the bitmap.
+
+The bitmap size is set inside the platform layer, in `ViewController`. This makes sense because the resolution at which we draw the output should be chosen to suit the device we are displaying it on. But instead of matching the world size, let's derive it from the screen size. In `ViewController.swift` replace the line:
+
+```swift
+var renderer = Renderer(width: 8, height: 8)
+```
+
+with:
+
+```swift
+let size = Int(min(imageView.bounds.width, imageView.bounds.height))
+var renderer = Renderer(width: size, height: size)
+```
+
+Note that the view size is in *points*, which on a Retina display is only a half or a third of the actual pixel resolution. This isn't a mistake - drawing a full-screen image with the CPU is expensive, and I don't recommend trying to do it at full pixel resolution. Besides, chunky pixels are good for the authentic retro look!
+
+Run the app again and you should now at last see the blue player rectangle moving smoothly across the screen (don't worry if it's a bit jerky on the simulator - it will run silky smooth in release mode on a real device).
+
+And that's a wrap for Part 1. To recap, in this part we...
+
+* Created a platform-independent software renderer
+* Created a simple game engine with a moving player avatar
+* Created an iOS platform layer to display the output of the engine
+
+In [Part 2](Part2.md) we'll make the world a bit more interesting, and implement user input so the player can travel in any direction, not just diagonally.
+
+
+
+[[1]](#reference1) MVC is not the only architecture used for apps, but patterns such as MVP, MVVM, Clean, VIPER, etc. still share the same basic concepts of *view*, *model* and *everything else*, even if they give them different names or divide their responsibilities across additional layers.
+
+[[2]](#reference2) On older systems, when RAM was at much more of a premium and memory bandwidth was lower, it was common practice to use an [indexed color palette](https://en.wikipedia.org/wiki/Indexed_color). Since the number of colors in the palette was smaller than the total number of possible colors, the space required to store a bitmap made up of color indexes was much smaller than the 3 or 4 bytes needed to store each color directly.
+
+[[3]](#reference3) Note that there is no `import Foundation`. Nothing in the Engine module relies on any code outside the Swift standard library, even Foundation.
+
+[[4]](#reference4) Note that the constants are added via a public extension rather than directly in the `Color` struct. Members added this way will be `public` by default instead of `internal`, which saves some typing.
+
+[[5]](#reference5) Or possibly *rectangles*, because in the popular [Mode 13h](https://en.wikipedia.org/wiki/Mode_13h) VGA display resolution used by games like Wolfenstein and Doom, the pixels weren't actually square.
+
+[[6]](#reference6) I'm not a big fan of operator overloading as a rule, but extending regular math operators to support vector operands like this can make code a lot more concise and easier to follow.
+
+[[7]](#reference7) We could compute X scale and Y scale separately, but since both our world and bitmap are square we know they'll be the same. Later that will change, but we'll be replacing this code before that happens anyway.
diff --git a/Tutorial/Part2.md b/Tutorial/Part2.md
new file mode 100644
index 0000000..c661395
--- /dev/null
+++ b/Tutorial/Part2.md
@@ -0,0 +1,690 @@
+## Part 2: Mazes and Motion
+
+In [Part 1](Part1.md) we laid the groundwork for our game, creating a cross-platform graphics engine, a rectangular world with a moving player avatar, and a platform layer for iOS.
+
+We will now build upon the code we created in Part 1, adding a 2D maze, user input and collision handling. If you haven't already done so, I strongly suggest you work through Part 1 and write the code yourself, but if you prefer you can just download the project from [here](https://github.com/nicklockwood/RetroRampage/archive/Part1.zip).
+
+### Amaze Me
+
+Wolfenstein, at its heart, is a 2D maze game built on a 64x64 tile grid. A grid of fixed-size tiles is a little too crude to create realistic architecture, but it's great for making twisting corridors, hidden alcoves and secret chambers. It's also really easy to define in code.
+
+Create a new file in the Engine module called `Tile.swift` with the following contents:
+
+```swift
+public enum Tile: Int, Decodable {
+ case floor
+ case wall
+}
+```
+
+Then, create another file called `Tilemap.swift`:
+
+```swift
+public struct Tilemap: Decodable {
+ private let tiles: [Tile]
+ public let width: Int
+}
+
+public extension Tilemap {
+ var height: Int {
+ return tiles.count / width
+ }
+
+ var size: Vector {
+ return Vector(x: Double(width), y: Double(height))
+ }
+
+ subscript(x: Int, y: Int) -> Tile {
+ return tiles[y * width + x]
+ }
+}
+```
+
+If you were thinking this looks familiar, you'd be right. `Tilemap` is pretty similar to the `Bitmap` struct we defined in the first part (they even both have "map" in the name). The only difference is that instead of colors, `Tilemap` contains tiles[[1]](#footnote1).
+
+In the `World` struct, replace the stored `size` property with:
+
+```swift
+public let map: Tilemap
+```
+
+Then modify the `World.init()` method to look like this:
+
+```swift
+public init(map: Tilemap) {
+ self.map = map
+ self.player = Player(position: map.size / 2)
+}
+```
+
+Since `size` is now a sub-property of `map`, rather than being of a property of the world itself, let's add a computed property for the size so we don't break all the existing references:
+
+```swift
+public extension World {
+ var size: Vector {
+ return map.size
+ }
+
+ ...
+}
+```
+
+To draw the map we will need to know if each tile is a wall or not. We could just check if the tile type is equal to `.wall`, but later if we add other kinds of wall tile, such code would break silently.
+
+Instead, add the following code to `Tile.swift`:
+
+```
+public extension Tile {
+ var isWall: Bool {
+ switch self {
+ case .wall:
+ return true
+ case .floor:
+ return false
+ }
+ }
+}
+```
+
+The `switch` statement in the `isWall` property is *exhaustive*, meaning it doesn't include a `default:` clause, so it will fail to compile if we add another case without handling it. That means we can't accidentally introduce a bug by forgetting to update it when we add new wall types.
+
+Repeating that `switch` everywhere we access the tiles would be a nuisance, but by using the `isWall` property instead, we can ensure the code is robust without being verbose.
+
+In `Renderer.draw()` add the following block of code before `// Draw player`:
+
+```swift
+// Draw map
+for y in 0 ..< world.map.height {
+ for x in 0 ..< world.map.width where world.map[x, y].isWall {
+ let rect = Rect(
+ min: Vector(x: Double(x), y: Double(y)) * scale,
+ max: Vector(x: Double(x + 1), y: Double(y + 1)) * scale
+ )
+ bitmap.fill(rect: rect, color: .white)
+ }
+}
+```
+
+That takes care of drawing the wall tiles. But so that we can actually see the white walls against the background, in `Renderer.init()` change the line:
+
+```swift
+self.bitmap = Bitmap(width: width, height: height, color: .white)
+```
+
+to:
+
+```swift
+self.bitmap = Bitmap(width: width, height: height, color: .black)
+```
+
+Now that we have code to draw the map, *we need a map to draw*.
+
+Here we hit upon another natural division between architectural layers. So far we have only really dealt with two layers: the game engine and the platform layer. Gameplay specifics such as particular map layouts do not really belong in the engine, but they are also not tied to a particular platform. So where do we put them?
+
+### The Best Code is No Code
+
+We could create a whole new module for game-specific code, but even better would be if we could treat our game as *data* to be consumed by the engine, rather than code at all.
+
+You may have noticed that the `Tile` enum has an `Int` type, and conforms to `Decodable` (as does `Tilemap` itself). The reason for this is to facilitate defining maps in JSON rather than having to hard-code them.
+
+Create a new empty file in the main project called `Map.json` and add the following contents:
+
+```swift
+{
+ "width": 8,
+ "tiles": [
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 0, 0, 1,
+ 1, 0, 0, 1, 1, 1, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1
+ ]
+}
+```
+
+The numbers match the case values in the `Tile` enum, so `0` is a floor tile and `1` is a wall. This is a tiny and trivial map, but it will serve to test the engine. You can replace it with a different map size or layout if you prefer - it won't affect the rest of the tutorial.
+
+In `ViewController.swift`, add the following free function outside the `ViewController` class[[2]](#footnote2):
+
+```swift
+private func loadMap() -> Tilemap {
+ let jsonURL = Bundle.main.url(forResource: "Map", withExtension: "json")!
+ let jsonData = try! Data(contentsOf: jsonURL)
+ return try! JSONDecoder().decode(Tilemap.self, from: jsonData)
+}
+```
+
+Then replace the line:
+
+```swift
+private var world = World()
+```
+
+with:
+
+```swift
+private var world = World(map: loadMap())
+```
+
+Run the app again and you should see the map rendered in all its glory:
+
+
+
+The player looks a bit silly now though, drifting diagonally across the map without regard for the placement of walls. Let's fix that.
+
+### Position is Everything
+
+For maximum flexibility in the map design, the player's starting position should be defined in the JSON file rather than hard-coded.
+
+We could add a new case called `player` to the `Tile` enum, and then place the player in the map array the same way we place walls, but if we later add different types of floor tile we'd have no way to specify the type of floor underneath the player if they both occupied the same element in the `tiles` array.
+
+Instead, let's add a new type to represent non-static objects in the map. Create a new file called `Thing.swift` in the Engine module, with the following contents:
+
+```swift
+public enum Thing: Int, Decodable {
+ case nothing
+ case player
+}
+```
+
+Then add a `things` property to `Tilemap`:
+
+```swift
+public struct Tilemap: Decodable {
+ private let tiles: [Tile]
+ public let things: [Thing]
+ public let width: Int
+}
+```
+
+Finally, add an array of `things` to the `Map.json` file:
+
+```
+{
+ "width": 8,
+ "tiles": [
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 0, 0, 1,
+ 1, 0, 0, 1, 1, 1, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1
+ ],
+ "things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+ ]
+}
+```
+
+Unlike the map tiles, we can't just look up the player position from the `things` array each time we need it, because once the player starts moving their position will no longer align to the tile grid.
+
+Instead we'll use `things` to get the *initial* player position, after which the `Player` object will keep track of its own position. In `World.init()`, replace the line:
+
+```swift
+self.player = Player(position: map.size / 2)
+```
+
+with:
+
+```swift
+for y in 0 ..< map.height {
+ for x in 0 ..< map.width {
+ let position = Vector(x: Double(x) + 0.5, y: Double(y) + 0.5)
+ let thing = map.things[y * map.width + x]
+ switch thing {
+ case .nothing:
+ break
+ case .player:
+ self.player = Player(position: position)
+ }
+ }
+}
+```
+
+This code scans through the things array and adds any non-zero things (currently just the player) to the world. Note that we add `0.5` to the map X and Y coordinate when we set the player's starting position so that the player will be placed in the center of the tile instead of at the top-left corner.
+
+If we try to run the app now the compiler will complain that we are trying to return from the initializer without initializing all properties. Although we can see that there is a `1` in the `things` array, the Swift compiler isn't able to detect that statically.
+
+To fix that, add a `!` to the `player` property (that means the app will crash if we forget to include the player in `things`, but the game can't work without a player anyway):
+
+```swift
+public var player: Player!
+```
+
+With the player avatar in the correct starting position, we now need to fix the player's movement. First, let's stop the hard-coded diagonal motion. In `Player.swift`, change the line:
+
+```swift
+self.velocity = Vector(x: 1, y: 1)
+```
+
+To:
+
+```swift
+self.velocity = Vector(x: 0, y: 0)
+```
+
+Now, to make the player move properly we need to implement *user input*. User input is handled by the platform layer, as it will vary significantly between different target devices. On a Mac you'd probably use arrow keys for movement. On tvOS you'd use a joypad. For iOS, we'll need to implement touch-based movement.
+
+### Pan Handling
+
+The most effective form of touch-based interface is direct manipulation, but that doesn't translate well to games where player movement often extends beyond a single screen. Traditional game control schemes also don't work well on a touch screen - some early ports of classic games to iOS had *truly horrible* controls because they tried to replicate physical buttons and joysticks with on-screen icons[[3]](#footnote3).
+
+The best general-purpose movement control I've found for action games is a *floating joystick*, where the stick position is measured relative to where the finger first touched down instead of a fixed point. The benefit of this approach is that it's much less susceptible to finger-drift than a fixed joystick, meaning that the player can keep their eyes on the action without worrying about their finger slipping off the controls.
+
+There are a couple of different ways to implement a joystick on iOS, but the most straightforward is to use a `UIPanGestureRecognizer`. Go ahead and add a `panGesture` property to the `ViewController` class:
+
+```swift
+class ViewController: UIViewController {
+ private let imageView = UIImageView()
+ private let panGesture = UIPanGestureRecognizer()
+
+ ...
+}
+```
+
+Then add the following line to `viewDidLoad()`:
+
+```swift
+view.addGestureRecognizer(panGesture)
+```
+
+Conveniently, `UIPanGestureRecognizer` already computes the relative finger position from the point at which the gesture started, so we don't need to store any state information beyond what the gesture recognizer already tracks for us.
+
+In a normal app we'd use the target/action pattern to bind a method that would be called whenever the gesture was recognized, but that's not really helpful in this case because we don't want to be informed about pan gestures *as they happen*, we want to sample them once per frame. Instead of an action method, we'll use a computed property for the input vector.
+
+Add the following code to `ViewController`:
+
+```swift
+private var inputVector: Vector {
+ switch panGesture.state {
+ case .began, .changed:
+ let translation = panGesture.translation(in: view)
+ return Vector(x: Double(translation.x), y: Double(translation.y))
+ default:
+ return Vector(x: 0, y: 0)
+ }
+}
+```
+
+This input needs to be passed to the engine. We could directly pass the vector, but we'll be adding other inputs in future (e.g. a fire button), so let's package the input up inside a new type. Create a new file in the Engine module called `Input.swift` with the following contents:
+
+```swift
+public struct Input {
+ public var velocity: Vector
+
+ public init(velocity: Vector) {
+ self.velocity = velocity
+ }
+}
+```
+
+Then replace the following line in `ViewController.update()`:
+
+```swift
+world.update(timeStep: timeStep)
+```
+
+with:
+
+```swift
+let input = Input(velocity: inputVector)
+world.update(timeStep: timeStep, input: input)
+```
+
+And in the `World.swift` file, change the `update()` method to:
+
+```swift
+mutating func update(timeStep: Double, input: Input) {
+ player.velocity = input.velocity
+ player.position += player.velocity * timeStep
+ player.position.x.formTruncatingRemainder(dividingBy: size.x)
+ player.position.y.formTruncatingRemainder(dividingBy: size.y)
+}
+```
+
+If you try running the app now, and drag your finger around the screen, you'll see that the player moves way too fast. The problem here is that we are measuring the input vector in screen points, but player movement in the game is supposed to be specified in world units - a finger swipe across the screen covers far more points than tiles.
+
+The solution is to divide the input vector by some value - but what value? We could use the relative scale factor of the screen to the world, but we don't really want the player speed to depend on the size of the world and/or screen.
+
+What we need to do is [normalize](https://en.wikipedia.org/wiki/Normalization_(statistics)) the input velocity, so that its value is independent of the input method. Regardless of the raw input values produced by the platform layer the game should always receive a value in the range 0 to 1.
+
+
+
+In order to normalize the input, we first need to pick a maximum value. If you think about a real joystick on a gamepad, they typically have a travel distance of about half an inch. On an iPhone that's roughly 80 screen points, so a sensible joystick radius would be around 40 points (equivalent to 80 points diameter).
+
+Add a new constant to the top of the `ViewController.swift` file (not inside the `ViewController` class itself):
+
+```
+private let joystickRadius: Double = 40
+```
+
+Now we have defined the maximum, we can divide the input by that value to get the normalized result. In the `inputVector` getter, replace the line:
+
+```swift
+return Vector(x: Double(translation.x), y: Double(translation.y))
+```
+
+with:
+
+```swift
+var vector = Vector(x: Double(translation.x), y: Double(translation.y))
+vector /= joystickRadius
+return vector
+```
+
+That's *almost* the solution, but there's still a problem. If the user drags their finger further than 40 points, the resultant vector magnitude will be > 1, so we need to clamp it. In order to do that, we need to calculate the actual length of the vector.
+
+[Pythagoras's theorem](https://en.wikipedia.org/wiki/Pythagorean_theorem#Other_forms_of_the_theorem) states that the length of the hypotenuse of a right-angle triangle is equal to the square root of the sum of the squares of the other two sides. This relationship allows us to derive the length of a vector from its X and Y components.
+
+
+
+In `Vector.swift`, add a computed property for `length`:
+
+```swift
+public extension Vector {
+ var length: Double {
+ return (x * x + y * y).squareRoot()
+ }
+
+ ...
+}
+```
+
+Then in `ViewController.inputVector`, replace the line:
+
+```swift
+vector /= joystickRadius
+```
+
+with:
+
+```swift
+vector /= max(joystickRadius, vector.length)
+```
+
+With that extra check, we can be confident that the resultant magnitude will never be greater than one. Run the app again and you should find that the player now moves at reasonable speed. If anything, one world unit per second is a bit *slow* for the maximum speed. Add a `speed` constant to `Player` so we can configure the maximum rate of movement:
+
+```swift
+public struct Player {
+ public let speed: Double = 2
+ ...
+}
+```
+
+Then, in `World.update()`, multiply the input velocity by the player's speed to get the final velocity:
+
+```swift
+func update(timeStep: Double, input: Vector) {
+ player.velocity = input.velocity * player.speed
+ ...
+}
+```
+
+The speed is reasonable now, but movement feels a bit *laggy*. The problem is, if you drag more than 40 points and then try to change direction, you have to move your finger back inside the joystick radius before it will register movement again. This was exactly the problem we wanted to mitigate by having a floating joystick, but although the joystick re-centers itself whenever you touch down, its position still remains fixed until you raise your finger.
+
+Ideally, it should never be possible to move your finger off the joystick. Attempting to move your finger outside the joystick radius should just drag the joystick to a new location.
+
+
+
+In the same way that we clamped the vector to prevent input values > 1, we can also clamp the cumulative translation value of the gesture itself. `UIPanGestureRecognizer` has a `setTranslation()` method that lets you update the distance travelled, so in the `inputVector` computed property, just before the `return vector` line, add the following:
+
+```swift
+panGesture.setTranslation(CGPoint(
+ x: vector.x * joystickRadius,
+ y: vector.y * joystickRadius
+), in: view)
+```
+
+This uses the clamped `vector` value multiplied by the `joystickRadius` to get the clamped translation distance in screen points, then reassigns that to the gesture recognizer, effectively changing its record of where the gesture started.
+
+### Barrier to Entry
+
+Next, we need to solve the problem of the player moving through walls. To prevent this, we first need a way to detect if the player is intersecting a wall.
+
+Because the map is a grid, we can identify the tiles that overlap the player rectangle by taking the integer parts of the `min` and `max` coordinates of the rectangle to get the equivalent tile coordinates, then looping through the tiles in that range to see if any of them is a wall.
+
+
+
+We'll implement that as an `isIntersecting()` method on `Player`:
+
+```swift
+public extension Player {
+ ...
+
+ func isIntersecting(map: Tilemap) -> Bool {
+ let playerRect = self.rect
+ let minX = Int(playerRect.min.x), maxX = Int(playerRect.max.x)
+ let minY = Int(playerRect.min.y), maxY = Int(playerRect.max.y)
+ for y in minY ... maxY {
+ for x in minX ... maxX {
+ if map[x, y].isWall {
+ return true
+ }
+ }
+ }
+ return false
+ }
+}
+```
+
+That gives us a way to detect if the player is colliding with a wall. Now we need a way to *stop* them from doing that. A simple approach is to wait for the collision to happen, then *undo* it. Replace the code in `World.update()` with the following:
+
+```swift
+mutating func update(timeStep: Double, input: Input) {
+ let oldPosition = player.position
+ player.velocity = input.velocity * player.speed
+ player.position += player.velocity * timeStep
+ if player.isIntersecting(map: map) {
+ player.position = oldPosition
+ }
+}
+```
+
+If you run the game again, you'll see that it's now not possible to walk through walls anymore. The only problem is that now if you walk into a wall, you get *stuck*, unless you walk directly away from the wall again.
+
+Once you touch the wall, any attempt at lateral movement is likely to lead to interpenetration and be rejected by the update handler. In fact, it's not really possible to move down the one-unit-wide corridor because of this.
+
+We can mitigate the problem a bit by reducing the player's `radius`. The current value of `0.5`, means the player's diameter is one whole world unit - the same width as the corridor. Reducing it to `0.25` will give us a bit more freedom to move around. In `Player.swift` replace the line:
+
+```swift
+public let radius: Double = 0.5
+```
+
+with:
+
+```swift
+public let radius: Double = 0.25
+```
+
+That helps with the narrow corridors, but we still stick to the walls when we hit them. It would be much nicer if you could slide along the wall when you walk into it, instead of stopping dead like you'd walked into flypaper.
+
+To fix this, we need to go beyond collision *detection*, and implement collision *response*. Instead of just working out if the player is intersecting a wall, we'll calculate *how far* into the wall they have moved, and then push them out by exactly that amount.
+
+To simplify the problem, we'll start by computing the intersection vector between two rectangles, then we can extend it to work with player/map collisions.
+
+To get the intersection, we measure the overlaps between all four edges of the two rectangles and create an intersection vector for each. If any of these overlaps is zero or negative, it means the rectangles are not intersecting, so we return `nil`. Then we sort the vectors to find the shortest.
+
+
+
+Add the following code in `Rect.swift`:
+
+```swift
+public extension Rect {
+ func intersection(with rect: Rect) -> Vector? {
+ let left = Vector(x: max.x - rect.min.x, y: 0)
+ if left.x <= 0 {
+ return nil
+ }
+ let right = Vector(x: min.x - rect.max.x, y: 0)
+ if right.x >= 0 {
+ return nil
+ }
+ let up = Vector(x: 0, y: max.y - rect.min.y)
+ if up.y <= 0 {
+ return nil
+ }
+ let down = Vector(x: 0, y: min.y - rect.max.y)
+ if down.y >= 0 {
+ return nil
+ }
+ return [left, right, up, down]
+ .sorted(by: { $0.length < $1.length }).first
+ }
+}
+```
+
+In `Player.swift`, replace the `isIntersecting(map:)` method with the following:
+
+```
+func intersection(with map: Tilemap) -> Vector? {
+ let playerRect = self.rect
+ let minX = Int(playerRect.min.x), maxX = Int(playerRect.max.x)
+ let minY = Int(playerRect.min.y), maxY = Int(playerRect.max.y)
+ for y in minY ... maxY {
+ for x in minX ... maxX where map[x, y].isWall {
+ let wallRect = Rect(
+ min: Vector(x: Double(x), y: Double(y)),
+ max: Vector(x: Double(x + 1), y: Double(y + 1))
+ )
+ if let intersection = rect.intersection(with: wallRect) {
+ return intersection
+ }
+ }
+ }
+ return nil
+}
+```
+
+The basic structure of the new method is the same as before: identifying the potentially overlapping tiles and looping through them. But now we actually compute and return the intersection vector, rather than just a boolean to indicate that an intersection has occurred.
+
+In `World.swift`, change the `update()` method to use the new intersection method:
+
+```swift
+mutating func update(timeStep: Double, input: Input) {
+ player.velocity = input.velocity * player.speed
+ player.position += player.velocity * timeStep
+ if let intersection = player.intersection(with: map) {
+ player.position -= intersection
+ }
+}
+```
+
+If an intersection was detected, we subtract the vector from the player's current position. Unlike the previous solution, this won't move the player back to where they were before, but instead it will move them the shortest distance necessary to push them out of the wall. If they approach the wall at an angle, their lateral movement will be preserved and they will appear to slide along the wall instead of stopping dead.
+
+If you run the game now and try sliding against a wall you may find that the player occasionally sinks into it, or even passes right through it to the other side.
+
+The problem is that if the player is at an intersection between two walls, pushing them out of one wall tile can potentially push them right into another one, since we only handle the first intersection we detect.
+
+That would probably be corrected by a subsequent collision response, but that wouldn't happen until one or more frames later, and in the meantime the player may have moved further into the wall.
+
+
+
+Instead of waiting until the next frame to apply further collision response steps, we can replace the `if` statement in the update logic with a `while` loop.
+
+In `World.update()` replace the following line:
+
+```swift
+if let intersection = player.intersection(with: map) {
+```
+
+with:
+
+```swift
+while let intersection = player.intersection(with: map) {
+```
+
+With that change in place, the collision logic will keep nudging the player until there are no further intersections detected.
+
+This collision response mechanism is still a bit fragile however, because it assumes that a player can never walk more than their own diameter's depth through a wall in a single frame. If they did somehow manage to do that, they could get trapped - stuck in an infinite loop of being nudged out of one wall into another and then back again. If they managed to go even faster they might pass the center point of the wall and end up getting pushed out the other side.
+
+
+
+So what would it take for that to happen? At the current player movement speed of 2 units-per-second, and a player diameter of 0.5 units, they will travel their own diameter's distance in 0.25 seconds (one quarter of a second). In order to prevent the player potentially getting stuck, we need to *guarantee* that the size of each time step remains well below that limit.
+
+### Baby Steps
+
+Of course, the game would be completely unplayable at 4 frames per second, and we would never ship it in that state, but there are plenty of scenarios where a game might suffer a single-frame stutter. For example, if the player answers a phone call in the middle of a game, the game will move into the background and the frame timer will be paused. When the player resumes 10 minutes later, we don't want their avatar to suddenly jump forward several thousand pixels as if they had been moving that whole time.
+
+A brute-force solution is to cap the update `timeStep` at some sensible maximum like 1/20 (i.e. a minimum of 20 FPS). If the frame period ever goes over that, we'll just pass 1/20 as the `timeStep` anyway.
+
+Add a new constant to the top of the `ViewController.swift` file:
+
+```swift
+private let maximumTimeStep: Double = 1 / 20
+```
+
+Then in `update()`, replace the line:
+
+```swift
+let timeStep = displayLink.timestamp - lastFrameTime
+```
+
+with:
+
+```swift
+let timeStep = min(maximumTimeStep, displayLink.timestamp - lastFrameTime)
+```
+
+That gives us a little bit of breathing room, but what happens later if we want to add projectiles with a radius of 0.1 units that travel 100 units per second? Well, we can't very well set a minimum frame rate of 1000 FPS, so we need a way to make the time steps as small as possible without increasing the frame rate.
+
+The solution is to perform multiple world updates per frame. There is no rule that says world updates have to happen in lock-step with drawing[[4]](#footnote4). For a game like this, where the frame rate will likely be limited by rendering rather than physics or AI, it makes sense to perform multiple world updates for every frame drawn.
+
+If the actual frame time is 1/20th of a second, we could update the world twice, passing a `timeStep` of 1/40th each time. The player won't see the effect of those intermediate steps on screen, so we won't bother drawing them, but by reducing the time between updates we can improve the accuracy of collisions and avoid any weirdness due to lag.
+
+Since the frame rate will be 60 FPS on most systems, a time step of 1/120 (120 FPS) seems reasonable for now (we can always increase it later if we need to handle tiny and/or fast-moving objects). Add the following constant to the top of `ViewController.swift`:
+
+```swift
+private let worldTimeStep: Double = 1 / 120
+```
+
+Then, in the `update()` method, replace the line:
+
+```swift
+world.update(timeStep: timeStep, input: input)
+```
+
+with:
+
+```swift
+let worldSteps = (timeStep / worldTimeStep).rounded(.up)
+for _ in 0 ..< Int(worldSteps) {
+ world.update(timeStep: timeStep / worldSteps, input: input)
+}
+```
+
+Regardless of the frame rate, the world will now always be updated at a minimum of 120 FPS, ensuring consistent and reliable collision handling.
+
+You might be thinking that with the `worldTimeStep` logic, we no longer need the `maximumTimeStep` check, but actually this saves us from another problem - the so-called *spiral of death* that can occur when your world updates take longer to execute than your frame step.
+
+By limiting the maximum frame time, we also limit the number of iterations of the world update. 1/20 divided by 1/120 is 6, so there will never be more than six world updates performed for a single frame drawn. That means that if a frame happens to take a long time (e.g. because the game was backgrounded or paused), then when the game wakes up it won't try to execute hundreds of world updates in order to catch up with the frame time, blocking the main thread and delaying the next frame - and so on - leading to the death spiral.
+
+That's it for Part 2. In this part we...
+
+* Loaded and displayed a simple maze
+* Added touch input for moving the player
+* Added collision detection and response so the player doesn't pass through walls
+* Refined the game loop and decoupled world updates from the frame rate
+
+In [Part 3](Part3.md) we'll take the jump into 3D and learn how to view a 2D maze from a first person perspective.
+
+
+
+[[1]](#reference1) We could replace both of these with a single `Map` type, perhaps using generics or a protocol in place of `Color`/`Tile`, but despite the superficial similarity, these two structs serve significantly different roles, and are likely to diverge as we add more code, rather than benefitting from a shared implementation.
+
+[[2]](#reference2) You may wonder why we haven't bothered with any error handling in the `loadMap()` function? Since the `Map.json` file is bundled with the app at build time, any runtime errors when loading it would really be symptomatic of a programmer error, so there is no point in trying to recover gracefully.
+
+[[3]](#reference3) I'd name and shame some examples, but due to the 32-bit *Appocalypse* of iOS 11, most of them don't exist anymore.
+
+[[4]](#reference4) Modern games often run their game logic on a different thread from graphics rendering. This has some nice advantages - such as improved input responsiveness - but it adds a lot of complexity because you need to ensure that all the data structures shared by the game logic and the renderer are thread-safe.
\ No newline at end of file
diff --git a/Tutorial/Part3.md b/Tutorial/Part3.md
new file mode 100644
index 0000000..e5d8090
--- /dev/null
+++ b/Tutorial/Part3.md
@@ -0,0 +1,743 @@
+## Part 3: Ray Casting
+
+In [Part 2](Part2.md) we created a 2D tile-based maze, implemented a player avatar with simple physics and collision handling, and added a touch-based joystick for movement.
+
+Starting with [that code](https://github.com/nicklockwood/RetroRampage/archive/Part2.zip) we will now make the jump into 3D using a technique called ray casting. But first, we need to lay some groundwork.
+
+### Sense of Direction
+
+The player avatar currently has an implicit direction of movement, but in a first person game we will need to have an *explicit* direction that the player is facing, even when stationary. Go ahead and add a `direction` property to the player:
+
+```swift
+public struct Player {
+ public let speed: Double = 2
+ public let radius: Double = 0.25
+ public var position: Vector
+ public var velocity: Vector
+ public var direction: Vector
+
+ public init(position: Vector) {
+ self.position = position
+ self.velocity = Vector(x: 0, y: 0)
+ self.direction = Vector(x: 1, y: 0)
+ }
+}
+```
+
+Note that we've used a vector for the direction, rather than an angle. Vectors are a better representation for directions as they avoid ambiguities such as where the angle is measured from, and whether it's measured clockwise or counter-clockwise. They're also easier to work with mathematically.
+
+A direction vector should ideally always be *normalized* (have a length of 1), which is why we've initialized the direction with 1,0 instead of 0,0.
+
+For now, we'll derive the direction from the input velocity vector. The input vector may have a length less than 1, so we can't use it directly, but we can get the normalized direction by dividing the vector by its own length.
+
+Insert the following code at the beginning of the `World.update()` method to set the player's direction:
+
+```swift
+let length = input.velocity.length
+if length > 0 {
+ player.direction = input.velocity / length
+}
+```
+
+### Plotting a Course
+
+The player's direction should now always face whichever way they last moved. But the player's avatar is just a square - how can we see which way it's facing? We need a way to visualize the player's line of sight.
+
+Let's draw a line to illustrate the direction vector. Add a new method to `Bitmap`:
+
+```swift
+public extension Bitmap {
+ ...
+
+ mutating func drawLine(from: Vector, to: Vector, color: Color) {
+
+ }
+}
+```
+
+To draw the line, we will need to fill each pixel in the bitmap that it touches. We'll start by working out the vector between the start and end of the line, which we'll call `difference`:
+
+```swift
+mutating func drawLine(from: Vector, to: Vector, color: Color) {
+ let difference = to - from
+
+}
+```
+
+To fill every pixel along the line, without leaving gaps or filling the same pixel twice, we need to step along its length exactly one pixel at a time. Each step should be a vector that, when added to the current position, moves to the next pixel along the line.
+
+To ensure the correct step length, the number of steps should be equal to the larger of the X or Y components of the total length. That way the step vector will be exactly one pixel in the longer axis and <= to one pixel in the other axis, so there will be no gaps when we drawn the line.
+
+Add the following code to `drawLine()` to compute the step count:
+
+```swift
+let stepCount: Int
+if abs(difference.x) > abs(difference.y) {
+ stepCount = Int(abs(difference.x).rounded(.up))
+
+} else {
+ stepCount = Int(abs(difference.y).rounded(.up))
+
+}
+```
+
+The longer component of that step vector will have a value of one (because we're moving one pixel at a time). If the longer component is the X component, the Y component will have a length of `difference.y / difference.x`.
+
+
+
+In other words it will be equal to the height of the line divided by the width, so that the slope of the step vector is the same as the slope of the line itself. If the longer component is Y, the X component will have a length of `difference.x / difference.y`, for the same reason.
+
+Update the `drawLine()` code to compute the step vector as follows:
+
+```swift
+mutating func drawLine(from: Vector, to: Vector, color: Color) {
+ let difference = to - from
+ let stepCount: Int
+ let step: Vector
+ if abs(difference.x) > abs(difference.y) {
+ stepCount = Int(abs(difference.x).rounded(.up))
+ let sign = difference.x > 0 ? 1.0 : -1.0
+ step = Vector(x: 1, y: difference.y / difference.x) * sign
+ } else {
+ stepCount = Int(abs(difference.y).rounded(.up))
+ let sign = difference.y > 0 ? 1.0 : -1.0
+ step = Vector(x: difference.x / difference.y, y: 1) * sign
+ }
+
+}
+```
+
+Complete the `drawLine()` method by adding a loop from 0 to `stepCount`, advancing the step each time and filling the specified pixel:
+
+```swift
+var point = from
+for _ in 0 ..< stepCount {
+ self[Int(point.x), Int(point.y)] = color
+ point += step
+}
+```
+
+This is a very basic line-drawing routine - it makes no attempt at [antialiasing](https://en.wikipedia.org/wiki/Spatial_anti-aliasing), or even to correct for the floating point offset of the start and end points within each pixel. It will suffice for visualization purposes, however.
+
+Add the following code to the end of the `Renderer.draw()` method:
+
+```swift
+// Draw line of sight
+let end = world.player.position + world.player.direction * 100
+bitmap.drawLine(from: world.player.position * scale, to: end * scale, color: .green)
+```
+
+This draws a green line extending out from the center of the player avatar, along their line of sight. The line of sight is technically infinite, but since we can't draw an infinitely long line, we've simply used an arbitrary large number for the length (100 world units).
+
+Run the app and move around a bit. You should see something like this:
+
+
+
+### Ray, Interrupted
+
+The line we have drawn is known as a *ray* - a line that extends out indefinitely from a fixed point until it hits something. Right now, we don't actually detect if it hits anything though, so let's fix that.
+
+First, let's create a data structure to represent the ray. A ray has an *origin* (the starting point) and a direction (a normalized vector). It *doesn't* have an end-point because it continues indefinitely. Create a new file called `Ray.swift` in the Engine module with the following contents:
+
+```swift
+public struct Ray {
+ public var origin, direction: Vector
+
+ public init(origin: Vector, direction: Vector) {
+ self.origin = origin
+ self.direction = direction
+ }
+}
+```
+
+To detect where the ray is interrupted by a wall, we need to perform a *hit test*. The hit test method will take a ray as an argument and return a vector indicating the point at which the ray should terminate.
+
+The problem of detecting the hit point inside the map is in some ways quite similar to the `drawLine()` function we wrote earlier. In both cases we need to step along a line inside a grid (in this case a grid of tiles rather than pixels) and find the intersection points.
+
+For the map hit test though, we'll need to be much more precise. It is not enough to know which tile we hit - we also need to know exactly *where* on the tile the collision occurred.
+
+We'll start by simplifying the problem to a single tile. Given that we are standing inside a map tile, where is the point on the tile's boundary where our line of sight exits the tile?
+
+Although the ray origin and direction are vectors, it's actually simpler in this case if we take the X and Y components and handle them individually.
+
+Since the tiles lie on a 1x1 grid, we can get the horizontal and vertical positions of the current tile's edges by rounding the X and Y components of ray's origin up or down, depending on the direction of the ray. From those we can subtract the un-rounded X and Y values to get the distance of the origin from those edges.
+
+
+
+Add the following method to `TileMap`:
+
+```swift
+public extension Tilemap {
+ ...
+
+ func hitTest(_ ray: Ray) -> Vector {
+ var position = ray.origin
+ let edgeDistanceX, edgeDistanceY: Double
+ if ray.direction.x > 0 {
+ edgeDistanceX = position.x.rounded(.down) + 1 - position.x
+ } else {
+ edgeDistanceX = position.x.rounded(.up) - 1 - position.x
+ }
+ if ray.direction.y > 0 {
+ edgeDistanceY = position.y.rounded(.down) + 1 - position.y
+ } else {
+ edgeDistanceY = position.y.rounded(.up) - 1 - position.y
+ }
+
+ }
+}
+```
+
+You might wonder why the code to compute `edgeDistanceX` and `edgeDistanceY` is using `rounded(.down) + 1` instead of `rounded(.up)` and `rounded(.up) - 1` instead of `rounded(.down)` to get the tile boundaries? This is to handle the edge case (*literally*) where the player is already standing at the edge of the tile.
+
+For example, if the player is standing at the exact leftmost edge of a tile looking right, `rounded(.up)` will give the coordinate of the leftmost edge (not what we want), whereas `rounded(.down) + 1` will give the position of the rightmost edge. The same applies if they are standing at the rightmost edge looking left (and so on for up and down).
+
+Now that we know the X and Y distances from the edges of our current tile, we can compute the step vectors we would need to take along the ray to reach either of those edges. We can use the slope of the ray (the X component of the direction divided by the Y component) to derive the missing components of the step vectors, which we'll call `step1` and `step2`.
+
+
+
+Append the following code to `hitTest()` to compute `step1` and `step2`:
+
+```swift
+let slope = ray.direction.x / ray.direction.y
+let step1 = Vector(x: edgeDistanceX, y: edgeDistanceX / slope)
+let step2 = Vector(x: edgeDistanceY * slope, y: edgeDistanceY)
+```
+
+Adding the shortest of these two steps to our current position gives us the position at which the ray would exit the tile we are current standing in. Complete the `hitTest()` method by adding this last block of code:
+
+```swift
+if step1.length < step2.length {
+ position += step1
+} else {
+ position += step2
+}
+return position
+```
+
+This is only the first piece of the puzzle, but it's a good point at which to check our work. In `Renderer.draw()`, replace the line:
+
+```swift
+let end = world.player.position + world.player.direction * 100
+```
+
+With:
+
+```swift
+let ray = Ray(origin: world.player.position, direction: world.player.direction)
+let end = world.map.hitTest(ray)
+```
+
+Now run the app and you should see something like this:
+
+
+
+The line length may at first seem arbitrary, but if you move around it should become clear that the line always stops at the nearest edge of whatever tile we are currently standing in (which isn't necessarily a wall).
+
+Now that we have the code to calculate where the ray hits the next tile, we can put that code inside a loop to extend the ray until the edge we have hit is a wall. Update `TileMap.hitTest()` as follows:
+
+```swift
+func hitTest(_ ray: Ray) -> Vector {
+ var position = ray.origin
+ let slope = ray.direction.x / ray.direction.y
+ repeat {
+ let edgeDistanceX, edgeDistanceY: Double
+ if ray.direction.x > 0 {
+ edgeDistanceX = position.x.rounded(.down) + 1 - position.x
+ } else {
+ edgeDistanceX = position.x.rounded(.up) - 1 - position.x
+ }
+ if ray.direction.y > 0 {
+ edgeDistanceY = position.y.rounded(.down) + 1 - position.y
+ } else {
+ edgeDistanceY = position.y.rounded(.up) - 1 - position.y
+ }
+ let step1 = Vector(x: edgeDistanceX, y: edgeDistanceX / slope)
+ let step2 = Vector(x: edgeDistanceY * slope, y: edgeDistanceY)
+ if step1.length < step2.length {
+ position += step1
+ } else {
+ position += step2
+ }
+ } while // TODO: Check if we hit a wall
+ return position
+}
+```
+
+Checking the type of tile we have just hit is not quite as simple as rounding the coordinates of `position` down to the nearest whole tile. All the collision points will be on the boundary between two (or even four) tiles, and which of those tiles we need to check depends on the direction the ray is pointing.
+
+Since this is a nontrivial piece of logic (and it may be useful later), let's extract it into its own method. Add a the following method to `TileMap`, just before the `hitTest()` method:
+
+```swift
+func tile(at position: Vector, from direction: Vector) -> Tile {
+ var offsetX = 0, offsetY = 0
+ if position.x.rounded(.down) == position.x {
+ offsetX = direction.x > 0 ? 0 : -1
+ }
+ if position.y.rounded(.down) == position.y {
+ offsetY = direction.y > 0 ? 0 : -1
+ }
+ return self[Int(position.x) + offsetX, Int(position.y) + offsetY]
+}
+```
+
+As before, we've simplified the problem by handling the X and Y components separately. If either component is a whole number (i.e. it lies on a tile boundary), we use the ray direction to determine which side should be checked.
+
+We can now use `tile(at:from:)` to complete the `hitTest()` method. Replace the line:
+
+```swift
+} while // TODO: Check if we hit a wall
+```
+
+with:
+
+```swift
+} while tile(at: position, from: ray.direction).isWall == false
+```
+
+Run the app again and you'll see that the ray now terminates at the first wall it hits, instead of the first tile boundary:
+
+
+
+This may not look like much (it certainly doesn't look much like Wolfenstein!) but the ray intersection logic we just wrote is the key to the ray casting engine.
+
+### Custom Frustum
+
+To render the whole scene using ray casting (instead of casting just a single ray in the direction the player is facing) we need to cast a *fan* of rays - one for each column of pixels on the screen. In a true 3D engine we would need to cast a ray for *every pixel* on the screen, but because our map is 2D we don't need to worry about the vertical axis.
+
+The area described by the fan represents a horizontal cross-section of the [view frustum](https://en.wikipedia.org/wiki/Viewing_frustum). In most 3D projections, the frustum has so-called *near* and *far* clipping planes that define the nearest and farthest things that can be seen. The 3D scene is projected onto the near plane to form the image that appears on the screen.
+
+We don't have a far plane, as the rays we are projecting go on indefinitely until they hit something. For this reason we'll refer to the near plane as the *view plane* instead.
+
+
+
+Since our world is two-dimensional, we can think of the view plane as a line rather than a rectangle. The direction of this line is always orthogonal to the direction that the camera is facing. The orthogonal vector can be computed as (-Y, X). This will be useful, so let's add another computed property to `Vector`:
+
+```swift
+public extension Vector {
+ var orthogonal: Vector {
+ return Vector(x: -y, y: x)
+ }
+
+ ...
+}
+```
+
+The length of the line represents the *view width* in world units. This has no direct relationship to how many pixels wide the view is on-screen, it's more about how big we want the world to appear from the player's viewpoint.
+
+Since the player's `direction` vector is normalized (has a length of 1) the orthogonal vector will be as well. That means we can just multiply the orthogonal vector by the view width to get a line of the correct length to represent the view plane.
+
+```swift
+let viewPlane = world.player.direction.orthogonal * viewWidth
+```
+
+The distance of the view plane from the player is the *focal length*. This affects how near things appear to be. Together, the view width and focal length define the *Field of View* (FoV), which determines how much of the world the player can see at once.
+
+
+
+We'll set both the view width and the focal length to `1.0` for now. This gives a FoV angle of ~53 degrees[[1]](#footnote1), which is a little narrow, but we'll fix that later. Add the following code to the end of the `Renderer.draw()` method:
+
+```swift
+// Draw view plane
+let focalLength = 1.0
+let viewWidth = 1.0
+let viewPlane = world.player.direction.orthogonal * viewWidth
+let viewCenter = world.player.position + world.player.direction * focalLength
+let viewStart = viewCenter - viewPlane / 2
+let viewEnd = viewStart + viewPlane
+bitmap.drawLine(from: viewStart * scale, to: viewEnd * scale, color: .red)
+```
+
+Run the app and you will the view plane drawn as a red line in front of the player.
+
+
+
+### Fan Service
+
+Now we have defined the view plane, we can replace the single line-of-sight ray with a fan of rays spanning the player's view. Delete the following lines from `Renderer.draw()`, as we won't need them anymore:
+
+```swift
+// Draw line of sight
+let ray = Ray(origin: world.player.position, direction: world.player.direction)
+let end = world.map.hitTest(ray)
+bitmap.drawLine(from: ray.origin * scale, to: end * scale, color: .green)
+```
+
+To get the direction of the first ray in the fan, we subtract the player position from the starting point of the view plane:
+
+```swift
+let rayDirection = columnPosition - world.player.position
+```
+
+The length of `rayDirection` is the diagonal distance from the player to the view plane. We mentioned earlier that direction vectors should always be normalized, and while it doesn't matter right now, it will avoid some weird bugs later. To normalize the ray direction, we divide it by its length:
+
+```swift
+let viewPlaneDistance = rayDirection.length
+let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+)
+```
+
+Eventually we'll need one ray for every horizontal pixel in the bitmap, but for now lets just draw 10 rays to test the principle. Add the following code to end of the `Renderer.draw()` method:
+
+```swift
+// Cast rays
+let columns = 10
+let step = viewPlane / Double(columns)
+var columnPosition = viewStart
+for _ in 0 ..< columns {
+ let rayDirection = columnPosition - world.player.position
+ let viewPlaneDistance = rayDirection.length
+ let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+ )
+ let end = world.map.hitTest(ray)
+ bitmap.drawLine(from: ray.origin * scale, to: end * scale, color: .green)
+ columnPosition += step
+}
+```
+
+Run the app again and you should see a fan of ten green rays spanning the red view plane line we drew earlier.
+
+
+
+From the length of those rays, we can determine the distance of the walls of the maze at every pixel along the screen. Just one last thing to do before we enter the third dimension...
+
+### A Quick Turnaround
+
+The touch-based joystick we created in Part 2 works well for navigating a maze from a top-down perspective, but it's not set up quite right for a first-person game. Currently, pushing the joystick up always moves the player avatar towards the top of the screen, but in 3D we'll want it to move us *forward* relative to whatever direction the avatar is facing.
+
+The joystick code itself is fine, but we need to change the input model to something more appropriate. Remove the `velocity` property of the `Input` struct and replace it with the following:
+
+```swift
+public struct Input {
+ public var speed: Double
+ public var rotation: Rotation
+
+ public init(speed: Double, rotation: Rotation) {
+ self.speed = speed
+ self.rotation = rotation
+ }
+}
+```
+
+Instead of a velocity vector, our input will now be a scalar speed (positive for forwards and negative for backwards) and a left/right rotation. But what is that `Rotation` type?
+
+We used a vector previously to represent the player's direction, but that won't work for rotation. The obvious way to represent rotation is with an angle, but that introduces a slight problem. We committed to building the engine of the game without any external dependencies - even Foundation - but the Swift standard library (as of version 5.0) does not contain any trigonometric functions, which means working with angles is going to be a bit awkward.
+
+### Enter The Matrix
+
+Fortunately there is another, *even better* way to represent a rotation - a 2x2 [rotation matrix](https://en.wikipedia.org/wiki/Rotation_matrix). This matrix encapsulates a rotation in a form that can be efficiently applied to a vector using only ordinary multiplication and addition.
+
+Create a new file in the Engine module called `Rotation.swift` with the following contents:
+
+```swift
+public struct Rotation {
+ var m1, m2, m3, m4: Double
+}
+```
+
+A 2x2 matrix contains 4 numbers, hence the four parameters. The `m[x]` naming is conventional, but unless you are well-versed with linear algebra those parameters won't mean a whole lot. Let's add an initializer with slightly more ergonomic parameters:
+
+```swift
+public extension Rotation {
+ init(sine: Double, cosine: Double) {
+ self.init(m1: cosine, m2: -sine, m3: sine, m4: cosine)
+ }
+}
+```
+
+This initializer takes the sine and cosine of a given angle and produces a matrix representing a rotation by that angle. We already said that we can't (easily) use the `sin` and `cos` functions inside the engine itself, but that's OK because we'll we be doing that part in the platform layer.
+
+Finally, we'll add a function to apply the rotation to a vector. This feels most natural to write as an extension method on `Vector` itself, but we'll put that extension in the `Rotation.swift` file because it makes more sense from a grouping perspective. Add the following code in `Rotation.swift`:
+
+```swift
+public extension Vector {
+ func rotated(by rotation: Rotation) -> Vector {
+ return Vector(
+ x: x * rotation.m1 + y * rotation.m2,
+ y: x * rotation.m3 + y * rotation.m4
+ )
+ }
+}
+```
+
+Next, we need to update the code in `ViewController.swift` to send the new input model. We'll pass the Y component of the joystick `inputVector` as the speed, and use the X component to calculate the rotation.
+
+As you may recall from Part 2, the input velocity's magnitude ranges from 0 to 1, measured in world-units per second. On the engine side we multiply this by the player's maximum speed and the `timeStep` value before adding it to the position each frame.
+
+If we treat the X component as a rotational velocity value, it becomes *radians* per second rather than world units. This will also need to be multiplied by the time-step and a maximum turning speed, so let's add a `turningSpeed` property to `Player`:
+
+```swift
+public struct Player {
+ public let speed: Double = 3
+ public let turningSpeed: Double = .pi
+ ...
+}
+```
+
+We've chosen a value of `pi` radians (180 degrees) for the turning speed, which means the player will be able to turn a full revolution in two seconds.
+
+Because we are fudging things a little by doing the trigonometry in the platform layer, we'll need to multiply the rotation by the `timeStep` and `turningSpeed` on the platform layer side instead of in `World.update()` as we did for the velocity. This is a bit inelegant, but still preferable to writing our own `sin` and `cos` functions.
+
+In `ViewController.update()`, replace the line:
+
+```swift
+let input = Input(velocity: inputVector)
+```
+
+with:
+
+```swift
+let inputVector = self.inputVector
+let rotation = inputVector.x * world.player.turningSpeed * worldTimeStep
+let input = Input(
+ speed: -inputVector.y,
+ rotation: Rotation(sine: sin(rotation), cosine: cos(rotation))
+)
+```
+
+Finally, in `World.update()`, replace the lines:
+
+```swift
+let length = input.velocity.length
+if length > 0 {
+ player.direction = input.velocity / length
+}
+player.velocity = input.velocity * player.speed
+```
+
+with:
+
+```swift
+player.direction = player.direction.rotated(by: input.rotation)
+player.velocity = player.direction * input.speed * player.speed
+```
+
+If you run the app again now, it should *look* exactly the same, but movement works differently. Swiping the joystick right or left will rotate the player clockwise/counter-clockwise respectively, and swiping up/down will move the player forwards or backwards relative to the direction they are currently facing.
+
+This is a considerably more awkward way to move the player when viewing from a fixed, top-down perspective. But that's OK, because *we won't be using that perspective from now on*.
+
+### A New Dimension
+
+The top-down 2D drawing code we've written so far won't be needed for the first person view, so you can go ahead and delete most of the code in the `Renderer.draw()` function, leaving only the following:
+
+```swift
+mutating func draw(_ world: World) {
+ let focalLength = 1.0
+ let viewWidth = 1.0
+ let viewPlane = world.player.direction.orthogonal * viewWidth
+ let viewCenter = world.player.position + world.player.direction * focalLength
+ let viewStart = viewCenter - viewPlane / 2
+
+ // Cast rays
+ let columns = 10
+ let step = viewPlane / Double(columns)
+ var columnPosition = viewStart
+ for _ in 0 ..< columns {
+ let rayDirection = columnPosition - world.player.position
+ let viewPlaneDistance = rayDirection.length
+ let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+ )
+ let end = world.map.hitTest(ray)
+
+ columnPosition += step
+ }
+}
+```
+
+Ten rays won't be enough to render a complete first-person view - we'll need one ray for every horizontal pixel of the bitmap, so replace the line:
+
+```swift
+let columns = 10
+```
+
+with:
+
+```swift
+let columns = bitmap.width
+```
+
+Inside the loop we need to compute the distance from the start of the ray to where it hits the wall:
+
+```swift
+let wallDistance = (end - ray.origin).length
+```
+
+From this we can use the [perspective projection equation](https://en.wikipedia.org/wiki/3D_projection#Weak_perspective_projection) to compute the height at which to draw the walls. The walls have a height of one world unit, so the code to compute the final wall height in pixels is:
+
+```swift
+let wallHeight = 1.0
+let height = wallHeight * focalLength / wallDistance * Double(bitmap.height)
+```
+
+Then it's just a matter of drawing that line in the bitmap, for which we can use the `drawLine()` method we wrote earlier. But instead of drawing *along* the ray, this time we'll draw a vertical line from the top to the bottom of the wall. The complete code for the draw function is below, replace Renderer.draw() with the following:
+
+```swift
+mutating func draw(_ world: World) {
+ let focalLength = 1.0
+ let viewWidth = 1.0
+ let viewPlane = world.player.direction.orthogonal * viewWidth
+ let viewCenter = world.player.position + world.player.direction * focalLength
+ let viewStart = viewCenter - viewPlane / 2
+
+ // Cast rays
+ let columns = bitmap.width
+ let step = viewPlane / Double(columns)
+ var columnPosition = viewStart
+ for x in 0 ..< columns {
+ let rayDirection = columnPosition - world.player.position
+ let viewPlaneDistance = rayDirection.length
+ let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+ )
+ let end = world.map.hitTest(ray)
+ let wallDistance = (end - ray.origin).length
+
+ // Draw wall
+ let wallHeight = 1.0
+ let height = wallHeight * focalLength / wallDistance * Double(bitmap.height)
+ let wallColor = Color.white
+ bitmap.drawLine(
+ from: Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2),
+ to: Vector(x: Double(x), y: (Double(bitmap.height) + height) / 2),
+ color: wallColor
+ )
+
+ columnPosition += step
+ }
+}
+```
+
+If you run the app now you'll see that we are standing in a 3D world.
+
+
+
+Try moving around - you should be able to walk around naturally by dragging the joystick up/down to move forwards/backwards, and left/right to turn. You'll notice that there's something a bit odd going on with the walls, but it's hard to tell exactly what it is when the view is so small and every surface is pure white.
+
+### Let There Be Light(ing)
+
+Eventually we'll be adding wall textures which will help with the contrast, but in the meantime, maybe we could do something to add some contrast?
+
+Early 3D games tended to use very simple lighting systems, as true, dynamic lights were too expensive. Wolfenstein actually had no lighting *at all*, it just used darker wall textures for North/South facing walls to add contrast.
+
+We don't have textures yet, but we can replicate Wolfenstein's approach by simply using two color tones. We know that walls are aligned on a 1x1 grid, so a wall coordinate with an exact integer Y value must be a North/South facing. In `Renderer.draw()`, replace the line
+
+```swift
+let wallColor = Color.white
+```
+
+with:
+
+```swift
+let wallColor: Color
+if end.x.rounded(.down) == end.x {
+ wallColor = .white
+} else {
+ wallColor = .gray
+}
+```
+
+Run the app again and you'll be able to see more clearly now where one wall ends and the next begins.
+
+
+
+### A Wider Perspective
+
+It would also help if we could see a bit more of the scene at once. We originally used a square aspect ratio for the bitmap because that matched up with the dimensions of the world, but now we've switched to a first-person perspective it seems a shame not to take advantage of the iPhone's glorious widescreen.
+
+Let's extend the bitmap to fill the full screen width. In `ViewController.update()` replace the lines:
+
+```swift
+let size = Int(min(imageView.bounds.width, imageView.bounds.height))
+var renderer = Renderer(width: size, height: size)
+```
+
+with:
+
+```swift
+let width = Int(imageView.bounds.width), height = Int(imageView.bounds.height)
+var renderer = Renderer(width: width, height: height)
+```
+
+Now run the app again. The view now fills the screen, but it looks stretched. Each wall tile ought to be square, but if you look at that doorway in front of us, it's clearly wider than it is tall.
+
+
+
+The stretching effect is because we changed the aspect ratio of the bitmap without updating the width of the view plane accordingly. As far as the code is concerned, the view plane is still square, but we're now stretching it to fill a rectangular bitmap. To fix that, in `Renderer.draw()`, replace the line:
+
+```swift
+let viewWidth = 1.0
+```
+
+with:
+
+```swift
+let viewWidth = Double(bitmap.width) / Double(bitmap.height)
+```
+
+Running the app again, you can see that we've fixed the stretching effect. Since the focal length is the same but the view width is wider, we've also increased the *field of view*[[2]](#footnote2).
+
+
+
+It's now also a bit easier now to see what's going on with the walls. They're... curved? *How are the walls curved?! Literally every line we've drawn is straight!*
+
+### Fishy Business
+
+When the fan of rays hits a wall perpendicular to the player, the further the ray is from the center of the view, the longer it is. Since we are using the length of the ray to compute the distance from the wall, that means that parts of the wall further from the center appear further away, causing a *fisheye* effect as the wall bends away from the camera.
+
+
+
+What we really need to use is not the *length* of the ray but the perpendicular distance from the end of the ray to the view plane. We could use Pythagoras's Theorem[[3]](#footnote3) to derive the perpendicular length from the ray length, but we would need to know the length of the opposite side of the triangle, which we currently don't.
+
+Fortunately, we can make use of the handy fact that the ratio between the lengths of the sides doesn't change when you scale a triangle up or down[[4]](#footnote4).
+
+Wherever the ray hits a wall, the triangle it forms perpendicular to the view plane will have the same proportions as the triangle that the ray forms with the view plane itself, which we already know all the sides of. So we can calculate the perpendicular length as follows:
+
+1. Compute the vector from the player to the view plane along the ray (which happens to be the vector we already used for the ray direction).
+2. Divide the length of that vector by the focal length to get the ratio between distance and perpendicular distance.
+3. Divide the distance from the wall by the ratio to get the perpendicular distance to the wall.
+
+
+
+To implement that in code, in `Renderer.draw()` replace the line:
+
+```swift
+let height = wallHeight * focalLength / wallDistance * Double(bitmap.height)
+```
+
+with:
+
+```swift
+let distanceRatio = viewPlaneDistance / focalLength
+let perpendicular = wallDistance / distanceRatio
+let height = wallHeight * focalLength / perpendicular * Double(bitmap.height)
+```
+
+Run the app again and you should see that all trace of curvature has disappeared.
+
+
+
+And that will do for now. To recap, in this part we...
+
+* Implemented ray casting to detect walls in the player's field of view
+* Changed from a directional to a rotational control scheme
+* Switched to a first-person perspective
+* Added simple lighting
+
+In [Part 4](Part4.md) we'll redecorate a bit and add some *texture* to our world.
+
+
+
+[[1]](#reference1) If you're curious how the field of view angle was derived, the point where the line of sight meets the view plane forms a right-angled triangle. Using [SOHCAHTOA](http://mathworld.wolfram.com/SOHCAHTOA.html), with the `focalLength` as the *adjacent* side and half the `viewWidth` as the *opposite* side, the FoV (in radians) can be computed using the formula `fieldOfView = 2 * atan(viewWidth / 2 / focalLength)`. Multiply the result by `180 / .pi` to get the angle in degrees.
+
+[[2]](#reference2) If you recall, I mentioned before that the earlier value of 53 degrees for the field of view was a bit narrow? Well now it's ~90 degrees (assuming a screen aspect ratio of ~2:1 as found on X-series iPhones).
+
+[[3]](#reference3) The squared length of the diagonal of a right-angle triangle is equal to the sum of the squares of the other two sides. We used this before in [Part 2](Part2.md) to calculate the length of a vector length from its X and Y components.
+
+[[4]](#reference4) You can [thank Pythagoras](https://en.wikipedia.org/wiki/Pythagorean_theorem#Proof_using_similar_triangles) for that little factoid as well.
diff --git a/Tutorial/Part4.md b/Tutorial/Part4.md
new file mode 100644
index 0000000..b21bc9c
--- /dev/null
+++ b/Tutorial/Part4.md
@@ -0,0 +1,679 @@
+## Part 4: Texture Mapping
+
+In [Part 3](Part3.md) we used ray casting to render our simple 2D maze in glorious 3D. The complete code from Part 3 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part3.zip).
+
+Walking around a 3D world of our own creation is pretty neat, but the novelty wears off pretty quick with only boring white walls to look at. Let's improve the decor a bit.
+
+### Sweet Release
+
+A quick note before we begin: Graphics code of the sort we are writing is extremely sensitive to compiler optimizations. You may find that the game is almost unplayably slow when running in *Debug* mode, but should be much smoother if you run in *Release* mode.
+
+To enable Release mode, go to Edit Scheme > Run > Build Configuration (see below).
+
+
+
+Note that when running in Release mode, debugging tools may not work as expected, so you may need to toggle back to Debug when diagnosing errors.
+
+### Surface Detail
+
+The traditional way to make walls more interesting in a 3D game is to use *texture mapping*. This is a technique where a 2D image is used to decorate a 3D surface, a bit like wallpaper. The term "texture" implies some sort of roughness, but the most common form of texture map is a *color* map, which only affects the color of the surface, not its shape[[1]](#footnote1).
+
+We'll need an image to use for the wall texture. This image should be square, and needs to wrap horizontally (i.e. the rightmost column of pixels should match up seamlessly with the leftmost column).
+
+I'm going to use a 16x16 image because big pixels make it easier to see what's going on[[2]](#footnote2), but you should feel free to use any size you like[[3]](#footnote3). Drawing performance is mainly constrained by the resolution of the *output* bitmap, not the size of individual input textures.
+
+
+
+You are welcome to use this image if you don't feel like drawing your own - you can find it [here](https://github.com/nicklockwood/RetroRampage/tree/Part4/Source/Rampage/Assets.xcassets), along with all other textures used in this tutorial.
+
+We'll need some way to get the image into the game. Since the Engine module knows nothing about the filesystem, it makes sense for the platform layer to load the images and pass them into the engine. Although in theory we could make `Bitmap` conform to `Decodable` and load it directly using our own file format, iOS already has optimized mechanisms for handling images using XCAssets and `.car` files, and we shouldn't fight the host operating system.
+
+In the main project, add your wall texture to `Assets.xcassets` and name it `wall` (all lower-case).
+
+We're going to need more than one texture eventually, so rather than just passing in a single `Bitmap`, let's create a wrapper we can modify later once we better understand what our requirements are. Create a new file called `Textures.swift` in the Engine module with the following contents:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall
+}
+
+public struct Textures {
+ private let textures: [Texture: Bitmap]
+}
+
+public extension Textures {
+ init(loader: (String) -> Bitmap) {
+ var textures = [Texture: Bitmap]()
+ for texture in Texture.allCases {
+ textures[texture] = loader(texture.rawValue)
+ }
+ self.init(textures: textures)
+ }
+
+ subscript(_ texture: Texture) -> Bitmap {
+ return textures[texture]!
+ }
+}
+```
+
+The `Texture` enum is backed by a `String` and conforms to `CaseIterable`. This allows for a very elegant implementation of the `Textures` initializer, which requests bitmaps for textures automatically based on their case names.
+
+The `Textures` struct is basically just a wrapper around a `Dictionary`, but because the initializer implementation makes it impossible to request an image that doesn't exist, the `subscript` can safely return a non-`Optional` value.
+
+In `Renderer.swift` add a `textures` property and change the initializer to accept a `textures` parameter:
+
+```swift
+public struct Renderer {
+ public private(set) var bitmap: Bitmap
+ private let textures: Textures
+
+ public init(width: Int, height: Int, textures: Textures) {
+ self.bitmap = Bitmap(width: width, height: height, color: .black)
+ self.textures = textures
+ }
+}
+```
+
+Images in XCAssets are typically loaded using `UIImage(named:)`. We already have code to convert a `Bitmap` to a `UIImage`, so let's add the *inverse* conversion. Open `UIImage+Bitmap.swift` and add the following code:
+
+```swift
+extension Bitmap {
+ init?(image: UIImage) {
+ guard let cgImage = image.cgImage else {
+ return nil
+ }
+
+ let alphaInfo = CGImageAlphaInfo.premultipliedLast
+ let bytesPerPixel = MemoryLayout.size
+ let bytesPerRow = cgImage.width * bytesPerPixel
+
+ var pixels = [Color](repeating: .clear, count: cgImage.width * cgImage.height)
+ guard let context = CGContext(
+ data: &pixels,
+ width: cgImage.width,
+ height: cgImage.height,
+ bitsPerComponent: 8,
+ bytesPerRow: bytesPerRow,
+ space: CGColorSpaceCreateDeviceRGB(),
+ bitmapInfo: alphaInfo.rawValue
+ ) else {
+ return nil
+ }
+
+ context.draw(cgImage, in: CGRect(origin: .zero, size: image.size))
+ self.init(width: cgImage.width, pixels: pixels)
+ }
+}
+```
+
+This method initializes a `Bitmap` from a `UIImage`. I works by creating a new `CGContext` backed by an array of `Color` pixels, then drawing the `UIImage` into that context, thereby filling the `Color` array with the image contents.
+
+In `ViewController.swift`, add the following free function at the top of the file[[4]](#footnote4):
+
+```swift
+private func loadTextures() -> Textures {
+ return Textures(loader: { name in
+ Bitmap(image: UIImage(named: name)!)!
+ })
+}
+```
+
+Then add the following property to the `ViewController` class:
+
+```swift
+private let textures = loadTextures()
+```
+
+And finally, in the `update()` method change the line:
+
+```swift
+var renderer = Renderer(width: width, height: height)
+```
+
+to:
+
+```swift
+var renderer = Renderer(width: width, height: height, textures: textures)
+```
+
+Now that the wall texture is available to the engine, we actually have to use it to draw the walls. The way that texture mapping works is that for each pixel of the 3D surface shown on screen, we find the equivalent coordinate on the texture, and then draw that color.
+
+This normally involves a lot of matrix math to convert between the two coordinate systems, but fortunately because our wall surfaces are squares that exactly match the texture dimensions, and are only rotated in one plane, the mapping in this case is fairly straightforward.
+
+We currently draw the walls one column of pixels at a time, with the height of each column determined by its distance from the camera. We know the top and bottom of the wall match up with the top and bottom of the texture, so the only tricky part will be mapping from the X position of the column on screen to the X position of the column within the texture.
+
+
+
+Again, the grid-based nature of the map really helps here. The `Tilemap.hitTest()` method provides the world coordinate at which each ray hits the wall. Because the walls are arranged on a 1x1 world-unit grid, the fractional part of the coordinate value represents how far along the wall the ray landed, in the range 0 - 1. We can take this value and multiply it by the texture width to get the correct X offset within the texture.
+
+We'll need to replace the `drawLine()` call with a new method that copies a column of the wall texture to the output bitmap. Add the following code to `Bitmap.swift`:
+
+```swift
+public extension Bitmap {
+ ...
+
+ mutating func drawColumn(_ sourceX: Int, of source: Bitmap, at point: Vector, height: Double) {
+ let start = Int(point.y), end = Int(point.y + height) + 1
+ let stepY = Double(source.height) / height
+ for y in max(0, start) ..< min(self.height, end) {
+ let sourceY = (Double(y) - point.y) * stepY
+ let sourceColor = source[sourceX, Int(sourceY)]
+ self[Int(point.x), y] = sourceColor
+ }
+ }
+}
+```
+
+This method is similar to `drawLine()`, but simpler because the line it draws is always vertical, so we are only stepping along the Y axis. The `sourceX` parameter is the X coordinate of the column of pixels within the source bitmap that we need to copy. The `point` parameter specifies the starting position in the destination bitmap to begin drawing, and `height` indicates the output height of the column of pixels to be drawn.
+
+The code first computes the `start` and `end` pixel positions in the destination bitmap, then loops through them. Because the actual size of the source bitmap may be larger or smaller than the size at which it is being drawn, we need to calculate the Y position of each pixel in the source for the current output Y position in the destination - that's what the `stepY` value is for.
+
+Now we need to update `Renderer.draw()` to use this method. Replace the lines:
+
+```swift
+let wallColor: Color
+if end.x.rounded(.down) == end.x {
+ wallColor = .white
+} else {
+ wallColor = .gray
+}
+bitmap.drawLine(
+ from: Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2),
+ to: Vector(x: Double(x), y: (Double(bitmap.height) + height) / 2),
+ color: wallColor
+)
+```
+
+with:
+
+```swift
+let wallTexture = textures[.wall]
+let wallX = end.x - end.x.rounded(.down)
+let textureX = Int(wallX * Double(wallTexture.width))
+let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2)
+bitmap.drawColumn(textureX, of: wallTexture, at: wallStart, height: height)
+```
+
+The first line takes the `end` position vector (the point where the ray intersects the wall) and extracts just the fractional part of the X component (`wallX`). This is then used to determine which column of the texture to draw (`textureX`). Try running the app:
+
+
+
+Hmm... well that's *almost* right. The problem is that we're only using the X component of the wall position for the texture offset, but we need to use either the X *or* Y parts, depending on whether it's a vertical (North/South) or horizontal (West/East) wall.
+
+We actually just deleted some code that checked if the wall was horizontal or vertical in order to select the correct wall color, so let's bring that back and use it to select either the X or Y component of the `end` position to use for the texture coordinate. Still in `Renderer.draw()`, replace the line:
+
+```swift
+let wallX = end.x - end.x.rounded(.down)
+```
+
+with:
+
+```swift
+let wallX: Double
+if end.x.rounded(.down) == end.x {
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+Run the app again and you'll see that the smeared rear wall is now textured correctly. If you look *closely* however, you may notice a small glitch running horizontally along the middle row of pixels on the screen.
+
+
+
+This glitch is caused by floating point rounding errors where the edge of the texture pixels are too close to the pixel boundary on screen. The computed texture coordinate is almost exactly on the boundary between two texture pixels, so it flips between the two as alternate columns are drawn.
+
+A simple (if somewhat hack-y) solution is just to add a tiny offset to the rendering position so the values no longer clash. In `Renderer.draw()` replace the line:
+
+```swift
+let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2)
+```
+
+with:
+
+```swift
+let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2 + 0.001)
+```
+
+### Watch Your Tone
+
+The walls look a lot better with textures applied, but we've lost the nice two-tone lighting effect.
+
+Wolfenstein achieved this effect by using two sets of textures. The fixed 256-color palette meant it was no simple feat to adjust brightness programmatically. Darker versions of a given color didn't necessarily exist in the palette, and in those cases a suitable substitute would have to be chosen by hand by the texture artist.
+
+Since we're working with 32-bit color, it's actually a lot simpler for us to darken a given image at runtime, but for now we'll just do as Wolfenstein did and create a second copy of the texture.
+
+Add a second, darker copy of `wall` called `wall2` to XCAssets, and add a matching case to the `Texture` enum:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+}
+```
+
+The same `if` statement we originally used to select the wall color (and then repurposed to help calculate the texture coordinate) can be used to select the correct wall texture. In `Renderer.draw()` replace the following code:
+
+```swift
+let wallTexture = textures[.wall]
+let wallX: Double
+if end.x.rounded(.down) == end.x {
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+with:
+
+```swift
+let wallTexture: Bitmap
+let wallX: Double
+if end.x.rounded(.down) == end.x {
+ wallTexture = textures[.wall]
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallTexture = textures[.wall2]
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+Run the app again and you'll see that the two-tone lighting is back, helping to make the world look more solid.
+
+
+
+### The Floor in the Plan
+
+That's the wallpaper sorted, but what about the floor and ceiling? You may be surprised to hear (if you haven't played it in the last 25 years) that Wolfenstein actually didn't bother with floor or ceiling textures, instead opting for a simple solid color.
+
+The process for drawing floor and ceiling textures is more computationally expensive than for walls, requiring a depth calculation for every pixel rather than just one for each column. But while the machines of Wolfenstein's era may have struggled with it, it's not really a problem for a modern CPU.
+
+Let's depart from strict adherence to the limitations of the Wolfenstein engine, and draw ourselves a textured floor.
+
+To draw the floor we need to fill the column of pixels below each column of the wall up to the edge of the screen. Let's begin by just filling the floor with a solid color, one column at a time. In the `Renderer.draw()` method, just before `columnPosition += step`, add the following:
+
+```swift
+// Draw floor
+let floorStart = Int(wallStart.y + height) + 1
+for y in min(floorStart, bitmap.height) ..< bitmap.height {
+ bitmap[x, y] = .red
+}
+```
+
+The `floorStart` value is basically the same as the upper bound we used for the `drawColumn()` method - in other words the floor starts exactly where the wall ends. The `min()` function is needed because if the wall is too close to the player, `floorStart` would actually be below the bottom edge of the screen, resulting in an invalid range. Run the app and you should see a solid red floor.
+
+
+
+Now that we know which pixels we are going to fill, we need a texture to fill them with. Go ahead and add two new texture called `floor` and `ceiling` to both the XCAssets and the `Texture` enum in `Textures.swift`. Like the walls, these textures should be square, but this time they will need to tile both horizontally *and* vertically to avoid ugly seams on the floor.
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+ case floor, ceiling
+}
+```
+
+Because the pixels in each column of the floor are not all at the same distance from the camera, we cannot use the `drawColumn()` function we wrote earlier. We'll have to compute the correct color for each pixel individually.
+
+When drawing the walls, we first computed the position of the wall in world units, and then used the [perspective projection equation](https://en.wikipedia.org/wiki/3D_projection#Weak_perspective_projection) to transform that into screen coordinates. This time we're going to go the *other way* and derive the world coordinate from the screen position.
+
+The map position at `floorStart` is the same distance away as the wall itself - in other words it will be `wallDistance` world units from the camera. Each successive pixel we step over in the loop moves the position closer to the camera. The distance of that position from the camera can be derived by applying the perspective equation in reverse.
+
+The first step is to convert the Y position (in pixels) to a normalized position relative to the view plane. We do that by dividing by the output bitmap height:
+
+```swift
+let normalizedY = Double(y) / Double(bitmap.height)
+```
+
+That gives us a value for `normalizedY` where zero is at the top of the view plane and one is at the bottom. The vanishing point (the point at which the floor or ceiling would be infinitely away) is in the middle of the screen, so we actually want the zero value of `normalizedY` to be the *center* of the screen, not the top. We can achieve that by multiplying the value by two and then subtracting one:
+
+```swift
+let normalizedY = (Double(y) / Double(bitmap.height)) * 2 - 1
+```
+
+From the normalized Y value, we can use the inverse perspective equation to derive the perpendicular distance of that pixel from the camera:
+
+```swift
+let perpendicular = wallHeight * focalLength / normalizedY
+```
+
+Next, by dividing by the `distanceRatio` we calculated earlier we can determine the actual distance, and from that we can compute the map position:
+
+```swift
+let distance = perpendicular * distanceRatio
+let mapPosition = ray.origin + ray.direction * distance
+```
+
+Because the tiles are on a 1x1 grid, the integer parts of the `mapPosition` X and Y components give us the tile coordinate, and the fractional parts give us the precise position within the tile itself, which we can use to derive the texture coordinate:
+
+```swift
+let tileX = mapPosition.x.rounded(.down), tileY = mapPosition.y.rounded(.down)
+let textureX = Int((mapPosition.x - tileX) * Double(floorTexture.width))
+let textureY = Int((mapPosition.y - tileY) * Double(floorTexture.height))
+```
+
+That's everything we need to texture the floor. In `Renderer.draw()` replace the following lines:
+
+```swift
+// Draw floor
+let floorStart = Int(wallStart.y + height) + 1
+for y in min(floorStart, bitmap.height) ..< bitmap.height {
+ bitmap[x, y] = .red
+}
+```
+
+with:
+
+```swift
+// Draw floor
+let floorTexture = textures[.floor]
+let floorStart = Int(wallStart.y + height) + 1
+for y in min(floorStart, bitmap.height) ..< bitmap.height {
+ let normalizedY = (Double(y) / Double(bitmap.height)) * 2 - 1
+ let perpendicular = wallHeight * focalLength / normalizedY
+ let distance = perpendicular * distanceRatio
+ let mapPosition = ray.origin + ray.direction * distance
+ let tileX = mapPosition.x.rounded(.down), tileY = mapPosition.y.rounded(.down)
+ let textureX = Int((mapPosition.x - tileX) * Double(floorTexture.width))
+ let textureY = Int((mapPosition.y - tileY) * Double(floorTexture.height))
+ bitmap[x, y] = floorTexture[textureX, textureY]
+}
+```
+
+Run the game and you should see that the floor is now textured.
+
+
+
+To draw the ceiling we could duplicate this code, with a slight change to the `normalizedY` calculation. But there's actually an even simpler way - because the ceiling's coordinates are a mirror of the floor's, we can draw them both with the same loop by just inverting the output Y coordinate. Replace the lines:
+
+```swift
+// Draw floor
+let floorTexture = textures[.floor]
+```
+
+with:
+
+```swift
+// Draw floor and ceiling
+let floorTexture = textures[.floor], ceilingTexture = textures[.ceiling]
+```
+
+And then just below the line that sets the floor pixel color:
+
+```swift
+bitmap[x, y] = floorTexture[textureX, textureY]
+```
+
+add the following line to fill the equivalent ceiling pixel:
+
+```swift
+bitmap[x, bitmap.height - y] = ceilingTexture[textureX, textureY]
+```
+
+Run the game again and you'll see that the textured floor now has a matching ceiling.
+
+
+
+### Not Normal
+
+We cheated a little by re-using the floor texture coordinates for the ceiling - not because they don't match up (they do) - but because the ceiling texture isn't *necessarily* the same size as the floor texture. If the floor and ceiling textures had different resolutions then this logic wouldn't work.
+
+We could solve this by duplicating the `textureX` and `textureY` variables and calculating them separately for each texture, but that's nasty. What we really want is to be able to look up texture pixels using *normalized* coordinates, so we don't have to worry about their actual pixel dimensions.
+
+Let's add a way to do that. In `Bitmap.swift` add the following code just below the existing `subscript`:
+
+```swift
+subscript(normalized x: Double, y: Double) -> Color {
+ return self[Int(x * Double(width)), Int(y * Double(height))]
+}
+```
+
+Now, back in the `Renderer.draw()` method, a little way below the `// Draw floor and ceiling` comment, replace the lines:
+
+```swift
+let textureX = Int((mapPosition.x - tileX) * Double(floorTexture.width))
+let textureY = Int((mapPosition.y - tileY) * Double(floorTexture.height))
+bitmap[x, y] = floorTexture[textureX, textureY]
+bitmap[x, bitmap.height - y] = ceilingTexture[textureX, textureY]
+```
+
+with:
+
+```swift
+let textureX = mapPosition.x - tileX, textureY = mapPosition.y - tileY
+bitmap[x, y] = floorTexture[normalized: textureX, textureY]
+bitmap[x, bitmap.height - y] = ceilingTexture[normalized: textureX, textureY]
+```
+
+The new code is more concise, but more importantly it's *correct*, regardless of the relative texture resolutions.
+
+### Variety Show
+
+Even with textures, the world looks a bit monotonous when every wall is the same. Currently the wall and floor textures are always the same because they're hard-coded in the renderer, but we really want these to be specified by the *map*.
+
+We'll start by adding two new wall types and a new floor type to the XCAssets file. For the walls, we need two textures for each new type (light and dark).
+
+
+
+In `Textures.swift` extend the `Texture` enum with these five additional cases:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+ case crackWall, crackWall2
+ case slimeWall, slimeWall2
+ case floor
+ case crackFloor
+ case ceiling
+}
+```
+
+Note that the order and grouping of the cases in the enum isn't important, as the values are keyed by name rather than index.
+
+Next, we need to extend the `Tile` enum with new cases as well:
+
+```swift
+public enum Tile: Int, Decodable {
+ case floor
+ case wall
+ case crackWall
+ case slimeWall
+ case crackFloor
+}
+```
+
+In this case the order *is* important. Because we are decoding these values by index from the `Map.json` file, if we change the order then it will break the map. If you'd prefer to have the freedom to group them logically, you can preserve compatibility with the JSON by setting the indices explicitly:
+
+```swift
+public enum Tile: Int, Decodable {
+ // Floors
+ case floor = 0
+ case crackFloor = 4
+
+ // Walls
+ case wall = 1
+ case crackWall = 2
+ case slimeWall = 3
+}
+```
+
+Now that we have more wall types, we'll need to update the `Tile.isWall` helper property we wrote earlier. Add the extra cases as follows:
+
+```swift
+var isWall: Bool {
+ switch self {
+ case .wall, .crackWall, .slimeWall:
+ return true
+ case .floor, .crackFloor:
+ return false
+ }
+}
+```
+
+Since there are two textures for each wall tile (and for the floor tiles too, if you count the ceiling texture) we'll need some form of mapping between them. For now we'll just hard-code the textures for each case using a `switch`. Add a new computed property to `Tile` as follows::
+
+```swift
+public extension Tile {
+ ...
+
+ var textures: [Texture] {
+ switch self {
+ case .floor:
+ return [.floor, .ceiling]
+ case .crackFloor:
+ return [.crackFloor, .ceiling]
+ case .wall:
+ return [.wall, .wall2]
+ case .crackWall:
+ return [.crackWall, .crackWall2]
+ case .slimeWall:
+ return [.slimeWall, .slimeWall2]
+ }
+ }
+}
+```
+
+The map layout itself is specified in the `Map.json` file in the main project. Edit the `tiles` array in the JSON to include some of our new wall and floor tiles (the exact layout doesn't matter for the tutorial, so feel free to be creative):
+
+```swift
+"tiles": [
+ 1, 3, 1, 1, 3, 1, 1, 1,
+ 1, 0, 0, 2, 0, 0, 0, 1,
+ 1, 4, 0, 3, 4, 0, 0, 3,
+ 2, 0, 0, 0, 0, 0, 4, 3,
+ 1, 4, 0, 1, 1, 1, 0, 1,
+ 1, 0, 4, 2, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 4, 4, 1,
+ 1, 3, 3, 1, 1, 3, 1, 1
+],
+```
+
+That's all of the updates to the game model - now we just need to update the renderer to use the textures specified by the `Tilemap` instead of hard-coded values.
+
+We'll start with the walls. In order to select the correct texture, we first need to work out which wall we hit. You may recall that in Part 3 we added a method `tile(at:from:)` to `Tilemap` that was used to determine which tile a given ray was hitting given a hit position and direction. We can call that method again with the output of `hitTest()` to tell us which wall tile we hit, and from that we can select the correct texture.
+
+In `Renderer.draw()`, a few lines below the `// Draw wall` comment, replace the lines:
+
+```swift
+if end.x.rounded(.down) == end.x {
+ wallTexture = textures[.wall]
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallTexture = textures[.wall2]
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+with:
+
+```swift
+let tile = world.map.tile(at: end, from: ray.direction)
+if end.x.rounded(.down) == end.x {
+ wallTexture = textures[tile.textures[0]]
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallTexture = textures[tile.textures[1]]
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+We'll use a similar approach for the floor and ceiling. We can't look up the textures in advance anymore, so delete the following line from just below the `// Draw floor and ceiling` comment:
+
+```swift
+let floorTexture = textures[.floor], ceilingTexture = textures[.ceiling]
+```
+
+Now we'll use the `wallX` and `wallY` values we already computed to look up the tile, which will give us the textures. Still in the `// Draw floor and ceiling` section, find the line:
+
+```swift
+let tileX = mapPosition.x.rounded(.down), tileY = mapPosition.y.rounded(.down)
+```
+
+then insert the following code just below it:
+
+```swift
+let tile = world.map[Int(tileX), Int(tileY)]
+let floorTexture = textures[tile.textures[0]]
+let ceilingTexture = textures[tile.textures[1]]
+```
+
+And that should do it! Run the app again to see the new wall and floor tiles.
+
+
+
+### Be Wise, Optimize
+
+You may notice the frame rate dropping a bit as you move around, even when running in release mode.
+
+We've mostly avoided talking about optimization until now. It's generally a bad idea to optimize the code before you've finished implementing all the functionality, because optimized code can be harder to modify[[5]](#footnote5).
+
+In this case however, the performance *just* took an unexpected nose dive, which tells us that we probably did something dumb. Now is the best time to figure out what that might be, before we pile on more code and lose the context.
+
+You may already have an intuition what the problem is, but it's a good idea to verify your assumptions before you start performance tuning. Open up the *Time Profiler* tool in Instruments, and run the game through it (ideally this should be done on a device, but for major issues the Simulator will give a reasonable picture of what's happening as well.
+
+You should see something like the screenshot below. Pay close attention to the `Call Tree` settings - inverting the call tree and hiding system libraries makes the output easer to interpret.
+
+
+
+The top function here is what we'd expect - the game spends most of its time inside the `Renderer.draw()` method. The second line item is a bit of a surprise though - `Textures.subscript.getter`. The most expensive single call inside `draw()` is not anything to do with vector math, or writing or reading pixels from a bitmap - it's just looking up the bitmaps for the textures.
+
+It doesn't make a lot of sense that this would be an expensive operation. We aren't doing any loading or decoding - we loaded all the bitmaps for our textures in advance, and stored them in a `Dictionary`, keyed by the texture name. A hash lookup from a dictionary in Swift is by no means an expensive operation, so we'd have to be calling it *a lot* for it to dominate the trace.
+
+*And it seems that we are.*
+
+When drawing the walls, we have to do a texture lookup once for each ray, which is not too bad. Previously we were doing the same for the floor and ceiling textures (which never changed), but now that we are getting the texture from the map tile, we are looking it up again for *every single pixel* of the visible floor surface.
+
+Most of the time, the texture doesn't change between consecutive pixels, so we shouldn't have to look it up again, we can just cache the most recently used texture and use it again. The problem (as always) with caching is knowing when to expire the cache.
+
+As we draw the floor (and ceiling), we need to fetch a new texture every time the tile type changes, but *only* when it changes. The `Tile` type is just an `enum` backed by an `Int`, so it's very cheap to compare. Let's use the current `Tile` as a cache key, and re-fetch the textures whenever it changes.
+
+When you hear the word "cache", you might be thinking of something like a dictionary of `Tile` to `Bitmap`, but remember that the objective here was to *avoid* the cost of a hash lookup. We only need to store a single cache entry (the last pair of texture bitmaps we used), so let's just use a few local variables outside the floor-drawing loop.
+
+In `Renderer.draw()` just below the comment `// Draw floor and ceiling`, add the following lines:
+
+```swift
+var floorTile: Tile!
+var floorTexture, ceilingTexture: Bitmap!
+```
+
+That's our cache storage. Next, replace the lines:
+
+```swift
+let floorTexture = textures[tile.textures[0]]
+let ceilingTexture = textures[tile.textures[1]]
+```
+
+with:
+
+```swift
+if tile != floorTile {
+ floorTexture = textures[tile.textures[0]]
+ ceilingTexture = textures[tile.textures[1]]
+ floorTile = tile
+}
+```
+
+And that's the cache implementation. If the tile has changed since the last loop iteration, we fetch the new floor and ceiling textures and update the `floorTile` value used as the cache key. If it hasn't changed, we do nothing.
+
+Run the game again and you should see that the frame drops have been fixed. If you compare the new Time Profiler trace, you'll also see that `Textures.subscript.getter` has been pushed way down the list and now represents only a small percentage of the total frame time.
+
+
+
+That's it for part 4. In this part we...
+
+* Added wall, floor and ceiling textures
+* Added texture variants, configurable by the map JSON
+* Encountered and fixed our first performance blip
+
+In Part 5 (TBD) we'll see about adding some other maze inhabitants to keep the player company.
+
+
+
+[[1]](#reference1) There are other types of texture map, such as [displacement maps](https://en.wikipedia.org/wiki/Displacement_mapping) that actually *can* affect the surface geometry, but such textures weren't seen in games until around 2005, coinciding with the mainstream availability of programmable GPUs capable of running complex shaders.
+
+[[2]](#reference2) OK, it's also because I'm not much of an artist and fewer pixels are easier to draw.
+
+[[3]](#reference3) Wolfenstein used 64x64 images for the wall textures, in case you were wondering. Doom used 128x128.
+
+[[4]](#reference4) As with the `loadMap()` function, we haven't bothered with error handling in `loadTextures()`. An error here would be fatal anyway, and it's better to crash as early as possible.
+
+[[5]](#reference5) As Kent Beck famously said, *"Make it work, make it right, make it fast."* (in that order).
diff --git a/Tutorial/Part5.md b/Tutorial/Part5.md
new file mode 100644
index 0000000..f55a885
--- /dev/null
+++ b/Tutorial/Part5.md
@@ -0,0 +1,727 @@
+## Part 5: Sprites
+
+In [Part 4](Part4.md) we added wall and floor textures to make our 3D environment more interesting. If you're just jumping in at this point, the code for Part 4 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part4.zip).
+
+So far we have succeeded in building a fairly compelling *wallpaper simulator*, however to make this into a game we'll need more than textured walls. It's time to add some other inhabitants to our world.
+
+### Monsters, Inc.
+
+Add a new file called `Monster.swift` to the Engine module with the following contents:
+
+```swift
+public struct Monster {
+ public var position: Vector
+
+ public init(position: Vector) {
+ self.position = position
+ }
+}
+```
+
+In `Thing.swift`, add a `monster` case to the `Thing` enum:
+
+```swift
+public enum Thing: Int, Decodable {
+ case nothing
+ case player
+ case monster
+}
+```
+
+This new monster has an index of 2, so go ahead and add a bunch of `2`s to the `things` array in `Map.json`:
+
+```swift
+"things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+]
+```
+
+Finally, in `World.swift` add a `monsters` property to the `World` struct, and update the `switch` inside `init()` to handle the `monster` case:
+
+```swift
+public struct World {
+ ...
+ public var monsters: [Monster]
+
+ public init(map: Tilemap) {
+ self.map = map
+ self.monsters = []
+ for y in 0 ..< map.height {
+ for x in 0 ..< map.width {
+ let position = Vector(x: Double(x) + 0.5, y: Double(y) + 0.5)
+ let thing = map.things[y * map.width + x]
+ switch thing {
+ case .nothing:
+ break
+ case .player:
+ self.player = Player(position: position)
+ case .monster:
+ self.monsters.append(Monster(position: position))
+ }
+ }
+ }
+ }
+}
+```
+
+That takes care of placing the monsters in the world, but how do we *draw* them?
+
+### Spritely Creatures
+
+Modern games use 3D models made from textured polygons. While this approach produces incomparable visual and animation detail, it requires a complex toolchain and engine to work with 3D assets. Back in the early '90s, the technology to render detailed 3D models in real time simply didn't exist, so a different solution had to be found. That solution was [sprites](https://en.wikipedia.org/wiki/Sprite_%28computer_graphics%29).
+
+A *sprite* is just a fancy term for an image that moves around on the screen independently of the background. Sprites were employed in 2D games from the very earliest days of computing, and many arcade machines and consoles had dedicated hardware for handling sprites.
+
+By scaling a sprite in proportion to its distance from the viewer, it can be used to simulate a 3D object[[1]](#footnote1). Flat, 2D images might seem like a poor way to represent something solid, but from a first-person perspective, a flat image that always rotates to face the viewer can be pretty convincing.
+
+Because sprites must always face the player, they can't rotate freely. Rotating the plane on which the sprite is drawn would just reveal its lack of depth and break the illusion. In order for a sprite-based character to appear to face away from the player, the sprite has to be swapped out for another image drawn from a different angle.
+
+
+
+The original DOS version of Wolfenstein used characters drawn from eight different orientations to simulate rotation, but this meant eight copies of every animation frame and pose - a huge amount of work for the artists, as each frame had to be drawn by hand. Later ports of the game such as the [SNES and Mac versions](https://wolfenstein.fandom.com/wiki/Mac_Family) saved memory by doing away with the multiple orientations and simply drawing every enemy facing the player at all times[[2]](#footnote2).
+
+We'll keep things simple and just use a single image for the monster for now. The monster sprite will be one world unit in size - the same width and height as a wall tile. As with the walls, I've used a 16x16 image for the sprite image, but feel free to use whatever resolution you like as long as it's square.
+
+
+
+The monster sprite does not need to completely fill the bounds of the image, but its feet should touch the bottom edge (unless you want it to hover). The background behind the monster should be transparent (i.e. the pixels should have an alpha component of zero). If you would like to use my terrible artwork, you can download it [here](https://github.com/nicklockwood/RetroRampage/tree/Part5/Source/Rampage/Assets.xcassets/monster.imageset).
+
+
+Add an XCAsset image called `monster`, then add a `monster` case to the `Texture` enum in `Textures.swift`:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+ case crackWall, crackWall2
+ ...
+ case monster
+}
+```
+
+To draw each monster we need to create a [billboard](https://en.wikipedia.org/wiki/2.5D#Billboarding) - a textured rectangle that always faces the player. As with the view plane, we can model this rectangle as a line because only its X and Y coordinates ever change.
+
+This next part will be much easier to visualize in 2D. In `Renderer.swift`, add a new method called `draw2D` containing the top-down drawing code we we used back in [Part 3](Part3.md):
+
+```swift
+mutating func draw2D(_ world: World) {
+ let scale = Double(bitmap.height) / world.size.y
+
+ // Draw map
+ for y in 0 ..< world.map.height {
+ for x in 0 ..< world.map.width where world.map[x, y].isWall {
+ let rect = Rect(
+ min: Vector(x: Double(x), y: Double(y)) * scale,
+ max: Vector(x: Double(x + 1), y: Double(y + 1)) * scale
+ )
+ bitmap.fill(rect: rect, color: .white)
+ }
+ }
+
+ // Draw player
+ var rect = world.player.rect
+ rect.min *= scale
+ rect.max *= scale
+ bitmap.fill(rect: rect, color: .blue)
+
+ // Draw view plane
+ let focalLength = 1.0
+ let viewWidth = 1.0
+ let viewPlane = world.player.direction.orthogonal * viewWidth
+ let viewCenter = world.player.position + world.player.direction * focalLength
+ let viewStart = viewCenter - viewPlane / 2
+ let viewEnd = viewStart + viewPlane
+ bitmap.drawLine(from: viewStart * scale, to: viewEnd * scale, color: .red)
+
+ // Cast rays
+ let columns = 10
+ let step = viewPlane / Double(columns)
+ var columnPosition = viewStart
+ for _ in 0 ..< columns {
+ let rayDirection = columnPosition - world.player.position
+ let viewPlaneDistance = rayDirection.length
+ let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+ )
+ let end = world.map.hitTest(ray)
+ bitmap.drawLine(from: ray.origin * scale, to: end * scale, color: .green)
+ columnPosition += step
+ }
+}
+```
+
+Then in `ViewController.update()`, replace the line:
+
+```swift
+renderer.draw(world)
+```
+
+with:
+
+```swift
+renderer.draw2D(world)
+```
+
+Although we have a method for drawing a line, we don't currently have a way to *model* one as a self-contained value. You might be thinking that `Ray` does this job, but `Ray` only has a start point and direction so its length is infinite/unspecified, whereas a line has a start *and* end point, so its length is part of the definition.
+
+Create a new file in the Engine module called `Billboard.swift` with the following contents:
+
+```swift
+public struct Billboard {
+ public var start: Vector
+ public var direction: Vector
+ public var length: Double
+
+ public init(start: Vector, direction: Vector, length: Double) {
+ self.start = start
+ self.direction = direction
+ self.length = length
+ }
+}
+
+public extension Billboard {
+ var end: Vector {
+ return start + direction * length
+ }
+}
+```
+
+Now, for every monster in the level, we need to create a `Billboard` that represents its sprite. Since the sprites all face the player, the plane of every sprite will be parallel to the view plane, which is itself orthogonal to the player's direction:
+
+```swift
+let spritePlane = player.direction.orthogonal
+```
+
+The width of the sprites will always be one world unit, so the length of the plane is already correct. We just need to subtract half of that length from each monster's position to get the starting points of their respective sprite billboards. Let's add a computed property to `World` that returns the billboards for each monster sprite. Add the following code in `World.swift`:
+
+```swift
+public extension World {
+ ...
+
+ var sprites: [Billboard] {
+ let spritePlane = player.direction.orthogonal
+ return monsters.map { monster in
+ Billboard(
+ start: monster.position - spritePlane / 2,
+ direction: spritePlane,
+ length: 1
+ )
+ }
+ }
+}
+```
+
+Now to draw those lines. Append the following code to the end of the `draw2D()` method in `Renderer.swift`:
+
+```swift
+// Draw sprites
+for line in world.sprites {
+ bitmap.drawLine(from: line.start * scale, to: line.end * scale, color: .green)
+}
+```
+
+Now run the game again. If you move around you'll see that the green lines representing the monster sprites always rotate to face the direction of the player[[3]](#footnote3).
+
+
+
+The view rays currently pass right through the sprites to the wall behind. In order to draw the sprites in 3D we will need to find where the rays intersect them. In `Billboard.swift` add the following placeholder method:
+
+```swift
+public extension Billboard {
+ ...
+
+ func hitTest(_ ray: Ray) -> Vector? {
+
+ }
+}
+```
+
+This should look familiar - we wrote a similar method on `Tilemap` previously. In that case we were looking for the intersection point between a `Ray` and a grid of map tiles. So how do we find the intersection point between a `Ray` and a `Billboard`?
+
+The first step is to convert the lines of the `Ray` and `Billboard` to [slope-intercept form](https://en.wikipedia.org/wiki/Linear_equation#Slope–intercept_form). The slope intercept equation defines the relationship between the X and Y coordinates along a line as:
+
+```swift
+y = m * x + c
+```
+
+In this equation, `m` is the slope of the line and `c` is the Y value at the point where the line crosses the Y axis (i.e. where X is zero).
+
+
+
+Mathematicians love to use single-letter variable names for some reason, but in the programming world that's generally considered an anti-pattern, so let's rewrite that more explicitly:
+
+```swift
+y = slope * x + intercept
+```
+
+Slope-intercept form discards the length information, so there is no difference between the slope for a line segment or a ray. For that reason, we might as well convert the `Billboard` to a `Ray`, then we can avoid having to duplicate the logic for multiple types. Add the following line to `Billboard.hitTest()`:
+
+```swift
+let lhs = ray, rhs = Ray(origin: start, direction: direction)
+```
+
+This code assigns the incoming ray to a local variable (`lhs` - short for *left-hand side*) and creates a `Ray` instance from the billboard itself, which we assign to the `rhs` variable.
+
+Now that we have two rays, we need to compute their slope intercept parameters. To get the slope of a ray, we need to take the `direction` vector and divide it's height by its width:
+
+```
+let slope = direction.y / direction.x
+```
+
+If we rearrange the slope intercept equation by subtracting `slope * x` from both sides, we get `y - slope * x = intercept`. We already have a known position on the ray - its `origin` - so if we plug in the `origin` values for `x` and `y` that will give us the intercept value:
+
+```
+let intercept = origin.y - slope * origin.x
+```
+
+Because the intercept depends on the slope, instead of having two separate properties, we'll combine slope and intercept into a single *tuple*. Add the following computed property to `Ray.swift`:
+
+```swift
+public extension Ray {
+ ...
+
+ var slopeIntercept: (slope: Double, intercept: Double) {
+ let slope = direction.y / direction.x
+ let intercept = origin.y - slope * origin.x
+ return (slope, intercept)
+ }
+}
+```
+
+Back in the `Billboard.swift` file, add the following code to `hitTest()`:
+
+```swift
+// Calculate slopes and intercepts
+let (slope1, intercept1) = lhs.slopeIntercept
+let (slope2, intercept2) = rhs.slopeIntercept
+```
+
+You may have noticed that unlike the `Tilemap.hitTest()` method, `Billboard.hitTest()` returns an *optional* `Vector`. That's because its possible that the ray doesn't intersect the billboard at all. One case where this could happen is if the ray and billboard are parallel, which would mean their slope values would be equal. Let's add a check for that:
+
+```swift
+// Check if slopes are parallel
+if slope1 == slope2 {
+ return nil
+}
+```
+
+What's next? Well, we have our slope intercept equations for the two lines:
+
+```swift
+y = slope1 * x + intercept1
+y = slope2 * x + intercept2
+```
+
+These are [simultaneous equations](https://en.wikipedia.org/wiki/System_of_linear_equations) - two separate equations that have two variables in common. In this case the variables are the X and Y coordinates of the point where the two lines cross.
+
+You solve a pair of simultaneous equations by defining one in terms of the other. We can replace `y` in the second equation with the body of the first equation, eliminating the `y` variable so we only need to find `x`:
+
+```swift
+slope1 * x + intercept1 = slope2 * x + intercept2
+```
+
+Rearranging this equation then gives us the formula for `x` in terms of values we already know:
+
+```swift
+let x = (intercept1 - intercept2) / (slope2 - slope1)
+```
+
+We now have the X coordinate of the intersection point between the ray and billboard. Given X, we can use the `y = m * x + c` equation that we started with to find Y:
+
+```swift
+let y = slope1 * x + intercept1
+```
+
+Putting it all together, the code for the `Billboard.hitTest()` function is:
+
+```swift
+func hitTest(_ ray: Ray) -> Vector? {
+ let lhs = ray, rhs = Ray(origin: start, direction: direction)
+
+ // Calculate slopes and intercepts
+ let (slope1, intercept1) = lhs.slopeIntercept
+ let (slope2, intercept2) = rhs.slopeIntercept
+
+ // Check if slopes are parallel
+ if slope1 == slope2 {
+ return nil
+ }
+
+ // Find intersection point
+ let x = (intercept1 - intercept2) / (slope2 - slope1)
+ let y = slope1 * x + intercept1
+
+ return Vector(x: x, y: y)
+}
+```
+
+Unfortunately there's an edge case (isn't there always?). For a perfectly vertical ray, the value of `direction.x` will be zero, which means that `direction.y / direction.x` will be infinite, and pretty soon we're going to end up with [NaNs](https://en.wikipedia.org/wiki/NaN) everywhere.
+
+We could add a bunch of logic to handle vertical rays as a special case, but instead we're going to use a time-honored tradition when working with floating point numbers - we're going to *fudge* it. When `direction.x` is zero (or very close to zero), we'll just add a small offset to the value (you may recall that we already used this trick once before when we added `0.001` to the texture coordinate in Part 4 to fix a rounding error).
+
+In `Billboard.hitTest()`, replace the following:
+
+```swift
+let lhs = ray, rhs = Ray(origin: start, direction: direction)
+```
+
+with:
+
+```swift
+var lhs = ray, rhs = Ray(origin: start, direction: direction)
+
+// Ensure rays are never exactly vertical
+let epsilon = 0.00001
+if abs(lhs.direction.x) < epsilon {
+ lhs.direction.x = epsilon
+}
+if abs(rhs.direction.x) < epsilon {
+ rhs.direction.x = epsilon
+}
+```
+
+The `epsilon` constant is the offset in world units. There's no great science to choosing this value - it just needs to be small enough that it won't be noticeable if an object is misplaced by this amount in the world, but large enough not to cause precision issues in the subsequent calculations[[4]](#footnote4).
+
+Now we have calculated the intersection point between the lines, let's update the drawing code so that the rays stop when they hit a sprite. In `Renderer.draw2D()`, in the `// Cast rays` section, replace the line:
+
+```swift
+let end = world.map.hitTest(ray)
+```
+
+with:
+
+```swift
+var end = world.map.hitTest(ray)
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+ if spriteDistance > (end - ray.origin).length {
+ continue
+ }
+ end = hit
+}
+```
+
+This logic loops through each sprite and checks if the ray hits it. If it does, it compares the distance from the player to the hit position with the distance to the current `end`, and updates `end` if the new position is closer. Run the game again and see how it looks.
+
+
+
+Hmm... not quite the effect we were hoping for. Several of the rays seem to be going the wrong way and/or stopping in mid-air.
+
+The problem is, our ray-to-billboard intersection code doesn't currently take the `origin` point of the ray into account. We are treating the ray as if it extends indefinitely in *both* directions. The rays in the top-down view are going the wrong way because the sprite behind us is closer than the one in front, and the slopes formed by the ray and that sprite *do* in fact intersect, even though they do so behind the player.
+
+
+
+We need to update the `hitTest()` method to return `nil` if the intersection point between the lines is outside the range we want to test. We can do that by checking if the vector between the ray origin and the intersection point lies in the same direction as the ray itself.
+
+Subtracting the ray origin from the intersection point gives us the direction vector. We can check if that direction matches the ray direction by comparing the signs of the X and Y components.
+
+```swift
+guard (x - lhs.origin.x).sign == lhs.direction.x.sign &&
+ (y - lhs.origin.y).sign == lhs.direction.y.sign else {
+ return nil
+}
+```
+
+But actually, since the X and Y components of a vector are both proportional to its length, and because of our hack that guarantees that `direction.x` will never be zero, we can figure this out using just the X components instead of the whole vector.
+
+If we take the X component of the vector from the ray origin to the intersection point, and divide it by the X component of the direction, the result will be proportional to the distance of the intersection point along the ray. If that result is less than zero it means that the intersection happens before the origin point of the ray (i.e. that the sprite is behind the player), so we can ignore it.
+
+In `Billboard.hitTest()` add the following code just before the line `return Vector(x: x, y: y)`:
+
+```swift
+// Check intersection point is in range
+let distanceAlongRay = (x - lhs.origin.x) / lhs.direction.x
+if distanceAlongRay < 0 {
+ return nil
+}
+```
+
+We also need to check that the intersection point lies between the start and end points of the sprite's billboard, otherwise the rays will stop in mid-air every time they cross any sprite plane. Add the following lines below the code we just wrote:
+
+```swift
+let distanceAlongBillboard = (x - rhs.origin.x) / rhs.direction.x
+if distanceAlongBillboard < 0 || distanceAlongBillboard > length {
+ return nil
+}
+```
+
+Run the game again and you should see that the rays all travel in the right direction, and no longer stop in mid-air.
+
+
+
+Now that we've got the intersection logic figured out, we can switch back to first-person perspective and draw the sprites in 3D.
+
+### Erase and Rewind
+
+In `ViewController.update` restore the line:
+
+```swift
+renderer.draw2D(world)
+```
+
+back to:
+
+```swift
+renderer.draw(world)
+```
+
+Then, back in `Renderer.swift`, delete the `draw2D()` method, as we won't be needing it anymore.
+
+Next, add the following code to the `Renderer.draw()` method, just before the line `columnPosition += step`:
+
+```swift
+// Draw sprites
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+
+}
+```
+
+As in the `draw2D` method, we need to compare the distance from the sprite to the player with the distance from the wall to the player. But in this case, we already have the distance from the player to the wall stored in the `wallDistance` variable so we don't need to calculate it. Add the following code to the loop:
+
+```swift
+if spriteDistance > wallDistance {
+ continue
+}
+```
+
+From the `spriteDistance` we can derive the height at which to draw the sprite - it's the same exact logic we used to draw the wall previously:
+
+```swift
+let perpendicular = spriteDistance / distanceRatio
+let height = wallHeight / perpendicular * Double(bitmap.height)
+```
+
+Now we need to derive the X coordinate to use for the sprite texture. This is proportional to the distance from the start of the sprite billboard to the hit position, divided by the billboard's length:
+
+```swift
+let spriteX = (hit - sprite.start).length / sprite.length
+let textureX = Int(spriteX * Double(wallTexture.width))
+```
+
+The rest of the code is the same as for drawing the walls. Putting it all together we have:
+
+```swift
+// Draw sprites
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+ if spriteDistance > wallDistance {
+ continue
+ }
+ let perpendicular = spriteDistance / distanceRatio
+ let height = wallHeight / perpendicular * Double(bitmap.height)
+ let spriteX = (hit - sprite.start).length / sprite.length
+ let textureX = Int(spriteX * Double(wallTexture.width))
+ let spriteTexture = textures[.monster]
+ let start = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2 + 0.001)
+ bitmap.drawColumn(textureX, of: spriteTexture, at: start, height: height)
+}
+```
+
+Run the app again and we should see... *Why, hello friend!*
+
+
+
+Why is the wall behind the monster black? You might be thinking it's because we aren't drawing the wall if the ray hits the sprite first, but that isn't it - we draw the wall first and then draw the sprite on top. The problem is actually to do with *blending*.
+
+### Will it Blend?
+
+As you may recall, every pixel in the bitmap is represented by a `Color` struct with four components. The `r`, `g` and `b` properties determine the color, and the `a` (*alpha*) component controls the transparency.
+
+Pixels with zero alpha should not be drawn to the screen. Ones with partial alpha values should be *blended* into the existing background color. At least, that's how it works normally on iOS. But we aren't using iOS to draw our pixels - we wrote our own drawing code.
+
+Our sprite has a transparent background, but at the moment, when the sprite is drawn into the bitmap, its pixels just replace the ones that are already there. The transparent pixels in the sprite aren't blended with the background, *they make the background transparent*. So why does it appear black? Because the `UIImageView` has a black background, and we're seeing right through to that.
+
+If we change the `UIImageView` background color to red, the background of the sprite will be red instead.
+
+
+
+To fix this, we'll need to implement alpha blending in `Bitmap.drawColumn()`. So how do we do that?
+
+In the era of indexed color, it wasn't practical to implement variable transparency (at least not without extremely clever palette management). Instead, games like Wolfenstein chose a single color in the palette to represent the transparency, and pixels that used that color were simply not drawn.
+
+The sprite we are using at the moment only has either fully transparent or opaque pixels, but since we are using full 32-bit color for our sprites, we may as well add proper support for alpha blending. In pseudo-code, the equation for alpha blending is:
+
+```swift
+result = backgroundColor * (1 - alpha) + foregroundColor * alpha
+```
+
+In this equation `alpha` is a value in the range 0 to 1, with 0 being fully transparent and 1 being fully opaque. This operation is actually quite expensive, requiring two multiplications for every component (a total of six for the red, green and blue components) per pixel.
+
+For this reason it is common practice to use an optimization called *premultiplied alpha* to eliminate half of those multiplications. With premultiplied alpha, the red, green and blue components in the image are premultiplied by the alpha component - i.e. every pixel in the image has already been multiplied by its alpha value. With premultiplied alpha, the blending equation is simplified to:
+
+```swift
+result = backgroundColor * (1 - alpha) + foregroundColor
+```
+
+So how do we know if our images are using premultiplied alpha or not? When we wrote the code to initialize a `Bitmap` from a `UIImage` we had to specify a `CGImageAlphaInfo` value, and the value we used was `premultipliedLast` which means that the color values in the bitmap *are* already premultiplied, so we can use the more efficient form of the blending equation.
+
+In `Bitmap.swift`, add the following new method:
+
+```swift
+public extension Bitmap {
+ ...
+
+ mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
+ let oldColor = self[x, y]
+ let inverseAlpha = 1 - Double(newColor.a) / 255
+ self[x, y] = Color(
+ r: UInt8(Double(oldColor.r) * inverseAlpha) + newColor.r,
+ g: UInt8(Double(oldColor.g) * inverseAlpha) + newColor.g,
+ b: UInt8(Double(oldColor.b) * inverseAlpha) + newColor.b
+ )
+ }
+}
+```
+
+Then in the `drawColumn()` method, replace the line:
+
+```swift
+self[Int(point.x), y] = sourceColor
+```
+
+with:
+
+```swift
+blendPixel(at: Int(point.x), y, with: sourceColor)
+```
+
+Run the app again and you'll see that the wall now shows through the background of the sprite.
+
+
+
+### We Don't Like Your Sort
+
+If you walk to the top-right corner of the map and stand behind the monster in that corner, looking down towards the monster in the bottom-right corner, you'll see something like this (that's assuming you're using the same map layout as we have in the tutorial - if not, you can just take my word for it).
+
+
+
+What's going on there? Well, basically we're seeing the far-away monster *through* the nearer one. This doesn't happen with the walls because the ray casting algorithm always stops at the wall nearest the camera, but because sprites have transparent areas we can't just draw the nearest sprite or we wouldn't be able to see other sprites showing through the gaps.
+
+The order in which we draw the sprites is determined by the order in which they are defined in the `things` array, so sprites that are further toward the bottom/right of the map will be drawn after (and therefore *on top of*) ones that are in the top/left, even if they are further away from the player.
+
+This problem still plagues modern graphics engines, and a common solution is to use the [Painter's algorithm](https://en.wikipedia.org/wiki/Painter's_algorithm) - a fancy name for a simple idea: When a painter wants to draw a person in front of a mountain, they draw the mountain first, and then the person. So likewise, when we want to draw one sprite in front of another, we need to make sure we draw the more distant sprite first.
+
+In other words, we need to sort the sprites according to their distance from the player before we draw them. In `Renderer.draw()`, add the following code just above the `// Draw sprites` comment:
+
+```swift
+// Sort sprites by distance
+var spritesByDistance: [(distance: Double, sprite: Billboard)] = []
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+ spritesByDistance.append(
+ (distance: spriteDistance, sprite: sprite)
+ )
+}
+```
+
+The first part of this loop looks a lot like the sprite drawing loop we already wrote. It iterates over the sprites, performs a hit test, then computes the distance. But instead of drawing anything, it appends a tuple of the distance and sprite itself to an array.
+
+Just below the for loop, add the following line:
+
+```swift
+spritesByDistance.sort(by: { $0.distance > $1.distance })
+```
+
+This sorts the tuples in the array in reverse order of the `distance`, so the sprite with the greatest distance appears first in the array. Finally, in the `// Draw sprites` section below, replace the line:
+
+```swift
+for sprite in world.sprites {
+```
+
+with:
+
+```swift
+for (_, sprite) in spritesByDistance {
+```
+
+This means we are now looping through the pre-sorted tuples instead of the unsorted sprites. We aren't using the distance value from the tuple, so we discard it using the `_` syntax and just keep the sprite. Try running the game again and you should find that the problem with sprite order has been resolved.
+
+
+
+### Measure Twice, Cut Once
+
+It seems inefficient that we are performing the hit test twice for each sprite, discarding the result in between. It's tempting to either include the hit position in the sorted tuples so we can reuse it in the drawing loop, or compute extra values like `textureX` in the first loop and store the results so they need not be recalculated in the second.
+
+This is the dangerous allure of *premature optimization*. It is easy to be distracted by small optimization opportunities, but applying a small optimization can often make it harder to recognize a larger one.
+
+The hit test is certainly an expensive calculation to be doing twice, but we are actually doing something *far more* wasteful. The sorting loop creates an array of billboards for every sprite in the level, then copies them into another array, then sorts them by distance from the player, and it does this every frame, for every ray. There is one ray cast for every horizontal pixel on the screen, so that's ~1000 times per frame.
+
+But because the sprites all face the player, their order doesn't change depending on their horizontal position on the screen. The (non-perpendicular) distance of each sprite varies depending on the angle of the ray, but the relative *order* of the sprites does not. That means we can hoist the sorting loop outside of the ray casting loop, and only do it once per frame instead of 1000 times.
+
+In `Renderer.draw()`, move the following block of code from its current position to just above the `//Cast rays` comment:
+
+```swift
+// Sort sprites by distance
+var spritesByDistance: [(distance: Double, sprite: Billboard)] = []
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+ spritesByDistance.append(
+ (distance: spriteDistance, sprite: sprite)
+ )
+}
+spritesByDistance.sort(by: { $0.distance > $1.distance })
+```
+
+At this point in the code we don't have a `ray` to use for the intersection test anymore, but we can use the distance from the player position to the starting point of the sprite billboard as an approximation. This won't always work correctly if two sprites are very close together, but the collision detection we'll add later should prevent that from happening anyway.
+
+In the `// Sort sprites by distance` block, replace the lines:
+
+```swift
+guard let hit = sprite.hitTest(ray) else {
+ continue
+}
+let spriteDistance = (hit - ray.origin).length
+```
+
+with:
+
+```swift
+let spriteDistance = (sprite.start - world.player.position).length
+```
+
+The `spriteDistance` value we are now using to sort the sprites is completely different from the one used inside the `//Draw sprites` loop. If we had "optimized" the rendering by combining those values, this (much more significant) optimization would not have been possible.
+
+In fact, the optimization we have done here is *also* a bit premature, and we may have cause to regret it later, but it's a good illustration of the principle that small optimizations can impede larger ones.
+
+That's it for Part 5. In this part we:
+
+* Added monsters to the map
+* Displayed the monsters in 3D using sprites
+* Implemented alpha blending
+* Learned a valuable lesson about premature optimization
+
+In [Part 6](Part6.md) we will bring the monsters to life with collision detection, animation, and some rudimentary AI.
+
+
+
+[[1]](#reference1) Long before modern GPU technology, consoles such as the [SNES](https://en.wikipedia.org/wiki/Super_Nintendo_Entertainment_System) included [built-in support](https://en.wikipedia.org/wiki/Mode_7) for scaling and rotating sprites instead of just moving them around.
+
+[[2]](#reference2) It's a testament to the immersiveness of Wolfenstein that I never noticed this fact when playing the Mac version as a teenager (although I did notice that the Mac graphics, despite being twice the resolution of the PC version, were significantly inferior artistically).
+
+[[3]](#reference3) If you're wondering why the map is left-aligned rather than centered as it was before, it's because we changed the aspect ratio of the output bitmap after we switched to a first-person view. The bitmap is now wider than it is tall, but the map is still square, so it only occupies the left-hand side.
+
+[[4]](#reference4) You might be tempted to use some *official* value for `epsilon` like `.ulpOfOne`, but don't do that. Dividing by `.ulpOfOne` will produce a gigantic number right at the limit of `Double` precision, and will certainly cause errors in the subsequent calculations.
+
diff --git a/Tutorial/Part6.md b/Tutorial/Part6.md
new file mode 100644
index 0000000..52a1dc2
--- /dev/null
+++ b/Tutorial/Part6.md
@@ -0,0 +1,719 @@
+## Part 6: Enemy Action
+
+In [Part 5](Part5.md) we added some monsters to the level, and displayed them using *sprites* - textured rectangles that scale with distance but always face the camera. The complete code for Part 5 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part5.zip).
+
+**Note: The capitalization of the texture images has changed from `lowercase` to `camelCase` since Part 5 was released. If you've been using the tutorial textures in your own project, watch out for this when updating.**
+
+The monster sprites *look* good[[1]](#footnote1), but they don't *do* very much. In fact we can walk right through them as if they weren't even there.
+
+### Don't Ghost Me
+
+The reason you can walk through the sprites is that they are ghosts. I know they look more like zombies, but they're actually ghosts. It's a feature, not a bug.
+
+OK, *fine* - I suppose we can make them solid if you insist. To make the zombies less ghostly and more zombie-ie, we'll need to implement collision handling. Fortunately we already did almost all the work for this when we implemented player-wall collisions back in [Part 2](Part2.md).
+
+To detect if the player was colliding with a wall, we modeled the player as a `Rect` and then did a `Rect`/`Rect` intersection test with the walls. We can use the same approach for `Player`/`Monster` collisions.
+
+Start by copying the computed `rect` property from `Player.swift` and adding it to `Monster.swift` as follows:
+
+```swift
+public extension Monster {
+ var rect: Rect {
+ let halfSize = Vector(x: radius, y: radius)
+ return Rect(min: position - halfSize, max: position + halfSize)
+ }
+}
+```
+
+That code depends on a `radius` property, so add a `radius` to the `Monster` as well:
+
+```swift
+public struct Monster {
+ public let radius: Double = 0.25
+
+ ...
+}
+```
+
+Now, in`Player.swift`, add the following method to the end of the `Player` extension:
+
+```swift
+func intersection(with monster: Monster) -> Vector? {
+ return rect.intersection(with: monster.rect)
+}
+```
+
+This code is just a proxy to the `Rect.insersection()` method, which returns a vector representing the overlap between the rectangles (or `nil` if they don't intersect).
+
+This gives us everything we need to determine if the player is colliding with a monster, and if so by how much we need to move them so that they won't be anymore. In `World.update()`, add the following block of code just above the `while` loop:
+
+```swift
+// Handle collisions
+for monster in monsters {
+ if let intersection = player.intersection(with: monster) {
+ player.position -= intersection
+ }
+}
+```
+
+This code loops through every monster in the map, checks if the player is intersecting them, and if so moves the player away so that they aren't. This solution isn't necessarily perfect - it's possible that if you bumped into a group of monsters, you'd end up being buffeted from one into another, but it should be good enough since you expect a certain amount of squishiness if you walk into a monster, so it's not a big problem if the collision response has some looseness to it.
+
+It's because of this looseness that we perform the monster collision loop *before* the wall collision loop - we don't mind if the player gets bounced from one monster into another, but we *definitely* don't want them to end up embedded in a wall, so we make sure that wall collisions are processed last.
+
+Run the game now and you should find that you are no longer able to walk through the monsters
+
+### You've Got to Give a Little
+
+Something feels a bit... *off* about the collision handling right now. Walking into those monsters is like walking into a brick wall because they don't have any *give*. Since they are roughly the same size as the player, and assuming they aren't glued to the floor, you'd expect to be able to push them around.
+
+Instead of applying the collision response solely to the player, what if we split it evenly between the player and monster, so that a collision pushes both player and monster in opposite directions?
+
+In order to move the monsters, we'll need to change the loop a bit. Because the monsters are structs (value types) we can't modify the monster variable in the loop - we'll need to make a copy of the monster, modify that, and then assign it back to the correct position in the `monsters` array. Replace the `// Handle collisions` block we just wrote with the following:
+
+```swift
+// Handle collisions
+for i in monsters.indices {
+ var monster = monsters[i]
+ if let intersection = player.intersection(with: monster) {
+ player.position -= intersection
+ }
+ monsters[i] = monster
+}
+```
+
+Instead of looping over the monsters directly, we are now looping over the *indices* of the array, which allows us to replace the original monster (that's what the `monsters[i] = monster` line is doing at the end of the loop[[2]](#footnote2). Still in the same block of code, replace the line:
+
+```swift
+player.position -= intersection
+```
+
+with:
+
+```swift
+player.position -= intersection / 2
+monster.position += intersection / 2
+```
+
+Now, when we bump into a monster, it will be pushed back. Because we're also being pushed back, we will move more slowly when pushing, making the interaction feel more realistic.
+
+Try running the game now and pushing some monsters around. Pretty soon, you'll notice we've introduced a new problem.
+
+
+
+### They're in the Walls!
+
+Now that we can push the monsters, we can push them *through walls*. So far, all our collision handling has been player-centric, but now that monsters can move (even if it's not of their own volition), they need their own collision logic.
+
+We already have a method to detect collisions between the player and the walls, and since the player and monster are both represented by rectangles, we can re-use that collision logic. The problem is that currently the `intersection(with:)` method we need is a member of the `Player` type, and we don't really want to copy and paste it into `Monster`. It's time for a refactor.
+
+The `Monster` and `Player` types have a bunch of properties and behavior in common, so it makes sense to allow them to share these via a common abstraction. In a traditional object-oriented language like Objective-C or Java, we might have done this by making them inherit from a common superclass, but in our game these are structs rather than classes, so they don't support inheritance.
+
+We could make them into classes, but this has all sorts of downsides, such as losing the ability to magically add serializion via the `Codable` protocol, or easily adding multithreading due to the data structures all being immutable, so let's not do that. Fortunately, Swift has a nice mechanism for sharing logic between value types, in the form of *protocol extensions*.
+
+Create a new file in the Engine module called `Actor.swift`, with the following contents:
+
+```swift
+public protocol Actor {
+ var radius: Double { get }
+ var position: Vector { get set }
+}
+```
+
+"Actor" is a generic term for entities such as the player, non-player-characters, scenery, or anything else that potentially moves or has agency within the game, as per this description from [Game Coding Complete](https://www.amazon.co.uk/gp/product/1133776574/ref=as_li_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1133776574&linkCode=as2&tag=charcoaldesig-21&linkId=5b73f74ee5a9de652e35f8c305266802):
+
+> A game actor is an object that represents a single entity in your game world. It could be an ammo pickup, a tank, a couch, an NPC, or anything you can think of. In some cases, the world itself might even be an actor. It’s important to define the parameters of game actors and to ensure that they are as flexible and reusable as possible.
+
+The `Actor` protocol we've defined makes the guarantee that any object conforming to it will have a read-only `radius` property, and a read-write `position`, which is everything we need to implement collision handling.
+
+In `Player.swift`, replace the following line:
+
+```swift
+public struct Player {
+```
+
+with:
+
+```swift
+public struct Player: Actor {
+```
+
+That says that `Player` conforms to the `Actor` protocol. We don't have to do anything else to conform to the protocol because `Player` already has a `radius` and `position` property.
+
+Next, cut the entire `public extension Player { ... }` block from `Player.swift`, and paste it into the `Actor.swift` file. Then replace all references to `Player` or `Monster` with `Actor`. The result should look like this:
+
+```swift
+public extension Actor {
+ var rect: Rect {
+ let halfSize = Vector(x: radius, y: radius)
+ return Rect(min: position - halfSize, max: position + halfSize)
+ }
+
+ func intersection(with map: Tilemap) -> Vector? {
+ let playerRect = self.rect
+ let minX = Int(playerRect.min.x), maxX = Int(playerRect.max.x)
+ let minY = Int(playerRect.min.y), maxY = Int(playerRect.max.y)
+ for y in minY ... maxY {
+ for x in minX ... maxX where map[x, y].isWall {
+ let wallRect = Rect(
+ min: Vector(x: Double(x), y: Double(y)),
+ max: Vector(x: Double(x + 1), y: Double(y + 1))
+ )
+ if let intersection = rect.intersection(with: wallRect) {
+ return intersection
+ }
+ }
+ }
+ return nil
+ }
+
+ func intersection(with actor: Actor) -> Vector? {
+ return rect.intersection(with: actor.rect)
+ }
+}
+```
+
+Now we'll do the same for `Monster`. In `Monster.swift` replace:
+
+```swift
+public struct Monster {
+```
+
+with:
+
+```swift
+public struct Monster: Actor {
+```
+
+Then you can delete the `public extension Monster { ... }` block completely, since the computed `rect` property is now inherited from the `Actor` protocol.
+
+That's the beauty of Swift's protocol extensions. In Objective-C it was possible for classes to conform to a common protocol, but they couldn't inherit behavior from it. In Swift however, we can extend the protocol with functionality that is then available to all types that conform to it. That even works for value types like struct that aren't polymorphic and don't support traditional inheritance.
+
+Anyway, enough about how awesome Swift is - let's get on with the task at hand. In `World.update()`, add the following code inside the `for` loop, just before the line `monsters[i] = monster`:
+
+```swift
+while let intersection = monster.intersection(with: map) {
+ monster.position -= intersection
+}
+```
+
+This applies the same wall collision logic we used for `Player` to every `Monster` in the map. Collision with walls is handled *after* collisions with the player, so that it overrides previous collision response handling - the monster may end up being pushed back into the player, but they shouldn't get pushed through a wall.
+
+If you run the game again though, you'll see that's not quite the case.
+
+
+
+It's no longer possible to push the monster right through the wall as before, but they still seem to be able to get stuck a little way into it - why is that?
+
+In short, it's because we've used the wrong *radius*. We set the monster's radius to 0.25, meaning that it occupies half a tile's width. But the sprite graphic we are using for the monster is 14 pixels wide inside a 16-pixel texture. Since the texture is one tile wide, that means the monster is actually 14/16ths of a tile wide - equivalent to 0.875 tiles.
+
+
+
+If the collision rectangle we use for the monsters is smaller than the sprite, the sprite will clip into the walls when the monster bumps up agains them. If we don't want that to happen, the correct radius to use for the monsters is 0.4375 (half of 0.875). In `Monster.swift`, change the line:
+
+```swift
+public let radius: Double = 0.25
+```
+
+to:
+
+```swift
+public let radius: Double = 0.4375
+```
+
+The monsters should no longer intersect the walls, however much you push them. But what about *other monsters*? It shouldn't be possible to push one monster right through another one, but right now there's nothing to prevent that. The collision handling should be able to prevent any monster from intersecting another.
+
+In order to implement monster-monster collisions we'll need to do a pairwise collision test between each monster and every other monster. For that we'll need to add a second loop through all of the monsters, *inside* the first loop.
+
+Back inside the `World.update()` method, find the line:
+
+```swift
+while let intersection = monster.intersection(with: map) {
+```
+
+Just above that line, add the following:
+
+```swift
+for j in monsters.indices where i != j {
+ if let intersection = monster.intersection(with: monsters[j]) {
+ monster.position -= intersection / 2
+ monsters[j].position += intersection / 2
+ }
+}
+```
+
+This inner loop[[3]](#footnote3) runs through the monsters again, checking for an intersection between the current monster from the outer loop (`monsters[i]`) and the current monster in the inner loop (`monsters[j]`). The `where i != j` ensures we don't check if a monster is colliding with itself (which would always be true, leading to some interesting bugs).
+
+If you're familiar with [Big O notation](https://en.wikipedia.org/wiki/Big_O_notation) for describing algorithmic complexity, this algorithm has a a *Big O* of O(n2), where *n* is the total number of monsters.
+
+An algorithm with O(n2) complexity slows down rapidly with the number of elements, and is generally considered a *bad thing*. Modern physics engines use a variety of tricks to cut down the number of collision tests they need to perform, such as subdividing objects into buckets and only checking for collisions between objects in the same or neighboring buckets.
+
+With such a small map and so few monsters, n2 will never get large enough to be a problem, so we won't worry about it for now. There is, however, a trivial optimization we can make which will halve the work we are doing.
+
+We're currently comparing every pair of monsters twice because we always compare `monsters[i]` with `monsters[j]` for every value of `i` and `j`. But if we've already compared `monsters[1]` with `monsters[2]` we don't *also* need to compare `monsters[2]` with `monsters[1]` because they're equivalent.
+
+So it follows that for the inner `for` loop, we only need to loop over monsters that we have not *already* covered in the outer loop, so instead of looping through every index apart from `i`, we can just loop through every index *after* `i`. In `World.update()` replace the line:
+
+```swift
+for j in monsters.indices where i != j {
+```
+
+with:
+
+```swift
+for j in i + 1 ..< monsters.count {
+```
+
+That should take care of monster-monster collisions. Run the app again and you should find that you now can't push monsters through walls *or* each other.
+
+The `// Handle collisions` code is maybe not the most beautiful we've written, but I'll leave refactoring it as an exercise for the reader because we have more interesting fish to fry.
+
+### Enemy State
+
+The monsters have had enough of being pushed around - it's time they got their revenge. The only problem is, they don't actually have the power of independent thought. We need to give them some *artificial intelligence*.
+
+There's an old joke in tech circles that when companies talk about their new software using "advanced AI", they probably mean it has a giant bunch of `if`/`else` statements. And it's funny because it's true - if you are trying to build a program that can "reason" about a situation and decide on a course of action, the simplest approach in most cases is to figure out all of the possible scenarios and then build a big `if` or `switch` statement with the appropriate responses to each of them.
+
+The AI for non-player characters (including enemies) in a game can be built around a [state machine](https://en.wikipedia.org/wiki/Finite-state_machine). At any given point in time, the character is in a particular state. While in that state they have a certain behavior. Certain events or *triggers* will cause them to transition to a different state, with different behavior.
+
+In Swift, the state machine can be implemented as a `switch` over an enum, with a bunch of `if`/`else` cases for handling state transitions. For now, we'll just define two states for the monsters - *idle* and *chasing*.
+
+Each monster will start out in the *idle* state. When they see the player they will switch to the *chasing* state. In the chasing state, the monster will pursue the player until it can't see them anymore, at which point it will revert back to idle.
+
+
+
+In `Monster.swift`, add the following code to the top of the file:
+
+```swift
+public enum MonsterState {
+ case idle
+ case chasing
+}
+```
+
+Then add a `state` property to the `Monster` itself:
+
+```swift
+public struct Monster: Actor {
+ ...
+ public var state: MonsterState = .idle
+
+ ...
+}
+```
+
+In `World.update()`, insert the following block of code above the `// Handle collisions` section:
+
+```swift
+// Update monsters
+for i in 0 ..< monsters.count {
+ var monster = monsters[i]
+ monster.update(in: self)
+ monsters[i] = monster
+}
+```
+
+Then, back in `Monster.swift` add the following block of code to the bottom of the file:
+
+```swift
+public extension Monster {
+ mutating func update(in world: World) {
+ switch state {
+ case .idle:
+
+ case .chasing:
+
+ }
+ }
+}
+```
+
+Here we have the outline for the AI routine - now we need to write the implementations for the two states. In `idle` mode, all the monster needs to do is wait until it sees the player. That means we need a method to detect if the monster can see the player.
+
+Since the monsters don't currently have a direction (they always face the player), we don't need to worry about their field of view. The only reason a monster *wouldn't* be able to see the player is if there was a wall between them.
+
+Still in `Monster.swift` add a `canSeePlayer()` method:
+
+```swift
+public extension Monster {
+ ...
+
+ func canSeePlayer(in world: World) -> Bool {
+
+ }
+}
+```
+
+The first thing we'll need to do is create a `Ray` from the monster to the `Player`. We've done this a few times now, so the code should require no explanation. Add the following lines to the start of the `canSeePlayer()` method:
+
+```swift
+let direction = world.player.position - position
+let playerDistance = direction.length
+let ray = Ray(origin: position, direction: direction / playerDistance)
+```
+
+We need to check if the view of the player is obstructed by a wall. We already have a way to check if a ray hits a wall, using the `Tilemap.hitTest()` method we wrote in [Part 3](Part3.md). Add the following line to the `canSeePlayer()` method:
+
+```swift
+let wallHit = world.map.hitTest(ray)
+```
+
+Now we have the point at which the ray intersects the map, we just need to check if the distance at which that occurs is closer or farther than the player. Add the following lines to complete the `canSeePlayer()` method:
+
+```swift
+let wallDistance = (wallHit - position).length
+return wallDistance > playerDistance
+```
+
+Using that method, we can now implement the monster's AI. Replace the empty switch cases in `Monster.update()` with the following:
+
+```swift
+switch state {
+case .idle:
+ if canSeePlayer(in: world) {
+ state = .chasing
+ }
+case .chasing:
+ guard canSeePlayer(in: world) else {
+ state = .idle
+ break
+ }
+}
+```
+
+So now if the monster sees the player it will enter the `chasing` state, and if it it can't see the player anymore it will return to the `idle` state. Just one last thing to do - we need to make it actually chase the player!
+
+### The Chase is On
+
+The `Player` has a `speed` property to control their maximum speed. Let's add one to `Monster` too:
+
+```swift
+public struct Monster: Actor {
+ public let speed: Double = 0.5
+ ...
+}
+```
+
+The player has a maximum speed of `2`, but we've set the monster's maximum speed to `0.5`. They're supposed to be zombies, so we don't really want them sprinting around. We may as well add a `velocity` property too while we're here:
+
+```swift
+public struct Monster: Actor {
+ ...
+ public var position: Vector
+ public var velocity: Vector = Vector(x: 0, y: 0)
+ public var state: MonsterState = .idle
+
+ ...
+}
+```
+
+In the `Monster.update()` method, add the following code inside `case .idle:`, after the `if` statement:
+
+```swift
+velocity = Vector(x: 0, y: 0)
+```
+
+Then, inside `case .chasing:`, after the `guard` statement, add the following:
+
+```swift
+let direction = world.player.position - position
+velocity = direction * (speed / direction.length)
+```
+
+Finally, back in `World.upate()` in the `// Update monsters` section, just before the line `monsters[i] = monster`, add the following:
+
+```swift
+monster.position += monster.velocity * timeStep
+```
+
+With these additions, each monster will move towards the player whenever it can see them. Run the game again to check everything is working as expected (prepare to be mobbed!)
+
+
+
+### An Animated Performance
+
+It's pretty eery seeing the monsters float towards you like ghosts, but they are supposed to be zombies and zombies *stagger*, they don't float. It would help to improve the realism if the monsters had some animation.
+
+Start by adding some walking frames for the monster.
+
+
+
+The walking animation has four frames, but two of them are the same as the standing image we already have. Feel free to use as many or as few frames as needed for your own animation (you can also just use [these ones](https://github.com/nicklockwood/RetroRampage/tree/Part6/Source/Rampage/Assets.xcassets/) if you want).
+
+**Note: The capitalization of the texture images has changed from `lowercase` to `camelCase` since Part 5 was released. If you've been using the tutorial textures in your own project, watch out for this when updating.**
+
+Add the images for the walking animation to XCAssets, then in `Textures.swift` extend the `Texture` enum with these two additional cases, matching the names of the image assets:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case monster
+ case monsterWalk1, monsterWalk2
+}
+```
+
+We'll need a new type to represent the animation itself. In the Engine module, create a new file called `Animation.swift` with the following contents:
+
+```swift
+public struct Animation {
+ public let frames: [Texture]
+ public let duration: Double
+
+ public init(frames: [Texture], duration: Double) {
+ self.frames = frames
+ self.duration = duration
+ }
+}
+```
+
+Then in `Monster.swift` add an `animation` property to `Monster`:
+
+```swift
+public struct Monster: Actor {
+ ...
+ public var state: MonsterState = .idle
+ public var animation: Animation = .monsterIdle
+
+ ...
+}
+```
+
+It may seem like the monster only has one animation, but it really has two - it's just that the idle/standing animation only has a single frame. Still in `Monster.swift`, add the following code to the bottom of the file:
+
+```swift
+public extension Animation {
+ static let monsterIdle = Animation(frames: [
+ .monster
+ ], duration: 0)
+ static let monsterWalk = Animation(frames: [
+ .monsterWalk1,
+ .monster,
+ .monsterWalk2,
+ .monster
+ ], duration: 0.5)
+}
+```
+
+We've added the animations as static constants on the `Animation` type because it means we can conveniently reference them using dot syntax, thanks to Swift's type inference. But since these animations are specific to the monster sprite, it makes sense to keep them together in with the other `Monster`-specific code rather than in the `Animation.swift` file.
+
+Now, in the `Monster.update()` method, modify the state machine again to swap the animations:
+
+```swift
+switch monster.state {
+case .idle:
+ if monster.canSeePlayer(in: self) {
+ state = .chasing
+ animation = .monsterWalk
+ }
+case .chasing:
+ guard monster.canSeePlayer(in: self) else {
+ state = .idle
+ animation = .monsterIdle
+ break
+ }
+ ...
+}
+```
+
+That's the data model side of animations taken care of, but what about the *rendering* side? Right now the renderer is just hard-coded to show the monster's standing image for each sprite. The renderer is going to need to know which animation frame to draw.
+
+Open `Billboard.swift` and add a `texture` property and initializer argument:
+
+```swift
+public struct Billboard {
+ public var start: Vector
+ public var direction: Vector
+ public var length: Double
+ public var texture: Texture
+
+ public init(start: Vector, direction: Vector, length: Double, texture: Texture) {
+ self.start = start
+ self.direction = direction
+ self.length = length
+ self.texture = texture
+ }
+}
+```
+
+Then, in `Renderer.draw()`, in the `// Draw sprites` section, replace the line:
+
+```swift
+let spriteTexture = textures[.monster]
+```
+
+with:
+
+```swift
+let spriteTexture = textures[sprite.texture]
+```
+
+It's now up to the `World` to supply the correct frame texture for each sprite billboard at a given point in time, but which frame should it select? We need to introduce a concept of the *current frame* for an animation.
+
+Back in `Animation.swift`, add a `time` property to the `Animation` struct:
+
+```swift
+public struct Animation {
+ public let frames: [Texture]
+ public let duration: Double
+ public var time: Double = 0
+
+ ...
+}
+```
+
+The `time` defaults to zero (the start of the animation), but it's a `var`, so we can advance the time of any given `Animation` instance in order to scrub through the frames. Still in `Animation.swift`, add the following extension method:
+
+```swift
+public extension Animation {
+ var texture: Texture {
+ guard duration > 0 else {
+ return frames[0]
+ }
+ let t = time.truncatingRemainder(dividingBy: duration) / duration
+ return frames[Int(Double(frames.count) * t)]
+ }
+}
+```
+
+This fetches the current frame texture for `time`. It calculates this by dividing `time` by the animation's `duration`, and then multiplying by the `frames` count. The `truncatingRemainder(dividingBy: duration)` means that if `time` is greater than `duration`, the animation will loop back around.
+
+In `World.swift`. update the computed `sprites` var, replacing the lines:
+
+```swift
+Billboard(
+ start: monster.position - spritePlane / 2,
+ direction: spritePlane,
+ length: 1
+)
+```
+
+with:
+
+```swift
+Billboard(
+ start: monster.position - spritePlane / 2,
+ direction: spritePlane,
+ length: 1,
+ texture: monster.animation.texture
+)
+```
+
+Each sprite billboard will now include the current animation frame for that monster as its texture. All that's left now is to actually advance the animation times as the game is playing. Still in `World.swift`, in the `// Update monsters` block inside the `update()` method, add the following line just before `monsters[i] = monster`:
+
+```swift
+monster.animation.time += timeStep
+```
+
+And that's it! Run the game now and you'll see the monsters striding towards you menacingly (it's actually a pretty creepy).
+
+### Space Invaders
+
+As terrifying as it is to be crowded by a bunch of zombies, it's a bit anticlimactic if all they do is try to stand uncomfortably close to you. Let's add a new state to the monster AI. First, we'll need a new animation:
+
+
+
+Add the images to XCAssets, then add the new cases to the `Texture` enum in `Textures.swift`:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case monsterScratch1, monsterScratch2, monsterScratch3, monsterScratch4
+ case monsterScratch5, monsterScratch6, monsterScratch7, monsterScratch8
+}
+```
+
+Next, at the bottom of the `Monster.swift` file, add a new static property for the `monsterScratch` animation:
+
+```swift
+public extension Animation {
+ ...
+
+ static let monsterScratch = Animation(frames: [
+ .monsterScratch1,
+ .monsterScratch2,
+ .monsterScratch3,
+ .monsterScratch4,
+ .monsterScratch5,
+ .monsterScratch6,
+ .monsterScratch7,
+ .monsterScratch8,
+ ], duration: 0.8)
+}
+```
+
+That's the animation taken care of - now we need to upgrade the monster's AI. At the top of `Monster.swift`, add a `scratching` case to the `MonsterState` enum:
+
+```swift
+public enum MonsterState {
+ case idle
+ case chasing
+ case scratching
+}
+```
+
+The monster can only scratch the player if they're in range, so we'll need some logic to determine that. This doesn't need to be anything fancy, we can just check if the distance between the player and monster is below a certain threshold.
+
+Still in `Monster.swift` add the following method just below the `canSeePlayer()` method we added earlier:
+
+```swift
+func canReachPlayer(in world: World) -> Bool {
+ let reach = 0.25
+ let playerDistance = (world.player.position - position).length
+ return playerDistance - radius - world.player.radius < reach
+}
+```
+
+This computes the distance from the monster to the player (we don't care about the direction) and then compares it with a `reach` constant that determines how far the monster can reach out when attacking.
+
+We subtract the player and monster radii from the `playerDistance` before comparing with `reach`. The player and monster cannot actually stand in exactly the same spot because of collision handling, so `reach` is measured relative to their minimum separation distance, which is the sum of their radii.
+
+In `Monster.update()`, inside `case .chasing:`, find the following block of code:
+
+```swift
+guard canSeePlayer(in: world) else {
+ state = .idle
+ animation = .monsterIdle
+ break
+}
+```
+
+Just below it, add the following:
+
+```switch
+if canReachPlayer(in: world) {
+ state = .scratching
+ animation = .monsterScratch
+}
+```
+
+Finally, add this extra case to the end of the `switch` block to complete the monster attack logic:
+
+```swift
+case .scratching:
+ guard canReachPlayer(in: world) else {
+ state = .chasing
+ animation = .monsterWalk
+ break
+ }
+```
+
+The monsters can now both chase and attack the player (albeit without actually doing any damage). Run the game again, but prepare for a scare!
+
+
+
+That's it for Part 6. In this part we:
+
+* Added collision handling for the monsters
+* Extracted common code between the `Player` and `Monster` classes
+* Created a state machine to act as the monster's brain
+* Gave the monsters the ability to chase and attack the player
+* Added walking and attack animations for the monsters
+
+In [Part 7](Part7.md) we'll raise the stakes a bit by giving the monsters the ability to hurt (and eventually kill) the player.
+
+
+
+[[1]](#reference1) Beauty is in the eye of the beholder, OK?
+
+[[2]](#reference2) The Swift experts among you will probably be turning up your noses at this unapologetically imperative code and wondering why I don't just use `Array.map()` like a civilized person. This will be explained, so try to contain your disgust for now and read on.
+
+[[3]](#reference3) The inner loop is why I didn't use `map()`. There may still be a way to solve this using functional programming, but I didn't want to write it and I *definitely* didn't want to have to explain it.
diff --git a/Tutorial/Part7.md b/Tutorial/Part7.md
new file mode 100644
index 0000000..e56917a
--- /dev/null
+++ b/Tutorial/Part7.md
@@ -0,0 +1,846 @@
+## Part 7: Death and Pixels
+
+In [Part 6](Part6.md) we added animations to the monsters inhabiting the maze, and gave them rudimentary intelligence so they could hunt and attack the player. The complete code for Part 6 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part6.zip).
+
+Although the monsters appear to attack, their blows don't actually have any effect on the player. Time to fix that.
+
+### Healthy Living
+
+In order for the player to be hurt (and eventually die), they need to have some sort of health meter. In `Player.swift`, add a `health` property to the `Player` and in the initializer set its starting value to `100`:
+
+```swift
+public struct Player: Actor {
+ public let speed: Double = 2
+ public let turningSpeed: Double = .pi
+ public let radius: Double = 0.25
+ public var position: Vector
+ public var velocity: Vector
+ public var direction: Vector
+ public var health: Double
+
+ public init(position: Vector) {
+ self.position = position
+ self.velocity = Vector(x: 0, y: 0)
+ self.direction = Vector(x: 1, y: 0)
+ self.health = 100
+ }
+}
+```
+
+In the same file, add the following convenience extension:
+
+```swift
+public extension Player {
+ var isDead: Bool {
+ return health <= 0
+ }
+}
+```
+
+### Hurt Me Plenty
+
+Now that the player has health, the monster needs a way to deplete it.
+
+Changes to the player are currently handled inline within the `World.update()` method. This is a *mutating* method, so it has the ability to make changes to the world and its contents.
+
+Monster behavior is delegated out to the `Monster.update()` method. This method is also marked as mutating - which means that it can make changes to the monster itself - but the `world` parameter it receives is immutable, which means it cannot make changes to the world.
+
+In order for the monster to be able to hurt the player, we need some way for its behavior to affect objects outside of itself. We could just inline the monster update logic inside `World.update()`, but that method is already doing more than it should so we don't really want to overload it any further.
+
+Instead, we can pass the world to `Monster.update()` as an `inout` parameter, thereby making it mutable. In `Monster.swift`, replace the line:
+
+```swift
+mutating func update(in world: World) {
+```
+
+with:
+
+```swift
+mutating func update(in world: inout World) {
+```
+
+Then, in `World.update()`, in the `// Update monsters` section, change the line:
+
+```swift
+monster.update(in: self)
+```
+
+to:
+
+```swift
+monster.update(in: &self)
+```
+
+This means that we are passing the world *by reference*, giving `Monster.update()` the ability to make changes to the world, along with anything in it (such as the player).
+
+We don't really want the monster to just manipulate the player's health directly though, because harming the player will potentially have side-effects in the game logic that the monster shouldn't need to know about.
+
+Let's lock the interface down a bit so that we don't accidentally break encapsulation. Find the following property declarations at the top of the `World` struct:
+
+```swift
+public var monsters: [Monster]
+public var player: Player!
+```
+
+And change them to:
+
+```swift
+public private(set) var monsters: [Monster]
+public private(set) var player: Player!
+```
+
+Then, still in `World.swift`, add the following method to the bottom of the extension:
+
+```swift
+mutating func hurtPlayer(_ damage: Double) {
+ player.health -= damage
+}
+```
+
+That gives the monster a way to hurt the player in a structured way without directly manipulating properties. Next, in `Monster.update()`, add the following code to the end of the `case .scratching:` block, just after the guard statement:
+
+```swift
+world.hurtPlayer(10)
+```
+
+### Game Over Man, Game Over!
+
+I mentioned earlier that reducing player health might have side-effects. One such side-effect is that if the player's health drops to zero, the game should end. This is a change that affects the entire world, which is why `hurtPlayer()` is a method of the world itself and not the `Player` type.
+
+Eventually, the player dying will have effects that extend even beyond the game world, such as displaying a "Game Over" screen, and presenting a menu for the player to quit or try again, etc. We haven't built any of the infrastructure for that yet, so for now we'll just reset the world to its initial conditions.
+
+The world is currently created by `ViewController` (in the platform layer) which might seem like it would cause a headache if we need to trigger re-initialization from *inside* the world itself. Fortunately, the initial conditions for the world are all encoded within the `TileMap`, which is never actually modified.
+
+Since the world already contains all the information needed to reset itself, we can go ahead and add a method to do that. Near the top of `World.swift`, find the `init()` method and copy the following lines:
+
+```swift
+self.monsters = []
+for y in 0 ..< map.height {
+ for x in 0 ..< map.width {
+ let position = Vector(x: Double(x) + 0.5, y: Double(y) + 0.5)
+ let thing = map.things[y * map.width + x]
+ switch thing {
+ case .nothing:
+ break
+ case .player:
+ self.player = Player(position: position)
+ case .monster:
+ monsters.append(Monster(position: position))
+ }
+ }
+}
+```
+
+Create a new mutating method in the extension block called `reset()` and paste the copied code into it:
+
+```swift
+mutating func reset() {
+ ...
+}
+```
+
+Then, still in `World.swift`, replace the `init()` implementation with the following:
+
+```swift
+public init(map: Tilemap) {
+ self.map = map
+ self.monsters = []
+ reset()
+}
+```
+
+That gives us the means to reinitialize the world in its starting state when the player dies. Find the `World.hurtPlayer()` function we created earlier and append the following code inside it:
+
+```swift
+if player.isDead {
+ reset()
+}
+```
+
+Now go ahead and run the game.
+
+*Hmm, that's odd...* as soon the the monster touches the player, everything starts flickering and the player can't move. What's going on?
+
+There are actually *two* problems here. The first is that the game is being reset every time the monster touches the player. The second is that the monster itself is unaffected by the reset, and continues to attack the player immediately after player re-spawns.
+
+### One Punch Man
+
+The monster only does 10 damage per swipe, so how come the player dies instantly? Let's look at the monster attack logic again:
+
+```swift
+case .scratching:
+ guard canReachPlayer(in: world) else {
+ state = .chasing
+ animation = .monsterWalk
+ break
+ }
+ world.hurtPlayer(10)
+}
+```
+
+Here is the problem - as long as the monster is in `scratching` mode and in range of the player, `hurtPlayer()` will be called on every update. Since `update()` is called ~120 times per second, it's no wonder that the player drops dead almost instantly.
+
+We will need to keep track of when the monster last struck the player, and only apply damage again if a minimum time has passed - we'll call this the *cooldown* period.
+
+The scratching animation is 0.8 seconds long, and the monster swipes at the player twice during each cycle of the attack animation, so a cooldown period of 0.4 seconds would make sense.
+
+In `Monster.swift`, add the following two properties to the `Monster` struct:
+
+```swift
+public let attackCooldown: Double = 0.4
+public private(set) var lastAttackTime: Double = 0
+```
+
+Then in `Monster.update()`, in the `case .scratching:` block, replace the line:
+
+```swift
+world.hurtPlayer(10)
+```
+
+with:
+
+```swift
+if animation.time - lastAttackTime >= attackCooldown {
+ lastAttackTime = animation.time
+ world.hurtPlayer(10)
+}
+```
+
+Then, just above in the `case .chasing:` block, find the code:
+
+```swift
+if canReachPlayer(in: world) {
+ state = .scratching
+ animation = .monsterScratch
+}
+```
+
+And add the following line inside the `if` block:
+
+```swift
+lastAttackTime = -attackCooldown
+```
+
+We set the `lastAttackTime` to `-attackCooldown` when the `scratching` state begins to ensure that the `animation.time - lastAttackTime >= attackCooldown` condition is initially true, otherwise the monster's first swipe wouldn't do any damage.
+
+If you run the game again, you should find that the player can take a few punches before the game resets. Now for the second problem - why isn't the monster being reset with the rest of the world?
+
+### Left Behind
+
+Take a look at the `// Update monsters` loop in the `World.update()` method:
+
+```swift
+for i in 0 ..< monsters.count {
+ var monster = monsters[i]
+ monster.update(in: &self)
+ monster.position += monster.velocity * timeStep
+ monster.animation.time += timeStep
+ monsters[i] = monster
+}
+```
+
+Inside the loop, we copy the monster at the current index into a local variable, then call its `update()` method, then modify its `position` and `animation` before assigning it back to the `monsters` array.
+
+If the player is killed during `Monster.update()` the world will be reset immediately, but the local `monster` var inside the `// Update monsters` loop will be unaffected by the reset, and will then be written back into the `monsters` array. That's why the monster is still in the same position after the reset occurs.
+
+Maybe it's not such a good idea for the `hurtPlayer()` method to reset the world immediately? Remove the following lines from the `World.hurtPlayer()` method:
+
+```swift
+if player.isDead {
+ reset()
+}
+```
+
+Then, at the very top of the `World.update()` method, add the following:
+
+```swift
+// Update player
+if player.isDead {
+ reset()
+ return
+}
+```
+
+In this way, the reset is deferred until the next update cycle after the player is killed, and performed before any other updates so it can't introduce inconsistencies in the world model.
+
+Run the game again and you should find that it now resets correctly when the player dies.
+
+### Cause and Effect
+
+The visual impact of being hacked to death by a ravenous zombie is currently rather underwhelming. Eventually we'll add an on-screen health meter, but in the meantime it would be good to at least have *some* visual indication of player damage.
+
+Modern games use a variety of effects to indicate the player being hurt - camera shake, blood spatter on the screen, blurred vision, etc. Wolfenstein 3D used a brief red flash of the screen, and so will we.
+
+Create a new file in the Engine module called `Effect.swift` with the following contents:
+
+```swift
+public enum EffectType {
+ case fadeIn
+}
+
+public struct Effect {
+ public let type: EffectType
+ public let color: Color
+ public let duration: Double
+ public var time: Double = 0
+
+ public init(type: EffectType, color: Color, duration: Double) {
+ self.type = type
+ self.color = color
+ self.duration = duration
+ }
+}
+```
+
+The `Effect` type is quite similar to the `Animation` type we added in [Part 6](Part6.md), but instead of an array of image frames it has a `type` and `color`. Just below the `Effect` struct, add the following extension:
+
+```swift
+public extension Effect {
+ var isCompleted: Bool {
+ return time >= duration
+ }
+
+ var progress: Double {
+ return min(1, time / duration)
+ }
+}
+```
+
+These convenience properties allow us to easily get the *progress* of the effect (a normalized representation of how far it is from completion) and whether it has already completed.
+
+Effects will span multiple frames, so we'll need to store them somewhere. It's debatable whether they really *exist* as physical objects within the world, but it seems like the logical place to put them for now. In `World.swift`, add an `effects` property to the `World` struct, and initialize it inside `World.init()`:
+
+```swift
+public struct World {
+ public let map: Tilemap
+ public private(set) var monsters: [Monster]
+ public private(set) var player: Player!
+ public private(set) var effects: [Effect]
+
+ public init(map: Tilemap) {
+ self.map = map
+ self.monsters = []
+ self.effects = []
+ reset()
+ }
+}
+```
+
+Like other objects in the world, effects will need to be updated over time. It seems like a good idea to update effects first, before any other updates because we don't want events like a world reset (which returns from the `update()` method early) to cause any ongoing effects to skip frames.
+
+Insert the following code at the very top of the `World.update()` method, above the `// Update player` section:
+
+```swift
+// Update effects
+effects = effects.map { effect in
+ var effect = effect
+ effect.time += timeStep
+ return effect
+}
+```
+
+This code is a little different from the other update loops. As with animations, we're incrementing the elapsed `time` property of each effect, but this time we're using `map()` to replace the original `effects` array, instead of a `for` loop.
+
+Functional programming is cool and all, but *why now*?
+
+Well, there's something else we need to do in this loop in addition to updating the effects - once an effect is complete we want to remove it from the array. That's awkward and error-prone to do with a traditional `for` loop because removing an item messes up the index (unless you resort to tricks like iterating the loop backwards).
+
+But with Swift's functional programming extensions, we can do this very cleanly using `compactMap()`. The `compactMap()` function works like a combination of `map()` (to replace the values in a collection) and `filter()` (to conditionally remove them). In the `// Update effects` block we just added, replace the line:
+
+```swift
+effects = effects.map { effect in
+```
+
+with:
+
+```swift
+effects = effects.compactMap { effect in
+ if effect.isCompleted {
+ return nil
+ }
+```
+
+So now we return `nil` for any effect that has completed, thereby removing it from the `effects` array. This check is performed *before* incrementing the time, so that the final state of the effect is displayed at least once before it is removed[[1]](#footnote1).
+
+The red flash effect should be triggered whenever the player is hurt. Still in `World.swift`, add the following line to the `hurtPlayer()` method:
+
+```swift
+effects.append(Effect(type: .fadeIn, color: .red, duration: 0.2))
+```
+
+Finally, we need to actually draw the effect.
+
+Wolfenstein implemented the red screen flash using a trick called [color cycling](https://en.wikipedia.org/wiki/Color_cycling) (also known as *palette animation*), made possible by the game's use of an indexed color palette. It would have been expensive to tint every pixel on-screen, and also impractical to craft a palette that had multiple red-shifted shades of each color, but color cycling neatly solves both of these problems.
+
+The palette introduces a layer of indirection between the 8-bit color indexes drawn into the output buffer and the colors that actually appeared on screen. By gradually red-shifting all the colors in the palette over time, the image drawn on the screen automatically tinted to red without the game needing to actually needing to redraw anything. And shifting the palette only required changing the 256 values in the palette itself, not the 64,000 pixels on screen[[2]](#footnote2).
+
+Pretty clever, right? Unfortunately, since we're using 32-bit "true" color instead of an indexed palette, we can't use the same trick[[3]](#footnote3). *Fortunately*, computers are immensely fast now so we can just use a brute-force approach and blend every pixel of the output bitmap.
+
+In `Renderer.swift` add the following code to the bottom of the `draw()` function (be careful to put it *outside* the `for` loop, as the nested closing braces can be confusing):
+
+```swift
+// Effects
+for effect in world.effects {
+ let color = effect.color
+ for y in 0 ..< bitmap.height {
+ for x in 0 ..< bitmap.width {
+ bitmap.blendPixel(at: x, y, with: color)
+ }
+ }
+}
+```
+
+Run the game and you should see the screen blink red every time the monster swipes at the player. The effect isn't very pretty yet though - we need to add the fading effect. In the `// Effects` code we just added, replace the line:
+
+```swift
+let color = effect.color
+```
+
+with:
+
+```swift
+var color = effect.color
+color.a = UInt8((1 - effect.progress) * 255)
+```
+
+If you recall from earlier, the `progress` property is the normalized effect time (in the range 0 - 1). We can use that to calculate the `a` (alpha) value for the color by subtracting it from 1 and multiplying by 255, so that the value falls from 255 to 0 over the duration of the effect.
+
+Run the game and... *crash*. Oh.
+
+
+
+The game crashed inside the `blendPixel()` function. A bit of investigation reveals that the cause of the crash is that we overflowed the range of `UInt8` - we tried to put a number bigger than 255 into it. How did that happen?
+
+If you recall, in [Part 5](Part5.md), when we originally added the `blendPixel()` method, we designed it to work with *premultiplied alpha* values, because it's better for performance. Premultiplied alpha is common when dealing with images, but it's a bit of a weird requirement for a public API.
+
+We can fix the crash by manually premultiplying the color components by the alpha value. In `Renderer.draw()`, inside the `// Effects` update loop, replace the lines:
+
+```swift
+var color = effect.color
+color.a = UInt8((1 - effect.progress) * 255)
+```
+
+with:
+
+```swift
+let opacity = 1 - effect.progress
+let color = Color(
+ r: UInt8(opacity * Double(effect.color.r)),
+ g: UInt8(opacity * Double(effect.color.g)),
+ b: UInt8(opacity * Double(effect.color.b)),
+ a: UInt8(opacity * 255)
+)
+```
+
+Now run the game again, and you should see the fading effect working correctly.
+
+
+
+### It's a Trap
+
+We've fixed the bug, but it seems a bit dangerous to have such a sharp edge exposed in the `Bitmap` API. The behavior of `blendPixel()` is pretty unintuitive. There are three possible ways I can think of to improve it:
+
+* Change the name to more accurately reflect its purpose
+* Add bounds checking so it won't crash for out-of-range values
+* Make it private
+
+This is a performance-critical method (it's called at least once for every pixel on screen), so adding bounds checking is probably not desirable. It's also not clear that would be the right solution anyway since a bounds error here is indicative of a programming mistake - it should't happen with valid input.
+
+For now, we'll just make the `blendPixel()` method private. In `Bitmap.swift`, replace the line:
+
+```swift
+mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
+```
+
+with:
+
+```swift
+private mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
+```
+
+If the method is private, we can't call it from outside of `Bitmap`, but applying a translucent tint seems like a general enough operation that we can just make a method for it on `Bitmap` itself.
+
+Still in `Bitmap.swift`, add the following method to the bottom of the extension:
+
+```swift
+mutating func tint(with color: Color, opacity: Double) {
+ let color = Color(
+ r: UInt8(opacity * Double(color.r)),
+ g: UInt8(opacity * Double(color.g)),
+ b: UInt8(opacity * Double(color.b)),
+ a: UInt8(opacity * 255)
+ )
+ for y in 0 ..< height {
+ for x in 0 ..< width {
+ blendPixel(at: x, y, with: color)
+ }
+ }
+}
+```
+
+This is pretty much the same logic we used in the effects loop. One extra enhancement we can make while we're here though is to take the original alpha value from the `color` into account, so that the maximum opacity of the overlay can be adjusted by changing the alpha of the effect color.
+
+Insert the following line at the beginning of the `Bitmap.tint()` function we just added:
+
+```swift
+let opacity = min(1, max(0, Double(color.a) / 255 * opacity))
+```
+
+Now, back in `Renderer.swift`, replace the whole `// Effects` block at the end of the `draw()` function with the following code:
+
+```swift
+// Effects
+for effect in world.effects {
+ bitmap.tint(with: effect.color, opacity: 1 - effect.progress)
+}
+```
+
+As you can see, this is a much nicer (and safer) API to work with.
+
+We mentioned above that the new `tint()` method supports using a partially transparent base color. In `World.hurtPlayer()`, replace the line:
+
+```swift
+effects.append(Effect(type: .fadeIn, color: .red, duration: 0.2))
+```
+
+with:
+
+```swift
+let color = Color(r: 255, g: 0, b: 0, a: 191)
+effects.append(Effect(type: .fadeIn, color: color, duration: 0.2))
+```
+
+An alpha value of `191` is equivalent to 75% opacity (255 * 0.75), so now the flash of red when the monster strikes will start at 75% opacity instead of 100%. This isn't just a cosmetic improvement - it also means the red flash won't completely obscure your vision if you are attacked repeatedly.
+
+### Fade to Black Red
+
+The red flash effect works well for player damage, but the re-spawn when the player eventually dies is much too sudden. We need a longer, more pronounced effect for the player's death to give the them time to react to what has happened, and prepare for the next attempt.
+
+Instead of a short fade *from* red, when the player dies let's show a longer fade *to* red. Since this is basically just the same effect in reverse, we can re-use a lot of the logic we've already written.
+
+In `Effect.swift`, add a `fadeOut` case to the `EffectType` enum:
+
+```swift
+public enum EffectType {
+ case fadeIn
+ case fadeOut
+}
+```
+
+Then in `Renderer.draw()`, modify the `// Effects` block again as follows:
+
+```swift
+// Effects
+for effect in world.effects {
+ switch effect.type {
+ case .fadeIn:
+ bitmap.tint(with: effect.color, opacity: 1 - effect.progress)
+ case .fadeOut:
+ bitmap.tint(with: effect.color, opacity: effect.progress)
+ }
+}
+```
+
+As you can see, the code for `fadeOut` is nearly identical to the original `fadeIn` effect, except that we are no longer subtracting the `progress` value from 1, so the opacity goes up from 0 to 1 instead of down from 1 to 0.
+
+In `World.swift`, append the following code to the `hurtPlayer()` method:
+
+```swift
+if player.isDead {
+ effects.append(Effect(type: .fadeOut, color: .red, duration: 2))
+}
+```
+
+This triggers a two-second fade to red. If you run the game again, you'll find this doesn't quite do what we wanted however. The fade works, but the game still resets instantly when the player dies, so the fade begins much too late.
+
+We need to defer the game reset until after the effect has completed. Let's take a look at the code responsible for resetting the game, which is located near the top of the `World.update()` method:
+
+```swift
+// Update player
+if player.isDead {
+ reset()
+ return
+}
+```
+
+In this block, replace the line:
+
+```swift
+if player.isDead {
+```
+
+with:
+
+```swift
+if player.isDead, effects.isEmpty {
+```
+
+Then try running the game again. When the player dies, the game now keeps running until the fade is complete. But now the fade never goes away - what's going on?
+
+### Rest in Peace
+
+Because the game now keeps running after the player is dead, the monster is going to keep hitting the player, causing more and more fade effects to trigger. Because of this, the `effects` array is never empty, so the game never actually resets.
+
+We could modify the monster AI so that it stops attacking the player once they're dead, but seeing the zombies continue to hack away at your corpse as you pass away is kind of cool[[4]](#footnote4), so let's solve it a different way. Add the following code to the top of the `World.hurtPlayer()` method:
+
+```swift
+if player.isDead {
+ return
+}
+```
+
+Now, once the player is dead, the monsters can't hurt them anymore (even though they'll keep trying). That means no further effects will be spawned, and the reset should happen on schedule.
+
+The last slightly weird thing about the way the death sequence works currently is that the player can keep moving after death. We need to keep the update loop running if we want the effects to keep animating and the monsters to keep attacking, but we don't necessarily need to keep accepting player input.
+
+To prevent the player wandering around after death (there are enough zombies in this game already), in `World.update()`, rearrange the `// Update player` code section as follows:
+
+```swift
+// Update player
+if player.isDead == false {
+ player.direction = player.direction.rotated(by: input.rotation)
+ player.velocity = player.direction * input.speed * player.speed
+ player.position += player.velocity * timeStep
+} else if effects.isEmpty {
+ reset()
+ return
+}
+```
+
+Now the player can finally die with bit of dignity. With, um, zombies hacking at their lifeless corpse.
+
+### Easy Does It
+
+The game-over fade looks pretty good, but it ends rather abruptly.
+
+We can mitigate this by adding a short fade-in after the reset. Still in `World.update()`, in the `// Update player` section, add the following just after the `reset()` line (or immediately before it - it doesn't really matter since effects aren't cleared by the reset):
+
+```swift
+effects.append(Effect(type: .fadeIn, color: .red, duration: 0.5))
+```
+
+You can play with the duration value a bit [[5]](#footnote5), but it still doesn't completely fix the problem. The issue is that the human eye is quite sensitive to discontinuities in animation timing. This is much more apparent with moving objects, but it also applies to fading effects.
+
+If we plot a graph of the change in opacity of the fade effect over time, we get the following:
+
+
+
+There is a sharp discontinuity in the middle where we transition from the `fadeOut` to `fadeIn` effects, which is why it doesn't quite feel right. We can smooth it out a bit by using a technique called *easing*.
+
+Easing (also known as *tweening*) is a (typically non-linear) mapping of time to an animated property such as position or scale (or in our case, opacity). If you were a Flash programmer in the early 2000s, the term *easing* may be almost synonymous with the name [Robert Penner](http://robertpenner.com/easing/), whose open-source easing functions have been ported to almost every language and platform you can think of.
+
+There are an infinite number of possible easing functions, but the most common variants are: *linear*, *ease-in*, *ease-out* and *ease-in-ease-out*.
+
+
+
+Real-life objects do not start and stop moving instantly, they have to accelerate up to speed and then decelerate back to a stop. The purpose of easing is to create realistic motion without all the complexities of a true physics engine, with simulated mass, friction, torque, etc.
+
+The best all-round easing function for general use is *ease-in-ease-out*, because it starts slow, reaches maximum speed at the half-way point, and then slows down again before stopping, just like a real object. That doesn't mean it's necessarily the best choice for every situation though.
+
+For the fade to red as the player dies, we want to start quickly at first and then go slower so that the effect seems more drawn out towards the end. For that, we can use the *ease-out* curve. And for the subsequent fade in we want the reverse effect (*ease-in*) so that the overall curve is smooth.
+
+Easing functions take a normalized input time and produce an output time that has been delayed or sped up according to the type of easing applied. We could implement these as free functions, but grouping them under a common namespace helps with autocompletion and prevents naming collisions.
+
+Create a new file in the Engine module called `Easing.swift` with the following contents:
+
+```swift
+public enum Easing {}
+
+public extension Easing {
+ static func linear(_ t: Double) -> Double {
+ return t
+ }
+
+ static func easeIn(_ t: Double) -> Double {
+ return t * t
+ }
+}
+```
+
+The first line here may seem a little baffling. An enum with no cases?!
+
+This is actually a neat little pattern in Swift for when you want to create a namespace. There is no `namespace` keyword in Swift, so to group functions together you have to make them members of a type. We could use a class or struct, but an empty enum has the nice property that you can't accidentally create an instance of it. It's what's known as an *uninhabited type* - a type with no possible values.
+
+After the empty enum we have an extension with some static methods. The first is the `linear()` easing function - this just returns the same time you pass in.
+
+Next is the `easeIn()` function. There are actually many ways to implement `easeIn()` - for example, UIKit and Core Animation use Bezier curves to implement easing. But you can also use sine/cosine, or various orders of [polynomial](https://en.wikipedia.org/wiki/Polynomial).
+
+In this case we have used a *quadratic* `easeIn()` implementation, meaning that it's proportional to the input time *squared*. If we had used `t * t * t`, it would have been a *cubic* `easeIn()` instead. There's no right or wrong type to use, so feel free to experiment and see what feels right.
+
+Let's add the equivalent quadratic implementations for ease-out and ease-in-ease-out:
+
+```swift
+public extension Easing {
+ ...
+
+ static func easeOut(_ t: Double) -> Double {
+ return 1 - easeIn(1 - t)
+ }
+
+ static func easeInEaseOut(_ t: Double) -> Double {
+ if t < 0.5 {
+ return 2 * easeIn(t)
+ } else {
+ return 4 * t - 2 * easeIn(t) - 1
+ }
+ }
+}
+```
+
+The `easeOut()` function is just the opposite of ease-in, so we can implement it by calling `easeIn()` with inverted input. The `easeInEaseOut()` function is a little more complex but essentially it behaves like ease-in up to the half-way point, then it behaves like ease-out for the rest (shifted, so it aligns seamlessly with the first half).
+
+In `Effect.swift`, find the code for the computed `progress` property and update it as follows:
+
+```swift
+var progress: Double {
+ let t = min(1, time / duration)
+ switch type {
+ case .fadeIn:
+ return Easing.easeIn(t)
+ case .fadeOut:
+ return Easing.easeOut(t)
+ }
+}
+```
+
+We're still using `time / duration` to compute the normalized time, but now we pass it through either `easeIn()` or `easeOut()` depending on the effect type. The game-over fade effect timing is now a smooth curve.
+
+
+
+Try running the game again and you should notice a subtle improvement.
+
+### Not with a Bang, but with a Fizzle
+
+Wolfenstein 3D didn't actually use a palette fade for the player death. Instead, John Carmack created a rather special custom effect he called *fizzlefade*. Fizzlefade is a sort of stippling transition where the pixels flip to red at random.
+
+The naive way to implement this would be to repeatedly set pixels to red at random until the screen is filled, but this doesn't work well in practice because as the screen fills up, the chances of randomly stumbling on an unfilled pixel tends towards zero. As a result, the effect runs slower and slower over time, and the total duration is non-deterministic.
+
+Wolfenstein used an ingenious solution to this problem which Fabien Sanglard [wrote about here](http://fabiensanglard.net/fizzlefade/). I'm not *sure* I fully understand how it works (let alone to a level where I can explain it), but fortunately we aren't as resource-constrained as Wolfenstein was, and can use a much simpler approach.
+
+To restate the problem: We need to visit every pixel on the screen only once, but in a random order. If we put the indexes of all the pixels into an array and then shuffle it before we begin, we can step through the shuffled array in order to ensure we only touch each pixel once.
+
+In `Effect.swift`, add a new case to the `EffectType` enum called `fizzleOut`:
+
+```swift
+public enum EffectType {
+ case fadeIn
+ case fadeOut
+ case fizzleOut
+}
+```
+
+Then add the following extra case to the switch statement in the computed `progress` property below:
+
+```swift
+case .fizzleOut:
+ return Easing.easeInEaseOut(t)
+```
+
+Although it's similar to fade-out, I found that `easeOut()` didn't work as well for the fizzle effect, so I've used `easeInEaseOut()` instead (it also looks pretty good with just `linear()` easing, which is what Wolfenstein used).
+
+Next, in `World.hurtPlayer()` replace the line:
+
+```swift
+effects.append(Effect(type: .fadeOut, color: .red, duration: 2))
+```
+
+with:
+
+```swift
+effects.append(Effect(type: .fizzleOut, color: .red, duration: 2))
+```
+
+To implement the fizzle effect itself, we need to pre-generate a buffer of pixel indexes. It's not immediately obvious where we should store that. We could put it in the renderer itself if it was persistent, but we are currently creating a new `Renderer` instance each frame so we can't store anything in it long-term. Another reasonable option would be to store it in the `Effect` itself, but since it's only needed for fizzle-type effects that's not ideal either.
+
+For now we'll store the fizzle buffer in a global constant. It's not a very elegant solution because it introduces potential issues with thread safety, but since the renderer is currently single-threaded anyway it will do as a stop-gap measure.
+
+At the top of the `Renderer.swift` file, add the following line:
+
+```swift
+private let fizzle = (0 ..< 10000).shuffled()
+```
+
+This populates the fizzle buffer with the values 0 to 9999, shuffled in a random order. A typical iPhone screen has a lot more than 10,000 pixels but we don't need a buffer entry for every single pixel, just enough so that any repetition isn't obvious.
+
+At the bottom of the `Renderer.draw()` method, add the missing `.fizzleOut` case to the `// Effects` block:
+
+```swift
+case .fizzleOut:
+ let threshold = Int(effect.progress * Double(fizzle.count))
+
+}
+```
+
+The threshold value is the number of pixels in the `fizzle` buffer that should be filled in a given frame. For example, if `effect.progress` is 0.5, we want to fill 50% of the pixels on screen, so any pixel that maps to the lower half of the buffer should be filled, and any in the upper half won't be.
+
+Next, we need to loop through every pixel in the image and determine if we should fill the pixel or not. Add the following code just after the `let threshold` line:
+
+```swift
+for y in 0 ..< bitmap.height {
+ for x in 0 ..< bitmap.width {
+ let index = y * bitmap.width + x
+
+ }
+}
+```
+
+The `let index...` line converts the X and Y pixel coordinates into a linear buffer index. Just below that line, add the following:
+
+```swift
+let fizzledIndex = fizzle[index % fizzle.count]
+```
+
+This looks up the fizzled index from the buffer. Since there are more pixels in the bitmap than entries in the `fizzle` buffer, we use `%` (the modulo operator) to wrap the index value within the buffer's range.
+
+Finally, we need to compare the fizzled index value to the threshold. If it's below the threshold we'll fill the pixel, otherwise we do nothing. Add the following code below the line we just added:
+
+```swift
+if fizzledIndex <= threshold {
+ bitmap[x, y] = effect.color
+}
+```
+
+Run the game and get yourself killed. You should see the original fade to red has now been replaced by the fizzle effect.
+
+
+
+This effect is pretty faithful to the original Wolfenstein 3D death sequence, but it doesn't really suit the lo-fi look of Retro Rampage because the resolution of a modern iPhone (even the virtual 1x resolution that we are using) is a bit too dense.
+
+We can improve the effect by artificially lowering the resolution for a more pixelated fizzle. In the code we just wrote, replace the line:
+
+```swift
+let index = y * bitmap.width + x
+```
+
+with:
+
+```swift
+let granularity = 4
+let index = y / granularity * bitmap.width + x / granularity
+```
+
+The `granularity` constant controls the sampling size. By dividing the real X and Y coordinates by the desired granularity, we increase the apparent size of each sampled pixel on screen.
+
+
+
+And with that, we'll bring this (rather morbid) chapter to a close. In this part we:
+
+* Added player health and damage
+* Handled player death and re-spawn
+* Added fullscreen fade effects for when the player is hurt or killed
+* Implemented animation easing for smoother fades
+* Recreated the old-school fizzlefade effect from Wolfenstein
+
+In Part 8 (TBD) we'll see about giving the player the means to fight back!
+
+
+
+[[1]](#reference1) This isn't actually guaranteed to work since there are multiple world updates per frame, but it doesn't seem to be a problem in practice.
+
+[[2]](#reference2) The original Wolfenstein 3D screen resolution was 320x200 (320 * 200 = 64000).
+
+[[3]](#reference3) There are actually similar tricks that you can do on modern machines by altering the [display gamma](https://en.wikipedia.org/wiki/Gamma_correction), but that's highly platform-specific and outside the scope of what we're trying to do here.
+
+[[4]](#reference4) It's, uh... completely normal to think like this, right?
+
+[[5]](#reference5) Don't make it too long, or the monster will be on top of you by the time your vision has cleared!