#include "../copyright"

#include "dsp4.h"
#include "memmap.h"

#define DSP4_READ_WORD(x) \
   READ_WORD(DSP4.parameters+x)

#define DSP4_WRITE_WORD(x,d) \
   WRITE_WORD(DSP4.output+x,d);

/* used to wait for dsp i/o */
#define DSP4_WAIT(x) \
    DSP4_Logic = x; \
    return

int32_t DSP4_Multiply(int16_t Multiplicand, int16_t Multiplier)
{
   return Multiplicand * Multiplier;
}

int16_t DSP4_UnknownOP11(int16_t A, int16_t B, int16_t C, int16_t D)
{
   return ((A * 0x0155 >>  2) & 0xf000) | ((B * 0x0155 >>  6) & 0x0f00) | ((C * 0x0155 >> 10) & 0x00f0) | ((D * 0x0155 >> 14) & 0x000f);
}

void DSP4_Op06(bool size, bool msb)
{
   /* save post-oam table data for future retrieval */
   op06_OAM[op06_index] |= (msb << (op06_offset + 0));
   op06_OAM[op06_index] |= (size << (op06_offset + 1));
   op06_offset += 2;

   if (op06_offset == 8)
   {
      /* move to next byte in buffer */
      op06_offset = 0;
      op06_index++;
   }
}

void DSP4_Op01(void)
{
   int16_t plane;
   int16_t index, lcv;
   int16_t py_dy, px_dx;
   int16_t y_out, x_out;
   uint16_t command;
   DSP4.waiting4command = false;

   switch (DSP4_Logic) /* op flow control */
   {
   case 1:
      goto resume1;
      break;
   case 2:
      goto resume2;
      break;
   }

   /*
    * process initial inputs
    */

   /* sort inputs */
   project_focaly = DSP4_READ_WORD(0x02);
   raster = DSP4_READ_WORD(0x04);
   viewport_top = DSP4_READ_WORD(0x06);
   project_y = DSP4_READ_WORD(0x08);
   viewport_bottom = DSP4_READ_WORD(0x0a);
   project_x1low = DSP4_READ_WORD(0x0c);
   project_focalx = DSP4_READ_WORD(0x0e);
   project_centerx = DSP4_READ_WORD(0x10);
   project_ptr = DSP4_READ_WORD(0x12);
   project_pitchylow = DSP4_READ_WORD(0x16);
   project_pitchy = DSP4_READ_WORD(0x18);
   project_pitchxlow = DSP4_READ_WORD(0x1a);
   project_pitchx = DSP4_READ_WORD(0x1c);
   far_plane = DSP4_READ_WORD(0x1e);
   project_y1low = DSP4_READ_WORD(0x22);

   /* pre-compute */
   view_plane = PLANE_START;

   /* find starting projection points */
   project_x1 = project_focalx;
   project_y -= viewport_bottom;
   project_x = project_centerx + project_x1;

   /* multi-op storage */
   multi_index1 = 0;
   multi_index2 = 0;

   /*
    * command check
    */

   do
   {
      /* scan next command */
      DSP4.in_count = 2;
      DSP4_WAIT(1);

resume1:
      /* inspect input */
      command = DSP4_READ_WORD(0);

      /* check for termination */
      if(command == 0x8000)
         break;

      /* already have 2 bytes in queue */
      DSP4.in_index = 2;
      DSP4.in_count = 8;
      DSP4_WAIT(2);

      /* process one iteration of projection */

      /* inspect inputs */

resume2:
      plane = DSP4_READ_WORD(0);
      px_dx = 0;

      /* ignore invalid data */
      if((uint16_t) plane == 0x8001)
         continue;

      /* one-time init */
      if (far_plane)
      {
         /* setup final parameters */
         project_focalx += plane;
         project_x1 = project_focalx;
         project_y1 = project_focaly;
         plane = far_plane;
         far_plane = 0;
      }

      /* use proportional triangles to project new coords */
      project_x2 = project_focalx * plane / view_plane;
      project_y2 = project_focaly * plane / view_plane;

      /* quadratic regression (rough) */
      if (project_focaly >= -0x0f)
         py_dy = (int16_t)(project_focaly * project_focaly * -0.20533553 - 1.08330005 * project_focaly - 69.61094639);
      else
         py_dy = (int16_t)(project_focaly * project_focaly * -0.000657035759 - 1.07629051 * project_focaly - 65.69315963);

      /* approximate # of raster lines */
      segments = ABS(project_y2 - project_y1);

      /* prevent overdraw */
      if(project_y2 >= raster)
         segments = 0;
      else
         raster = project_y2;

      /* don't draw outside the window */
      if(project_y2 < viewport_top)
         segments = 0;

      /* project new positions */
      if (segments > 0)
         px_dx = ((project_x2 - project_x1) << 8) / segments; /* interpolate between projected points */

      /* prepare output */
      DSP4.out_count = 8 + 2 + 6 * segments;

      /* pre-block data */
      DSP4_WRITE_WORD(0, project_focalx);
      DSP4_WRITE_WORD(2, project_x2);
      DSP4_WRITE_WORD(4, project_focaly);
      DSP4_WRITE_WORD(6, project_y2);
      DSP4_WRITE_WORD(8, segments);

      index = 10;

      for (lcv = 0; lcv < segments; lcv++) /* iterate through each point */
      {
         /* step through the projected line */
         y_out = project_y + ((py_dy * lcv) >> 8);
         x_out = project_x + ((px_dx * lcv) >> 8);

         /* data */
         DSP4_WRITE_WORD(index + 0, project_ptr);
         DSP4_WRITE_WORD(index + 2, y_out);
         DSP4_WRITE_WORD(index + 4, x_out);
         index += 6;

         /* post-update */
         project_ptr -= 4;
      }

      /* post-update */
      project_y += ((py_dy * lcv) >> 8);
      project_x += ((px_dx * lcv) >> 8);

      if (segments > 0) /* new positions */
      {
         project_x1 = project_x2;
         project_y1 = project_y2;

         /* multi-op storage */
         multi_focaly[multi_index2++] = project_focaly;
         multi_farplane[1] = plane;
         multi_raster[1] = project_y1 - 1;
      }

      /* update projection points */
      project_pitchy += (int8_t)DSP4.parameters[3];
      project_pitchx += (int8_t)DSP4.parameters[5];

      project_focaly += project_pitchy;
      project_focalx += project_pitchx;
   } while (1);

   /* terminate op */
   DSP4.waiting4command = true;
   DSP4.out_count = 0;
}

