public void Rasterize()
            {
                // locate the tile table in memory
                System.Diagnostics.Debug.Assert(Memory[TERRAIN_BASE_ADDRESS] == 0xE00);
                TileTableBaseAddress = Memory[TERRAIN_BASE_ADDRESS]; // always 0x0E00

                Parent.StartRender();

                Parent.LoadLightVector();
                Parent.LoadViewPosition();
                Parent.LoadViewMatrix(0x15);
                Rotation = Parent.ViewRotation;
                Parent.SetWorldMatrix(ref Parent.Terrain.Rotation);

                // determine rendering method (dot/vector/polygon)
                switch (Memory[TERRAIN_RENDERING_MODE])
                {
                case 0x0000: renderMode = Mathbox.RenderMode.Polygon; break;

                case 0x0100: renderMode = Mathbox.RenderMode.Vector; break;

                default: renderMode = Mathbox.RenderMode.Dot; break;
                }

                // get address of terrain object buffer
                ObjectList = Memory[TERRAIN_OBJECT_LIST_ADDRESS];

                Int16 z_max    = (Int16)Memory[TERRAIN_Z_MAX];
                Int16 z_min    = (Int16)Memory[TERRAIN_Z_MIN];
                Int16 x        = (Int16)Memory[TERRAIN_X];
                Int16 z_frac   = (Int16)Memory[TERRAIN_Z_FRAC];
                Int16 x_offset = (Int16)Memory[TERRAIN_X_OFFSET];
                Int16 z_offset = (Int16)Memory[TERRAIN_Z_OFFSET];

                // locate left front tile corner (x1,y1,z1)
                //         +-------+
                //        /       /|
                //       /       / |           x2 = x1 + Mathbox.Tile.SIZE_X
                // y1-- +-------+  |           y2 = y1 + Mathbox.Tile.SIZE_Y
                //      |       |  + --z2      z2 = z1 + Mathbox.Tile.SIZE_Z
                //      |       | /
                //      |       |/
                // y2-- +-------+ --z1
                //      |       |
                //      x1      x2
                Vector3 corner = new Vector3(
                    Tile.WIDTH_X - x_offset * 128 - x,
                    -Parent.ViewPosition.Y,
                    z_max * 128 - z_frac);

                // render each row in the terrain
                int row = z_offset / 16 + z_max; // first absolute row to be rendered

                Row.Count = z_max - z_min + 1;   // number of rows to display
                while (Row.Count-- > 0)
                {
                    DrawTerrainRow(row-- & 31, corner);
                    corner.Z -= Tile.DEPTH_Z; // move to next row along the Z axis
                }

                Parent.EndRender();
            }
            /// <summary>
            /// Parses the list of objects to render
            /// </summary>
            /// <param name="address">base address of object list</param>
            public void ParseObjectList(UInt16 address)
            {
                // address+0  Object position.X
                // address+1  Object position.Y
                // address+2  Object position.Z
                // address+3  Object Instruction
                // address+4  Address of view matrix
                //    address+5  Address of rotation matrix (only if specified by instruction)
                //    address+6  Address of surface list (only if specified by instruction)
                // address +5 or + 7 first child object
#if false
                Debug.WriteLine($"{Memory[address+0].HexString()} {Memory[address + 1].HexString()} {Memory[address + 2].HexString()} {Memory[address + 3].HexString()} {Memory[address + 4].HexString()} {Memory[address + 5].HexString()} {Memory[address + 6].HexString()}");
#endif

                for (; ;)
                {
                    // check for end of list
                    if (address == 0 || address >= 0x8000)
                    {
                        return;
                    }

                    // Control word encoding
                    // 0x8000 = stop flag
                    // 0x4000 = don't load camera matrix
                    // 0x1000 = ?
                    // 0x0800 = don't load base address or rotation matrix
                    // 0x0400 = x/y/z value is relative offset, not absolute position
                    Mathbox.ObjectInstruction opcode = Memory[address + 3];

                    // load camera matrix
                    if (!opcode.SkipObjectRotation)
                    {
                        Parent.LoadViewMatrix(Memory[address + 4]);
                    }

                    // Get new primitive list address and rotation matrix if they exist
                    int childAddr = address + 5;
                    if (!opcode.UsePreviousObjectPointsAndFaces)
                    {
                        PrimitiveListAddress = Memory[address + 6];
                        if (PrimitiveListAddress >= 0x8000)
                        {
                            return;
                        }
                        Parent.LoadRotationMatrix(Memory[address + 5]);
                        childAddr += 2;
                    }

                    // Don't render invalid objects
                    if (PrimitiveListAddress >= 0x8000)
                    {
                        return;
                    }

                    // Determine position of object
                    Vector3 pt = Parent.GetVectorAt(address);
                    if (opcode.ObjectPositionIsRelative)
                    {
                        // relative position
                        Parent.WorldPosition += Vector3.Transform(pt, Parent.ViewRotation);
                    }
                    else
                    {
                        // absolute position
                        pt -= Parent.ViewPosition;
                        Parent.WorldPosition = Vector3.Transform(pt, Parent.ViewRotation);
                    }

                    Parent.SetWorldMatrix(ref Parent.WorldPosition, ref Parent.WorldRotation);

#if WIDESCREEN_STARS
                    if (PrimitiveListAddress >= 0x4AE8 && PrimitiveListAddress <= 0x4B44)
                    {
                        for (int n = 0; n < Stars.Length; n++)
                        {
                            Vertices[n] = Vector3.Transform(Stars[n], Parent.D3DTS_WORLD);
                        }

                        DisplayListManager.AddPrimitive(Mathbox.RenderMode.Dot, Vertices, Stars.Length, Parent.GetColor(7));
                        return;
                    }
#endif

                    // parese the surfaces in this object
                    ParsePrimitiveList(PrimitiveListAddress);

                    // parse all child objects
                    for (; ;)
                    {
                        UInt16 child = Memory[childAddr++];
                        if (child == 0 || child >= 0x8000)
                        {
                            return;
                        }
                        if (child == 0x0002)
                        {
                            address += 8;
                            break;
                        }

                        ParseObjectList(child);
                    }
                }
            }