void DSP4_Op07(void)
{
   uint16_t command;
   int16_t plane;
   int16_t index, lcv;
   int16_t y_out, x_out;
   int16_t py_dy, px_dx;

   DSP4.waiting4command = false;

   /* op flow control */
   switch (DSP4_Logic)
   {
   case 1:
      goto resume1;
      break;
   case 2:
      goto resume2;
      break;
   }

   /* sort inputs */

   project_focaly = DSP4_READ_WORD(0x02);
   raster = DSP4_READ_WORD(0x04);
   viewport_top = DSP4_READ_WORD(0x06);
   project_y = DSP4_READ_WORD(0x08);
   viewport_bottom = DSP4_READ_WORD(0x0a);
   project_x1low = DSP4_READ_WORD(0x0c);
   project_x1 = DSP4_READ_WORD(0x0e);
   project_centerx = DSP4_READ_WORD(0x10);
   project_ptr = DSP4_READ_WORD(0x12);

   /* pre-compute */
   view_plane = PLANE_START;

   /* find projection targets */
   project_y1 = project_focaly;
   project_y -= viewport_bottom;
   project_x = project_centerx + project_x1;

   /* multi-op storage */
   multi_index2 = 0;

   /* command check */

   do
   {
      /* scan next command */
      DSP4.in_count = 2;
      DSP4_WAIT(1);

resume1:
      /* inspect input */
      command = DSP4_READ_WORD(0);

      /* check for opcode termination */
      if(command == 0x8000)
         break;

      /* already have 2 bytes in queue */
      DSP4.in_index = 2;
      DSP4.in_count = 12;
      DSP4_WAIT(2);

      /* process one loop of projection */

resume2:
      px_dx = 0;

      /* inspect inputs */
      plane = DSP4_READ_WORD(0);
      project_y2 = DSP4_READ_WORD(2);
      project_x2 = DSP4_READ_WORD(6);

      /* ignore invalid data */
      if((uint16_t) plane == 0x8001)
         continue;

      /* multi-op storage */
      project_focaly = multi_focaly[multi_index2];

      /* quadratic regression (rough) */
      if (project_focaly >= -0x0f)
         py_dy = (int16_t)(project_focaly * project_focaly * -0.20533553 - 1.08330005 * project_focaly - 69.61094639);
      else
         py_dy = (int16_t)(project_focaly * project_focaly * -0.000657035759 - 1.07629051 * project_focaly - 65.69315963);

      /* approximate # of raster lines */
      segments = ABS(project_y2 - project_y1);

      /* prevent overdraw */
      if(project_y2 >= raster)
         segments = 0;
      else
         raster = project_y2;

      /* don't draw outside the window */
      if(project_y2 < viewport_top)
         segments = 0;

      /* project new positions */
      if (segments > 0)
      {
         /* interpolate between projected points */
         px_dx = ((project_x2 - project_x1) << 8) / segments;
      }

      /* prepare pre-output */
      DSP4.out_count = 4 + 2 + 6 * segments;

      DSP4_WRITE_WORD(0, project_x2);
      DSP4_WRITE_WORD(2, project_y2);
      DSP4_WRITE_WORD(4, segments);

      index = 6;
      for (lcv = 0; lcv < segments; lcv++)
      {
         /* pre-compute */
         y_out = project_y + ((py_dy * lcv) >> 8);
         x_out = project_x + ((px_dx * lcv) >> 8);

         /* data */
         DSP4_WRITE_WORD(index + 0, project_ptr);
         DSP4_WRITE_WORD(index + 2, y_out);
         DSP4_WRITE_WORD(index + 4, x_out);
         index += 6;

         /* post-update */
         project_ptr -= 4;
      }

      /* update internal variables */
      project_y += ((py_dy * lcv) >> 8);
      project_x += ((px_dx * lcv) >> 8);

      /* new positions */
      if (segments > 0)
      {
         project_x1 = project_x2;
         project_y1 = project_y2;

         /* multi-op storage */
         multi_index2++;
      }
   } while (1);

   DSP4.waiting4command = true;
   DSP4.out_count = 0;
}

void DSP4_Op08(void)
{
   uint16_t command;
   /* used in envelope shaping */
   int16_t x1_final;
   int16_t x2_final;
   int16_t plane, x_left, y_left, x_right, y_right;
   int16_t envelope1, envelope2;
   DSP4.waiting4command = false;

   /* op flow control */
   switch (DSP4_Logic)
   {
   case 1:
      goto resume1;
      break;
   case 2:
      goto resume2;
      break;
   }

   /* process initial inputs */

   /* clip values */
   path_clipRight[0] = DSP4_READ_WORD(0x00);
   path_clipRight[1] = DSP4_READ_WORD(0x02);
   path_clipRight[2] = DSP4_READ_WORD(0x04);
   path_clipRight[3] = DSP4_READ_WORD(0x06);

   path_clipLeft[0] = DSP4_READ_WORD(0x08);
   path_clipLeft[1] = DSP4_READ_WORD(0x0a);
   path_clipLeft[2] = DSP4_READ_WORD(0x0c);
   path_clipLeft[3] = DSP4_READ_WORD(0x0e);

   /* path positions */
   path_pos[0] = DSP4_READ_WORD(0x20);
   path_pos[1] = DSP4_READ_WORD(0x22);
   path_pos[2] = DSP4_READ_WORD(0x24);
   path_pos[3] = DSP4_READ_WORD(0x26);

   /* data locations */
   path_ptr[0] = DSP4_READ_WORD(0x28);
   path_ptr[1] = DSP4_READ_WORD(0x2a);
   path_ptr[2] = DSP4_READ_WORD(0x2c);
   path_ptr[3] = DSP4_READ_WORD(0x2e);

   /* project_y1 lines */
   path_raster[0] = DSP4_READ_WORD(0x30);
   path_raster[1] = DSP4_READ_WORD(0x32);
   path_raster[2] = DSP4_READ_WORD(0x34);
   path_raster[3] = DSP4_READ_WORD(0x36);

   /* viewport_top */
   path_top[0] = DSP4_READ_WORD(0x38);
   path_top[1] = DSP4_READ_WORD(0x3a);
   path_top[2] = DSP4_READ_WORD(0x3c);
   path_top[3] = DSP4_READ_WORD(0x3e);

   /* unknown (constants) */

   view_plane = PLANE_START;

   /* command check */

   do
   {
      /* scan next command */
      DSP4.in_count = 2;
      DSP4_WAIT(1);

resume1:
      /* inspect input */
      command = DSP4_READ_WORD(0);

      /* terminate op */
      if(command == 0x8000)
         break;

      /* already have 2 bytes in queue */
      DSP4.in_index = 2;
      DSP4.in_count = 18;
      DSP4_WAIT(2);

resume2:
      /* projection begins */

      /* look at guidelines */
      plane = DSP4_READ_WORD(0x00);
      x_left = DSP4_READ_WORD(0x02);
      y_left = DSP4_READ_WORD(0x04);
      x_right = DSP4_READ_WORD(0x06);
      y_right = DSP4_READ_WORD(0x08);

      /* envelope guidelines (one frame only) */
      envelope1 = DSP4_READ_WORD(0x0a);
      envelope2 = DSP4_READ_WORD(0x0c);

      /* ignore invalid data */
      if((uint16_t) plane == 0x8001)
         continue;

      /* first init */
      if (plane == 0x7fff)
      {
         int32_t pos1, pos2;

         /* initialize projection */
         path_x[0] = x_left;
         path_x[1] = x_right;

         path_y[0] = y_left;
         path_y[1] = y_right;

         /* update coordinates */
         path_pos[0] -= x_left;
         path_pos[1] -= x_left;
         path_pos[2] -= x_right;
         path_pos[3] -= x_right;

         pos1 = path_pos[0] + envelope1;
         pos2 = path_pos[1] + envelope2;

         /* clip offscreen data */
         if(pos1 < path_clipLeft[0])
            pos1 = path_clipLeft[0];
         if(pos1 > path_clipRight[0])
            pos1 = path_clipRight[0];
         if(pos2 < path_clipLeft[1])
            pos2 = path_clipLeft[1];
         if(pos2 > path_clipRight[1])
            pos2 = path_clipRight[1];

         path_plane[0] = plane;
         path_plane[1] = plane;

         /* initial output */
         DSP4.out_count = 2;
         DSP4.output[0] = pos1 & 0xFF;
         DSP4.output[1] = pos2 & 0xFF;
      }
      /* proceed with projection */
      else
      {
         int16_t index = 0, lcv;
         int16_t left_inc = 0, right_inc = 0;
         int16_t dx1 = 0, dx2 = 0, dx3, dx4;

         /* # segments to traverse */
         segments = ABS(y_left - path_y[0]);

         /* prevent overdraw */
         if(y_left >= path_raster[0])
            segments = 0;
         else
            path_raster[0] = y_left;

         /* don't draw outside the window */
         if(path_raster[0] < path_top[0])
            segments = 0;

         /* proceed if visibility rules apply */
         if (segments > 0)
         {
            /* use previous data */
            dx1 = (envelope1 * path_plane[0] / view_plane);
            dx2 = (envelope2 * path_plane[0] / view_plane);

            /* use temporary envelope pitch (this frame only) */
            dx3 = (envelope1 * plane / view_plane);
            dx4 = (envelope2 * plane / view_plane);

            /* project new shapes (left side) */
            x1_final = x_left + dx1;
            x2_final = path_x[0] + dx3;

            /* interpolate between projected points with shaping */
            left_inc = ((x2_final - x1_final) << 8) / segments;

            /* project new shapes (right side) */
            x1_final = x_left + dx2;
            x2_final = path_x[0] + dx4;

            /* interpolate between projected points with shaping */
            right_inc = ((x2_final - x1_final) << 8) / segments;
            path_plane[0] = plane;
         }

         /* zone 1 */
         DSP4.out_count = (2 + 4 * segments);
         DSP4_WRITE_WORD(index, segments);
         index += 2;

         for (lcv = 1; lcv <= segments; lcv++)
         {
            int16_t pos1, pos2;

            /* pre-compute */
            pos1 = path_pos[0] + ((left_inc * lcv) >> 8) + dx1;
            pos2 = path_pos[1] + ((right_inc * lcv) >> 8) + dx2;

            /* clip offscreen data */
            if(pos1 < path_clipLeft[0])
               pos1 = path_clipLeft[0];
            if(pos1 > path_clipRight[0])
               pos1 = path_clipRight[0];
            if(pos2 < path_clipLeft[1])
               pos2 = path_clipLeft[1];
            if(pos2 > path_clipRight[1])
               pos2 = path_clipRight[1];

            /* data */
            DSP4_WRITE_WORD(index, path_ptr[0]);
            index += 2;
            DSP4.output[index++] = pos1 & 0xFF;
            DSP4.output[index++] = pos2 & 0xFF;

            /* post-update */
            path_ptr[0] -= 4;
            path_ptr[1] -= 4;
         }
         lcv--;

         if (segments > 0)
         {
            /* project points w/out the envelopes */
            int16_t inc = ((path_x[0] - x_left) << 8) / segments;

            /* post-store */
            path_pos[0] += ((inc * lcv) >> 8);
            path_pos[1] += ((inc * lcv) >> 8);

            path_x[0] = x_left;
            path_y[0] = y_left;
         }

         /* zone 2 */
         segments = ABS(y_right - path_y[1]);

         /* prevent overdraw */
         if(y_right >= path_raster[2])
            segments = 0;
         else path_raster[2] = y_right;

         /* don't draw outside the window */
         if(path_raster[2] < path_top[2])
            segments = 0;

         /* proceed if visibility rules apply */
         if (segments > 0)
         {
            /* use previous data */
            dx1 = (envelope1 * path_plane[1] / view_plane);
            dx2 = (envelope2 * path_plane[1] / view_plane);

            /* use temporary envelope pitch (this frame only) */
            dx3 = (envelope1 * plane / view_plane);
            dx4 = (envelope2 * plane / view_plane);

            /* project new shapes (left side) */
            x1_final = x_left + dx1;
            x2_final = path_x[1] + dx3;

            /* interpolate between projected points with shaping */
            left_inc = ((x2_final - x1_final) << 8) / segments;

            /* project new shapes (right side) */
            x1_final = x_left + dx2;
            x2_final = path_x[1] + dx4;

            /* interpolate between projected points with shaping */
            right_inc = ((x2_final - x1_final) << 8) / segments;

            path_plane[1] = plane;
         }

         /* write out results */
         DSP4.out_count += (2 + 4 * segments);
         DSP4_WRITE_WORD(index, segments);
         index += 2;

         for (lcv = 1; lcv <= segments; lcv++)
         {
            int16_t pos1, pos2;

            /* pre-compute */
            pos1 = path_pos[2] + ((left_inc * lcv) >> 8) + dx1;
            pos2 = path_pos[3] + ((right_inc * lcv) >> 8) + dx2;

            /* clip offscreen data */
            if(pos1 < path_clipLeft[2])
               pos1 = path_clipLeft[2];
            if(pos1 > path_clipRight[2])
               pos1 = path_clipRight[2];
            if(pos2 < path_clipLeft[3])
               pos2 = path_clipLeft[3];
            if(pos2 > path_clipRight[3])
               pos2 = path_clipRight[3];

            /* data */
            DSP4_WRITE_WORD(index, path_ptr[2]);
            index += 2;
            DSP4.output[index++] = pos1 & 0xFF;
            DSP4.output[index++] = pos2 & 0xFF;

            /* post-update */
            path_ptr[2] -= 4;
            path_ptr[3] -= 4;
         }
         lcv--;

         if (segments > 0)
         {
            /* project points w/out the envelopes */
            int16_t inc = ((path_x[1] - x_right) << 8) / segments;

            /* post-store */
            path_pos[2] += ((inc * lcv) >> 8);
            path_pos[3] += ((inc * lcv) >> 8);

            path_x[1] = x_right;
            path_y[1] = y_right;
         }
      }
   } while (1);

   DSP4.waiting4command = true;
   DSP4.out_count = 2;
   DSP4_WRITE_WORD(0, 0);
}

void DSP4_Op0D(void)
{
   uint16_t command;
   /* inspect inputs */
   int16_t plane;
   int16_t index, lcv;
   int16_t py_dy, px_dx;
   int16_t y_out, x_out;

   DSP4.waiting4command = false;

   /* op flow control */
   switch (DSP4_Logic)
   {
   case 1:
      goto resume1;
      break;
   case 2:
      goto resume2;
      break;
   }

   /* process initial inputs */

   /* sort inputs */
   project_focaly = DSP4_READ_WORD(0x02);
   raster = DSP4_READ_WORD(0x04);
   viewport_top = DSP4_READ_WORD(0x06);
   project_y = DSP4_READ_WORD(0x08);
   viewport_bottom = DSP4_READ_WORD(0x0a);
   project_x1low = DSP4_READ_WORD(0x0c);
   project_x1 = DSP4_READ_WORD(0x0e);
   project_focalx = DSP4_READ_WORD(0x0e);
   project_centerx = DSP4_READ_WORD(0x10);
   project_ptr = DSP4_READ_WORD(0x12);
   project_pitchylow = DSP4_READ_WORD(0x16);
   project_pitchy = DSP4_READ_WORD(0x18);
   project_pitchxlow = DSP4_READ_WORD(0x1a);
   project_pitchx = DSP4_READ_WORD(0x1c);
   far_plane = DSP4_READ_WORD(0x1e);

   /* multi-op storage */
   multi_index1++;
   multi_index1 %= 4;

   /* remap 0D->09 window data ahead of time */
   /* index starts at 1-3,0 */
   /* Op0D: BL,TL,BR,TR */
   /* Op09: TL,TR,BL,BR (1,2,3,0) */
   switch (multi_index1)
   {
   case 1:
      multi_index2 = 3;
      break;
   case 2:
      multi_index2 = 1;
      break;
   case 3:
      multi_index2 = 0;
      break;
   case 0:
      multi_index2 = 2;
      break;
   }

   /* pre-compute */
   view_plane = PLANE_START;

   /* figure out projection data */
   project_y -= viewport_bottom;
   project_x = project_centerx + project_x1;

   /* command check */

   do
   {
      /* scan next command */
      DSP4.in_count = 2;
      DSP4_WAIT(1);

resume1:
      /* inspect input */
      command = DSP4_READ_WORD(0);

      /* terminate op */
      if(command == 0x8000)
         break;

      /* already have 2 bytes in queue */
      DSP4.in_index = 2;
      DSP4.in_count = 8;
      DSP4_WAIT(2);

      /* project section of the track */

resume2:
      plane = DSP4_READ_WORD(0);
      px_dx = 0;


      /* ignore invalid data */
      if((uint16_t) plane == 0x8001)
         continue;

      /* one-time init */
      if (far_plane)
      {
         /* setup final data */
         project_x1 = project_focalx;
         project_y1 = project_focaly;
         plane = far_plane;
         far_plane = 0;
      }

      /* use proportional triangles to project new coords */
      project_x2 = project_focalx * plane / view_plane;
      project_y2 = project_focaly * plane / view_plane;

      /* quadratic regression (rough) */
      if (project_focaly >= -0x0f)
         py_dy = (int16_t)(project_focaly * project_focaly * -0.20533553 - 1.08330005 * project_focaly - 69.61094639);
      else
         py_dy = (int16_t)(project_focaly * project_focaly * -0.000657035759 - 1.07629051 * project_focaly - 65.69315963);

      /* approximate # of raster lines */
      segments = ABS(project_y2 - project_y1);

      /* prevent overdraw */
      if(project_y2 >= raster)
         segments = 0;
      else
         raster = project_y2;

      /* don't draw outside the window */
      if(project_y2 < viewport_top)
         segments = 0;

      /* project new positions */
      if (segments > 0)
      {
         /* interpolate between projected points */
         px_dx = ((project_x2 - project_x1) << 8) / segments;
      }

      /* prepare output */
      DSP4.out_count = 8 + 2 + 6 * segments;
      DSP4_WRITE_WORD(0, project_focalx);
      DSP4_WRITE_WORD(2, project_x2);
      DSP4_WRITE_WORD(4, project_focaly);
      DSP4_WRITE_WORD(6, project_y2);
      DSP4_WRITE_WORD(8, segments);
      index = 10;

      for (lcv = 0; lcv < segments; lcv++) /* iterate through each point */
      {
         /* step through the projected line */
         y_out = project_y + ((py_dy * lcv) >> 8);
         x_out = project_x + ((px_dx * lcv) >> 8);

         /* data */
         DSP4_WRITE_WORD(index + 0, project_ptr);
         DSP4_WRITE_WORD(index + 2, y_out);
         DSP4_WRITE_WORD(index + 4, x_out);
         index += 6;

         /* post-update */
         project_ptr -= 4;
      }

      /* post-update */
      project_y += ((py_dy * lcv) >> 8);
      project_x += ((px_dx * lcv) >> 8);

      if (segments > 0)
      {
         project_x1 = project_x2;
         project_y1 = project_y2;

         /* multi-op storage */
         multi_farplane[multi_index2] = plane;
         multi_raster[multi_index2] = project_y1;
      }

      /* update focal projection points */
      project_pitchy += (int8_t)DSP4.parameters[3];
      project_pitchx += (int8_t)DSP4.parameters[5];

      project_focaly += project_pitchy;
      project_focalx += project_pitchx;
   } while (1);

   DSP4.waiting4command = true;
   DSP4.out_count = 0;
}

void DSP4_Op09(void)
{
   uint16_t command;
   bool clip;
   int16_t sp_x, sp_y, sp_oam, sp_msb;
   int16_t sp_dx, sp_dy;

   DSP4.waiting4command = false;

   /* op flow control */
   switch (DSP4_Logic)
   {
   case 1:
      goto resume1;
      break;
   case 2:
      goto resume2;
      break;
   case 3:
      goto resume3;
      break;
   case 4:
      goto resume4;
      break;
   case 5:
      goto resume5;
      break;
   case 6:
      goto resume6;
      break;
   case 7:
      goto resume7;
      break;
   }

   /* process initial inputs */

   /* grab screen information */
   view_plane = PLANE_START;
   center_x = DSP4_READ_WORD(0x00);
   center_y = DSP4_READ_WORD(0x02);
   viewport_left = DSP4_READ_WORD(0x06);
   viewport_right = DSP4_READ_WORD(0x08);
   viewport_top = DSP4_READ_WORD(0x0a);
   viewport_bottom = DSP4_READ_WORD(0x0c);

   /* expand viewport dimensions */
   viewport_left -= 8;

   /* cycle through viewport window data */
   multi_index1++;
   multi_index1 %= 4;

   /* convert track line to the window region */
   project_y2 = center_y + multi_raster[multi_index1] * (viewport_bottom - center_y) / (0x33 - 0);
   if (!op09_mode)
      project_y2 -= 2;

   goto no_sprite;

   do
   {
      /* check for new sprites */
      do
      {
         uint16_t second;

         DSP4.in_count = 4;
         DSP4.in_index = 2;
         DSP4_WAIT(1);

resume1:
         /* try to classify sprite */
         second = DSP4_READ_WORD(2);

         /* op termination */
         if(second == 0x8000)
            goto terminate;

         second >>= 8;
         sprite_type = 0;

         /* vehicle sprite */
         if (second == 0x90)
         {
            sprite_type = 1;
            break;
         }
         /* terrain sprite */
         else if (second != 0)
         {
            sprite_type = 2;
            break;
         }

no_sprite:
         /* no sprite. try again */
         DSP4.in_count = 2;
         DSP4_WAIT(2);

resume2:;
      } while (1);

      /* process projection information */

sprite_found:
      /* vehicle sprite */
      if (sprite_type == 1)
      {
         int16_t plane;
         int16_t car_left, car_right;
         int16_t focal_back;
         int32_t height;

         /* we already have 4 bytes we want */
         DSP4.in_count = 6 + 12;
         DSP4.in_index = 4;
         DSP4_WAIT(3);

resume3:
         /* filter inputs */
         project_y1 = DSP4_READ_WORD(0x00);
         focal_back = DSP4_READ_WORD(0x06);
         car_left = DSP4_READ_WORD(0x0c);
         plane = DSP4_READ_WORD(0x0e);
         car_right = DSP4_READ_WORD(0x10);

         /* calculate car's x-center */
         project_focalx = car_right - car_left;

         /* determine how far into the screen to project */
         project_focaly = focal_back;
         project_x = project_focalx * plane / view_plane;
         segments = 0x33 - project_focaly * plane / view_plane;
         far_plane = plane;

         /* prepare memory */
         sprite_x = center_x + project_x;
         sprite_y = viewport_bottom - segments;
         far_plane = plane;

         /* make the car's x-center available */
         DSP4.out_count = 2;
         DSP4_WRITE_WORD(0, project_focalx);

         /* grab a few remaining vehicle values */
         DSP4.in_count = 4;

         DSP4_WAIT(4);

resume4: /* store final values */
         height = DSP4_READ_WORD(0);
         sprite_offset = DSP4_READ_WORD(2);

         /* vertical lift factor */
         sprite_y += height;
      }
      else if (sprite_type == 2) /* terrain sprite */
      {
         int16_t plane;

         /* we already have 4 bytes we want */
         DSP4.in_count = 6 + 6 + 2;
         DSP4.in_index = 4;
         DSP4_WAIT(5);

resume5:
         /* sort loop inputs */
         project_y1 = DSP4_READ_WORD(0x00);
         plane = DSP4_READ_WORD(0x02);
         project_centerx = DSP4_READ_WORD(0x04);
         project_focalx = DSP4_READ_WORD(0x08);
         project_focaly = DSP4_READ_WORD(0x0a);
         sprite_offset = DSP4_READ_WORD(0x0c);

         /* determine distances into virtual world */
         segments = 0x33 - project_y1;
         project_x = project_focalx * plane / view_plane;
         project_y = project_focaly * plane / view_plane;

         /* prepare memory */
         sprite_x = center_x + project_x - project_centerx;
         sprite_y = viewport_bottom - segments + project_y;
         far_plane = plane;
      }

      /* default sprite size: 16x16 */
      sprite_size = true;

      /* convert tile data to OAM */

      do
      {
         DSP4.in_count = 2;
         DSP4_WAIT(6);

resume6:
         command = DSP4_READ_WORD(0);

         /* opcode termination */
         if(command == 0x8000)
            goto terminate;

         /* toggle sprite size */
         if (command == 0x0000)
         {
            sprite_size = !sprite_size;
            continue;
         }

         /* new sprite information */
         command >>= 8;
         if (command != 0x20 && command != 0x40 && command != 0x60 && command != 0xa0 && command != 0xc0 && command != 0xe0)
            break;

         DSP4.in_count = 6;
         DSP4.in_index = 2;
         DSP4_WAIT(7);

         /* process tile data */

resume7:
         /* sprite deltas */
         sp_dy = DSP4_READ_WORD(2);
         sp_dx = DSP4_READ_WORD(4);

         /* update coordinates */
         sp_y = sprite_y + sp_dy;
         sp_x = sprite_x + sp_dx;

         /* reject points outside the clipping window */
         clip = false;
         if(sp_x < viewport_left || sp_x > viewport_right)
            clip = true;
         if(sp_y < viewport_top || sp_y > viewport_bottom)
            clip = true;

         /* track depth sorting */
         if(far_plane <= multi_farplane[multi_index1] && sp_y >= project_y2)
            clip = true;

         /* don't draw offscreen coordinates */
         DSP4.out_count = 0;
         if (!clip)
         {
            int16_t out_index = 0;
            int16_t offset = DSP4_READ_WORD(0);

            /* update sprite nametable/attribute information */
            sp_oam = sprite_offset + offset;
            sp_msb = (sp_x < 0 || sp_x > 255);

            /* emit transparency information */
            if((sprite_offset & 0x08) && ((sprite_type == 1 && sp_y >= 0xcc) || (sprite_type == 2 && sp_y >= 0xbb)))
            {
               DSP4.out_count = 6;

               /* one block of OAM data */
               DSP4_WRITE_WORD(0, 1);

               /* OAM: x,y,tile,no attr */
               DSP4.output[2] = sp_x & 0xFF;
               DSP4.output[3] = (sp_y + 6) & 0xFF;
               DSP4_WRITE_WORD(4, 0xEE);
               out_index = 6;

               /* OAM: size,msb data */
               DSP4_Op06(sprite_size, (int8_t) sp_msb);
            }

            /* normal data */
            DSP4.out_count += 8;

            /* one block of OAM data */
            DSP4_WRITE_WORD(out_index + 0, 1);

            /* OAM: x,y,tile,attr */
            DSP4.output[out_index + 2] = sp_x & 0xFF;
            DSP4.output[out_index + 3] = sp_y & 0xFF;
            DSP4_WRITE_WORD(out_index + 4, sp_oam);

            /* no following OAM data */
            DSP4_WRITE_WORD(out_index + 6, 0);

            /* OAM: size,msb data */
            DSP4_Op06(sprite_size, (int8_t) sp_msb);
         }

         /* no sprite information */
         if (DSP4.out_count == 0)
         {
            DSP4.out_count = 2;
            DSP4_WRITE_WORD(0, 0);
         }
      } while (1);

      /* special cases: plane == 0x0000 */

      /* special vehicle case */
      if (command == 0x90)
      {
         sprite_type = 1;

         /* shift bytes */
         DSP4.parameters[2] = DSP4.parameters[0];
         DSP4.parameters[3] = DSP4.parameters[1];
         DSP4.parameters[0] = 0;
         DSP4.parameters[1] = 0;

         goto sprite_found;
      }
      else if (command != 0x00 && command != 0xff) /* special terrain case */
      {
         sprite_type = 2;

         /* shift bytes */
         DSP4.parameters[2] = DSP4.parameters[0];
         DSP4.parameters[3] = DSP4.parameters[1];
         DSP4.parameters[0] = 0;
         DSP4.parameters[1] = 0;

         goto sprite_found;
      }
   } while (1);

terminate:
   DSP4.waiting4command = true;
   DSP4.out_count = 0;
}
