romdevtools 0.44.0 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +572 -0
  2. package/README.md +9 -7
  3. package/examples/dreamcast/hello/main.c +24 -0
  4. package/examples/n64/platformer/main.c +158 -0
  5. package/examples/n64/puzzle/main.c +117 -0
  6. package/examples/n64/racing/main.c +147 -0
  7. package/examples/n64/shmup/main.c +122 -0
  8. package/examples/n64/sports/main.c +127 -0
  9. package/examples/ps1/platformer/main.c +158 -0
  10. package/examples/ps1/puzzle/main.c +125 -0
  11. package/examples/ps1/racing/main.c +147 -0
  12. package/examples/ps1/shmup/main.c +192 -0
  13. package/examples/ps1/sports/main.c +127 -0
  14. package/examples/ps1/sprite_move/main.c +38 -0
  15. package/package.json +11 -2
  16. package/src/analysis/analyze.js +224 -29
  17. package/src/analysis/decompile.js +7 -0
  18. package/src/analysis/decompiler/sleigh/mips.ldefs +25 -0
  19. package/src/analysis/decompiler/sleigh/mips32.pspec +78 -0
  20. package/src/analysis/decompiler/sleigh/mips32be.cspec +107 -0
  21. package/src/analysis/decompiler/sleigh/mips32be.sla +211162 -0
  22. package/src/analysis/decompiler/sleigh/mips32le.cspec +106 -0
  23. package/src/analysis/decompiler/sleigh/mips32le.sla +210624 -0
  24. package/src/analysis/rizin.js +24 -1
  25. package/src/cores/capabilities.js +122 -2
  26. package/src/cores/registry.js +17 -0
  27. package/src/host/LibretroGL.js +273 -0
  28. package/src/host/LibretroGLBridge.js +836 -0
  29. package/src/host/LibretroHost.js +203 -3
  30. package/src/host/callbacks.js +22 -2
  31. package/src/host/coreLoader.js +65 -5
  32. package/src/host/cpu-state.js +27 -0
  33. package/src/host/framebuffer.js +14 -1
  34. package/src/host/glOptionalDep.js +60 -0
  35. package/src/host/n64-ai-state.js +43 -0
  36. package/src/host/ps1-spu-state.js +65 -0
  37. package/src/host/retroConstants.js +14 -0
  38. package/src/mcp/tools/disasm.js +2 -0
  39. package/src/mcp/tools/frame.js +18 -9
  40. package/src/mcp/tools/lifecycle.js +1 -1
  41. package/src/mcp/tools/platform-tools.js +20 -4
  42. package/src/mcp/tools/platforms.js +38 -4
  43. package/src/mcp/tools/project.js +116 -0
  44. package/src/mcp/tools/rendering-context.js +2 -1
  45. package/src/mcp/tools/toolchain.js +1 -1
  46. package/src/platforms/n64/lib/c/n64.c +196 -0
  47. package/src/platforms/n64/lib/c/n64.h +68 -0
  48. package/src/platforms/ps1/lib/c/psx.c +200 -0
  49. package/src/platforms/ps1/lib/c/psx.h +83 -0
  50. package/src/toolchains/index.js +65 -0
  51. package/src/toolchains/mips-c/lib/be/libc.a +0 -0
  52. package/src/toolchains/mips-c/lib/be/libgcc.a +0 -0
  53. package/src/toolchains/mips-c/lib/be/libm.a +0 -0
  54. package/src/toolchains/mips-c/lib/el/libc.a +0 -0
  55. package/src/toolchains/mips-c/lib/el/libm.a +0 -0
  56. package/src/toolchains/mips-c/lib/n64-crt0.s +21 -0
  57. package/src/toolchains/mips-c/lib/n64-ipl3.s +30 -0
  58. package/src/toolchains/mips-c/lib/n64.ld +15 -0
  59. package/src/toolchains/mips-c/lib/ps1-crt0.s +20 -0
  60. package/src/toolchains/mips-c/lib/ps1.ld +15 -0
  61. package/src/toolchains/mips-c/lib/softint.c +37 -0
  62. package/src/toolchains/mips-c/mips-c.js +155 -0
  63. package/src/toolchains/mips-elf-gcc/gcc.js +130 -0
  64. package/src/toolchains/sh-c/lib/dc-crt0.s +23 -0
  65. package/src/toolchains/sh-c/lib/dc.h +102 -0
  66. package/src/toolchains/sh-c/lib/dc.ld +11 -0
  67. package/src/toolchains/sh-c/lib/libc.a +0 -0
  68. package/src/toolchains/sh-c/lib/libgcc.a +0 -0
  69. package/src/toolchains/sh-c/lib/libm.a +0 -0
  70. package/src/toolchains/sh-c/sh-c.js +101 -0
  71. package/src/toolchains/sh-elf-gcc/gcc.js +122 -0
@@ -0,0 +1,158 @@
1
+ /*
2
+ * platformer/main.c — BLOCK HOP: a 3D PlayStation platformer.
3
+ *
4
+ * A 3D character (cube hero) runs and jumps across floating platforms rendered in
5
+ * perspective. Gravity + jump physics in 16.16 fixed point, AABB landing tests
6
+ * against platform tops, coins to collect, a pit you can fall into (lose a life),
7
+ * a chase camera that follows the hero. Title -> play -> game-over, score + lives.
8
+ *
9
+ * Build: build({ platform:"ps1", language:"c" }). Controls: LEFT/RIGHT run,
10
+ * CROSS jump, START begin/restart.
11
+ *
12
+ * 3D technique: platforms are flat-topped boxes drawn as no-cull quads; the hero
13
+ * is a culled cube. The camera tracks the hero's X with a slight downward tilt so
14
+ * you see the platform tops. Coins are small spinning cubes.
15
+ */
16
+ #include "psx.h"
17
+
18
+ #define NPLAT 6
19
+ #define NCOIN 6
20
+ enum { TITLE, PLAY, OVER };
21
+
22
+ typedef struct { fix x, y, z, w; } Plat; /* top-center + half-width */
23
+ static Plat plat[NPLAT];
24
+ static struct { fix x, y, z; int got; } coin[NCOIN];
25
+
26
+ static fix hx, hy, vy; /* hero pos + vertical velocity */
27
+ static int onground;
28
+ static int state, score, lives, hi;
29
+ static fix cam_x, spin;
30
+ static unsigned int prev_pad;
31
+
32
+ static void box_top(fix cx, fix top, fix z, fix hw, fix depth, unsigned int col)
33
+ {
34
+ Vec3 a,b,c,d;
35
+ a.x=cx-hw; a.y=top; a.z=z-depth; b.x=cx+hw; b.y=top; b.z=z-depth;
36
+ c.x=cx+hw; c.y=top; c.z=z+depth; d.x=cx-hw; d.y=top; d.z=z+depth;
37
+ psx_quad3d_nc(a,b,c,d,col); /* top face */
38
+ /* front face for thickness */
39
+ a.y=top; b.y=top; c.y=top-FIX(2); d.y=top-FIX(2);
40
+ a.x=cx-hw; a.z=z+depth; b.x=cx+hw; b.z=z+depth; c.x=cx+hw; c.z=z+depth; d.x=cx-hw; d.z=z+depth;
41
+ psx_quad3d_nc(a,b,c,d, col - RGB(20,20,20));
42
+ }
43
+
44
+ static void draw_cube_at(fix x, fix y, fix z, fix s, fix yaw, unsigned int col)
45
+ {
46
+ Vec3 v[8]; int i;
47
+ static const int sx[8]={-1,1,1,-1,-1,1,1,-1},sy[8]={-1,-1,1,1,-1,-1,1,1},sz[8]={-1,-1,-1,-1,1,1,1,1};
48
+ psx_model(x,y,z,yaw);
49
+ for(i=0;i<8;i++){v[i].x=sx[i]*s;v[i].y=sy[i]*s;v[i].z=sz[i]*s;}
50
+ psx_quad3d(v[0],v[1],v[2],v[3],col); psx_quad3d(v[5],v[4],v[7],v[6],col);
51
+ psx_quad3d(v[4],v[0],v[3],v[7],col); psx_quad3d(v[1],v[5],v[6],v[2],col);
52
+ psx_quad3d(v[4],v[5],v[1],v[0],col); psx_quad3d(v[3],v[2],v[6],v[7],col);
53
+ }
54
+
55
+ static void reset_game(void)
56
+ {
57
+ int i;
58
+ /* a run of platforms along +X with a gap (pit) in the middle */
59
+ for (i = 0; i < NPLAT; i++) {
60
+ plat[i].x = (fix)(i * 4 - 4) << 16;
61
+ plat[i].y = (fix)((i % 3) - 1) << 16;
62
+ plat[i].z = FIX(8);
63
+ plat[i].w = FIX(2);
64
+ }
65
+ plat[3].y = FIX(-6); /* the pit: a platform dropped far below */
66
+ for (i = 0; i < NCOIN; i++) { coin[i].x = (fix)(i*4-3)<<16; coin[i].y = plat[i].y + FIX(2); coin[i].z = FIX(8); coin[i].got = 0; }
67
+ hx = FIX(-4); hy = FIX(2); vy = 0; onground = 0;
68
+ score = 0; lives = 3;
69
+ }
70
+
71
+ /* find the platform top under the hero's x (or a very low floor = pit). */
72
+ static fix ground_at(fix x)
73
+ {
74
+ int i; fix best = FIX(-20);
75
+ for (i = 0; i < NPLAT; i++) {
76
+ fix dx = x - plat[i].x; if (dx < 0) dx = -dx;
77
+ if (dx < plat[i].w) { if (plat[i].y > best) best = plat[i].y; }
78
+ }
79
+ return best;
80
+ }
81
+
82
+ static void update(void)
83
+ {
84
+ unsigned int pad = psx_pad();
85
+ int i;
86
+ if (pad & PAD_LEFT) hx -= FIXF(0.18f);
87
+ if (pad & PAD_RIGHT) hx += FIXF(0.18f);
88
+ if (hx < FIX(-6)) hx = FIX(-6); if (hx > FIX(14)) hx = FIX(14);
89
+
90
+ if ((pad & PAD_CROSS) && !(prev_pad & PAD_CROSS) && onground) { vy = FIXF(0.55f); onground = 0; }
91
+
92
+ vy -= FIXF(0.04f); /* gravity */
93
+ hy += vy;
94
+ {
95
+ fix g = ground_at(hx) + FIX(1); /* hero sits 1 unit above the platform top */
96
+ if (hy <= g && vy <= 0) {
97
+ if (g < FIX(-8)) { /* fell in the pit */
98
+ if (--lives <= 0) { if (score>hi) hi=score; state = OVER; }
99
+ hx = FIX(-4); hy = FIX(4); vy = 0;
100
+ } else { hy = g; vy = 0; onground = 1; }
101
+ } else onground = 0;
102
+ }
103
+
104
+ for (i = 0; i < NCOIN; i++) if (!coin[i].got) {
105
+ fix dx = hx - coin[i].x, dy = hy - coin[i].y;
106
+ if (dx<0)dx=-dx; if (dy<0)dy=-dy;
107
+ if (dx < FIX(1) && dy < FIX(2)) { coin[i].got = 1; score += 100; }
108
+ }
109
+
110
+ cam_x += (hx - cam_x) >> 3; /* smooth follow */
111
+ spin += FIX(4);
112
+ prev_pad = pad;
113
+ }
114
+
115
+ static void render(void)
116
+ {
117
+ int i;
118
+ psx_clear(RGB(40, 60, 120)); /* sky */
119
+ psx_camera(cam_x, FIX(2), FIX(-1), 0, FIXF(-0.22f));
120
+
121
+ for (i = 0; i < NPLAT; i++)
122
+ box_top(plat[i].x, plat[i].y, plat[i].z, plat[i].w, FIX(2),
123
+ (i==3)?RGB(120,40,40):RGB(90, 150, 70));
124
+ for (i = 0; i < NCOIN; i++) if (!coin[i].got)
125
+ draw_cube_at(coin[i].x, coin[i].y, coin[i].z, FIXF(0.3f), spin, RGB(255, 220, 40));
126
+
127
+ draw_cube_at(hx, hy, FIX(8), FIXF(0.6f), 0, RGB(230, 90, 60));
128
+
129
+ psx_number(8, 6, (unsigned)score, RGB(255,255,255));
130
+ for (i = 0; i < lives; i++) psx_rect(290 - i*10, 8, 6, 6, RGB(230, 90, 60));
131
+ }
132
+
133
+ int main(void)
134
+ {
135
+ psx_init();
136
+ psx_srand(0xBEEF);
137
+ state = TITLE; prev_pad = 0; cam_x = 0;
138
+ for (;;) {
139
+ unsigned int pad = psx_pad();
140
+ if (state == TITLE) {
141
+ psx_clear(RGB(20, 30, 60));
142
+ psx_camera(0, 0, FIX(-1), 0, 0);
143
+ spin += FIX(3);
144
+ draw_cube_at(0, 0, FIX(6), FIX(1), spin, RGB(230, 90, 60));
145
+ if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_game(); state = PLAY; }
146
+ prev_pad = pad;
147
+ } else if (state == PLAY) { update(); render(); }
148
+ else {
149
+ psx_clear(RGB(8, 8, 30));
150
+ psx_number(110, 100, (unsigned)score, RGB(255, 220, 40));
151
+ psx_number(120, 130, (unsigned)hi, RGB(255, 255, 120));
152
+ if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_game(); state = PLAY; }
153
+ prev_pad = pad;
154
+ }
155
+ psx_vsync();
156
+ }
157
+ return 0;
158
+ }
@@ -0,0 +1,125 @@
1
+ /*
2
+ * puzzle/main.c — DROP GRID: a 2D PlayStation falling-block puzzle.
3
+ *
4
+ * The one 2D game in the PS1 set — a grid puzzle is naturally flat, so it's drawn
5
+ * with the GPU's 2D rectangles (no 3D pipeline). Colored blocks fall down a well;
6
+ * steer + drop them; a full row clears and scores. Speed ramps up. Title -> play ->
7
+ * game-over (stack reaches the top). 16.16 not needed — plain integer grid logic.
8
+ *
9
+ * Build: build({ platform:"ps1", language:"c" }). Controls: LEFT/RIGHT move the
10
+ * falling block, DOWN soft-drop, START begin/restart.
11
+ *
12
+ * 2D technique: an integer GRID is the model; render() paints each filled cell as a
13
+ * psx_rect. This is the right tool for a flat board even on a 3D machine.
14
+ */
15
+ #include "psx.h"
16
+
17
+ #define GW 8
18
+ #define GH 12
19
+ #define CELL 16
20
+ #define OX 96 /* board origin on screen */
21
+ #define OY 24
22
+
23
+ enum { TITLE, PLAY, OVER };
24
+
25
+ static unsigned char grid[GH][GW]; /* 0 empty, else color index 1..4 */
26
+ static int fx, fy, fc; /* falling block cell + color */
27
+ static int tick, fall_period;
28
+ static int state, score, hi;
29
+ static unsigned int prev_pad;
30
+
31
+ static const unsigned int PAL[5] = {
32
+ 0, RGB(230,60,60), RGB(60,200,90), RGB(80,150,255), RGB(240,200,60)
33
+ };
34
+
35
+ static void new_block(void)
36
+ {
37
+ fx = GW/2; fy = 0; fc = 1 + (psx_rand() % 4);
38
+ if (grid[0][fx]) { if (score>hi) hi=score; state = OVER; }
39
+ }
40
+
41
+ static void reset_game(void)
42
+ {
43
+ int r,c;
44
+ for (r=0;r<GH;r++) for(c=0;c<GW;c++) grid[r][c]=0;
45
+ score = 0; tick = 0; fall_period = 30;
46
+ new_block();
47
+ }
48
+
49
+ static int blocked(int x, int y) { return x<0 || x>=GW || y>=GH || (y>=0 && grid[y][x]); }
50
+
51
+ static void lock_and_clear(void)
52
+ {
53
+ int r,c,rr;
54
+ grid[fy][fx] = fc;
55
+ /* clear full rows */
56
+ for (r = GH-1; r >= 0; r--) {
57
+ int full = 1;
58
+ for (c=0;c<GW;c++) if (!grid[r][c]) { full = 0; break; }
59
+ if (full) {
60
+ for (rr = r; rr > 0; rr--) for(c=0;c<GW;c++) grid[rr][c]=grid[rr-1][c];
61
+ for (c=0;c<GW;c++) grid[0][c]=0;
62
+ score += 100;
63
+ if (fall_period > 8) fall_period--;
64
+ r++; /* recheck this row */
65
+ }
66
+ }
67
+ new_block();
68
+ }
69
+
70
+ static void step_fall(void)
71
+ {
72
+ if (!blocked(fx, fy+1)) fy++;
73
+ else lock_and_clear();
74
+ }
75
+
76
+ static void update(void)
77
+ {
78
+ unsigned int pad = psx_pad();
79
+ if ((pad & PAD_LEFT) && !(prev_pad & PAD_LEFT) && !blocked(fx-1, fy)) fx--;
80
+ if ((pad & PAD_RIGHT) && !(prev_pad & PAD_RIGHT) && !blocked(fx+1, fy)) fx++;
81
+ if (pad & PAD_DOWN) { step_fall(); tick = 0; }
82
+ if (++tick >= fall_period) { step_fall(); tick = 0; }
83
+ prev_pad = pad;
84
+ }
85
+
86
+ static void render(void)
87
+ {
88
+ int r,c;
89
+ psx_clear(RGB(12, 12, 24));
90
+ /* well border */
91
+ psx_rect(OX-3, OY-3, GW*CELL+6, GH*CELL+6, RGB(60,60,90));
92
+ psx_rect(OX, OY, GW*CELL, GH*CELL, RGB(20,20,36));
93
+ /* settled blocks */
94
+ for (r=0;r<GH;r++) for(c=0;c<GW;c++) if (grid[r][c])
95
+ psx_rect(OX+c*CELL+1, OY+r*CELL+1, CELL-2, CELL-2, PAL[grid[r][c]]);
96
+ /* falling block */
97
+ if (fy>=0) psx_rect(OX+fx*CELL+1, OY+fy*CELL+1, CELL-2, CELL-2, PAL[fc]);
98
+ psx_number(8, 6, (unsigned)score, RGB(255,255,255));
99
+ }
100
+
101
+ int main(void)
102
+ {
103
+ psx_init();
104
+ psx_srand(0xD0D0);
105
+ state = TITLE; prev_pad = 0;
106
+ for (;;) {
107
+ unsigned int pad = psx_pad();
108
+ if (state == TITLE) {
109
+ psx_clear(RGB(20, 12, 30));
110
+ psx_rect(110, 90, 100, 60, RGB(80,150,255));
111
+ psx_rect(120, 100, 80, 40, RGB(230,60,60));
112
+ if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_game(); state = PLAY; }
113
+ prev_pad = pad;
114
+ } else if (state == PLAY) { update(); render(); }
115
+ else {
116
+ psx_clear(RGB(30, 8, 8));
117
+ psx_number(110, 100, (unsigned)score, RGB(255, 120, 120));
118
+ psx_number(120, 130, (unsigned)hi, RGB(255, 255, 120));
119
+ if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_game(); state = PLAY; }
120
+ prev_pad = pad;
121
+ }
122
+ psx_vsync();
123
+ }
124
+ return 0;
125
+ }
@@ -0,0 +1,147 @@
1
+ /*
2
+ * racing/main.c — POLE BENDER: a 3D PlayStation racer.
3
+ *
4
+ * The road is a ribbon of quads receding to the horizon (real perspective, drawn
5
+ * far-to-near). You steer a car near the camera between the verges; the world
6
+ * scrolls toward you and the track curves. Rival cars (cubes) sit further up the
7
+ * road and grow as you close on them — collide and you spin out (lose time/score).
8
+ * Title -> race -> results state machine, distance score, a lap timer.
9
+ *
10
+ * Build: build({ platform:"ps1", language:"c" }). Controls: LEFT/RIGHT steer,
11
+ * UP accelerate, DOWN brake, START begins/restarts.
12
+ *
13
+ * 3D technique: the track is N segments ahead; each segment is a quad between two
14
+ * road-edge pairs, its centerline x driven by a curve value so the road bends. The
15
+ * camera sits just behind/above the car. All 16.16 fixed point.
16
+ */
17
+ #include "psx.h"
18
+
19
+ #define SEGS 14
20
+ enum { TITLE, RACE, DONE };
21
+
22
+ static fix car_x; /* lateral position of the car */
23
+ static fix scroll; /* how far we've travelled (for segment phase) */
24
+ static fix curve; /* current road curvature */
25
+ static fix speed;
26
+ static int state, score, best;
27
+ static int rivals_z[3], rival_x[3];
28
+ static unsigned int prev_pad;
29
+
30
+ static void reset_race(void)
31
+ {
32
+ int i;
33
+ car_x = 0; scroll = 0; curve = 0; speed = FIXF(0.4f); score = 0;
34
+ for (i = 0; i < 3; i++) { rivals_z[i] = 8 + i * 5; rival_x[i] = ((i * 97) % 5) - 2; }
35
+ }
36
+
37
+ /* a flat ground-plane quad at depth z0..z1, centered at lateral cx with half-width hw. */
38
+ static void road_quad(fix cx0, fix z0, fix cx1, fix z1, fix hw, unsigned int col)
39
+ {
40
+ Vec3 a, b, c, d;
41
+ a.x = cx0 - hw; a.y = FIXF(-2.0f); a.z = z0;
42
+ b.x = cx0 + hw; b.y = FIXF(-2.0f); b.z = z0;
43
+ c.x = cx1 + hw; c.y = FIXF(-2.0f); c.z = z1;
44
+ d.x = cx1 - hw; d.y = FIXF(-2.0f); d.z = z1;
45
+ /* ground plane: no back-face cull (a flat floor's winding is ambiguous). */
46
+ psx_quad3d_nc(a, b, c, d, col);
47
+ }
48
+
49
+ static void draw_cube_at(fix x, fix y, fix z, fix s, unsigned int col)
50
+ {
51
+ Vec3 v[8]; int i;
52
+ static const int sx[8]={-1,1,1,-1,-1,1,1,-1},sy[8]={-1,-1,1,1,-1,-1,1,1},sz[8]={-1,-1,-1,-1,1,1,1,1};
53
+ psx_model(x, y, z, 0);
54
+ for (i = 0; i < 8; i++){ v[i].x=sx[i]*s; v[i].y=sy[i]*s; v[i].z=sz[i]*s; }
55
+ psx_quad3d(v[0],v[1],v[2],v[3],col); psx_quad3d(v[5],v[4],v[7],v[6],col);
56
+ psx_quad3d(v[4],v[0],v[3],v[7],col); psx_quad3d(v[1],v[5],v[6],v[2],col);
57
+ psx_quad3d(v[4],v[5],v[1],v[0],col); psx_quad3d(v[3],v[2],v[6],v[7],col);
58
+ }
59
+
60
+ static void update(void)
61
+ {
62
+ unsigned int pad = psx_pad();
63
+ int i;
64
+ if (pad & PAD_LEFT) car_x -= FMUL(speed, FIXF(0.4f));
65
+ if (pad & PAD_RIGHT) car_x += FMUL(speed, FIXF(0.4f));
66
+ if (pad & PAD_UP) { speed += FIXF(0.01f); if (speed > FIX(1)) speed = FIX(1); }
67
+ if (pad & PAD_DOWN) { speed -= FIXF(0.03f); if (speed < FIXF(0.1f)) speed = FIXF(0.1f); }
68
+ if (car_x < FIX(-4)) car_x = FIX(-4); if (car_x > FIX(4)) car_x = FIX(4);
69
+
70
+ /* advance the world + wander the curve */
71
+ scroll += speed;
72
+ curve = psx_sin(scroll >> 2); /* the road bends with a sine */
73
+ curve = FMUL(curve, FIX(3));
74
+ score += F2I(speed << 2);
75
+
76
+ /* rivals roll toward us; collide = spin (reset speed + small score penalty) */
77
+ for (i = 0; i < 3; i++) {
78
+ rivals_z[i] -= F2I(speed << 1) + 1;
79
+ if (rivals_z[i] < 2) { rivals_z[i] = 30 + (psx_rand() % 12); rival_x[i] = (int)(psx_rand() % 5) - 2; }
80
+ if (rivals_z[i] < 4) {
81
+ fix rx = (fix)rival_x[i] << 16;
82
+ fix dx = rx - car_x; if (dx < 0) dx = -dx;
83
+ if (dx < FIX(1)) { speed = FIXF(0.15f); if (score > 30) score -= 30; }
84
+ }
85
+ }
86
+ }
87
+
88
+ static void render(void)
89
+ {
90
+ int s, i;
91
+ psx_clear(RGB(70, 130, 200)); /* sky */
92
+ /* far ground band */
93
+ psx_rect(0, 120, 320, 120, RGB(40, 90, 40));
94
+
95
+ /* draw road segments far -> near; centerline curves with depth */
96
+ for (s = SEGS - 1; s >= 0; s--) {
97
+ fix z0 = FIX(2 + s * 3);
98
+ fix z1 = FIX(2 + (s + 1) * 3);
99
+ /* centerline x grows with depth*curve for the bend */
100
+ fix cx0 = FMUL(curve, FMUL(z0, z0)) >> 6;
101
+ fix cx1 = FMUL(curve, FMUL(z1, z1)) >> 6;
102
+ unsigned int col = (s & 1) ? RGB(60, 60, 70) : RGB(80, 80, 90);
103
+ road_quad(cx0 - car_x, z0, cx1 - car_x, z1, FIX(3), col);
104
+ /* verges */
105
+ road_quad(cx0 - car_x - FIX(3), z0, cx1 - car_x - FIX(3), z1, FIXF(0.4f), RGB(220,220,220));
106
+ road_quad(cx0 - car_x + FIX(3), z0, cx1 - car_x + FIX(3), z1, FIXF(0.4f), RGB(220,60,60));
107
+ }
108
+ /* rivals on the road */
109
+ for (i = 0; i < 3; i++) if (rivals_z[i] >= 2 && rivals_z[i] < 40) {
110
+ fix z = (fix)rivals_z[i] << 16;
111
+ fix cx = FMUL(curve, FMUL(z, z)) >> 6;
112
+ draw_cube_at(((fix)rival_x[i] << 16) + cx - car_x, FIXF(-1.4f), z, FIXF(0.5f), RGB(230, 200, 40));
113
+ }
114
+ /* the player's car, near the camera */
115
+ draw_cube_at(0, FIXF(-1.4f), FIX(3), FIXF(0.6f), RGB(220, 40, 40));
116
+
117
+ psx_number(8, 6, (unsigned)score, RGB(255, 255, 255));
118
+ }
119
+
120
+ int main(void)
121
+ {
122
+ psx_init();
123
+ psx_srand(0x1337);
124
+ psx_camera(0, FIXF(-0.5f), FIX(-1), 0, FIXF(-0.18f)); /* low chase cam, looking down */
125
+ state = TITLE; prev_pad = 0;
126
+
127
+ for (;;) {
128
+ unsigned int pad = psx_pad();
129
+ if (state == TITLE) {
130
+ static fix t; t += FIX(3);
131
+ psx_clear(RGB(20, 20, 50));
132
+ draw_cube_at(0, 0, FIX(6), FIX(1), RGB(220, 40, 40));
133
+ if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_race(); state = RACE; }
134
+ } else if (state == RACE) {
135
+ update(); render();
136
+ if (score > 4000) { if (score > best) best = score; state = DONE; }
137
+ } else {
138
+ psx_clear(RGB(8, 30, 8));
139
+ psx_number(110, 100, (unsigned)score, RGB(120, 255, 120));
140
+ psx_number(120, 130, (unsigned)best, RGB(255, 255, 120));
141
+ if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_race(); state = RACE; }
142
+ }
143
+ prev_pad = pad;
144
+ psx_vsync();
145
+ }
146
+ return 0;
147
+ }
@@ -0,0 +1,192 @@
1
+ /*
2
+ * shmup/main.c — STARFALL: a 3D PlayStation vertical shooter.
3
+ *
4
+ * Idiomatic PS1 3D: the playfield recedes into the screen. You pilot a ship near
5
+ * the camera; enemy cubes fly IN from the far distance toward you, growing as the
6
+ * perspective projection scales them up. Shoot them with a stream of bullets that
7
+ * fire away into Z. Title -> play -> game-over state machine, score + lives,
8
+ * AABB-in-screen-space collision, an xorshift wave spawner, a parallax starfield.
9
+ *
10
+ * Build: build({ platform:"ps1", language:"c" }). Controls: d-pad/stick move,
11
+ * CROSS fires, START begins / restarts. Runs on the romdev pcsx_rearmed core.
12
+ *
13
+ * Pipeline: psx_camera fixes the eye; each entity is a small cube drawn via
14
+ * psx_model (translate to its world pos) + psx_quad3d faces. Depth sort is implicit
15
+ * (we draw far enemies first by iterating; the painter order is good enough for
16
+ * non-overlapping cubes). All math is 16.16 fixed point.
17
+ */
18
+ #include "psx.h"
19
+
20
+ #define MAX_E 8
21
+ #define MAX_B 6
22
+
23
+ enum { TITLE, PLAY, OVER };
24
+
25
+ typedef struct { int alive; fix x, y, z; } Ent;
26
+
27
+ static Ent enemy[MAX_E];
28
+ static Ent bullet[MAX_B];
29
+ static fix px, py; /* player position (x,y); z fixed near camera */
30
+ static int state, score, lives, hi;
31
+ static unsigned int prev_pad;
32
+
33
+ /* a unit cube's 6 faces, drawn at the current psx_model origin, scaled by `s`. */
34
+ static void draw_cube(fix s, unsigned int col)
35
+ {
36
+ Vec3 v[8];
37
+ int i;
38
+ static const int sx[8] = {-1, 1, 1,-1,-1, 1, 1,-1};
39
+ static const int sy[8] = {-1,-1, 1, 1,-1,-1, 1, 1};
40
+ static const int sz[8] = {-1,-1,-1,-1, 1, 1, 1, 1};
41
+ for (i = 0; i < 8; i++) { v[i].x = sx[i] * s; v[i].y = sy[i] * s; v[i].z = sz[i] * s; }
42
+ psx_quad3d(v[0],v[1],v[2],v[3], col);
43
+ psx_quad3d(v[5],v[4],v[7],v[6], col);
44
+ psx_quad3d(v[4],v[0],v[3],v[7], col);
45
+ psx_quad3d(v[1],v[5],v[6],v[2], col);
46
+ psx_quad3d(v[4],v[5],v[1],v[0], col);
47
+ psx_quad3d(v[3],v[2],v[6],v[7], col);
48
+ }
49
+
50
+ static void reset_game(void)
51
+ {
52
+ int i;
53
+ for (i = 0; i < MAX_E; i++) enemy[i].alive = 0;
54
+ for (i = 0; i < MAX_B; i++) bullet[i].alive = 0;
55
+ px = 0; py = FIXF(-1.5f);
56
+ score = 0; lives = 3;
57
+ }
58
+
59
+ static void spawn_enemy(void)
60
+ {
61
+ int i;
62
+ for (i = 0; i < MAX_E; i++) if (!enemy[i].alive) {
63
+ enemy[i].alive = 1;
64
+ enemy[i].x = (fix)((psx_rand() % 7) - 3) << 16; /* -3..3 */
65
+ enemy[i].y = (fix)((psx_rand() % 5) - 1) << 16; /* -1..3 */
66
+ enemy[i].z = FIX(40); /* far away */
67
+ return;
68
+ }
69
+ }
70
+
71
+ static void fire(void)
72
+ {
73
+ int i;
74
+ for (i = 0; i < MAX_B; i++) if (!bullet[i].alive) {
75
+ bullet[i].alive = 1; bullet[i].x = px; bullet[i].y = py; bullet[i].z = FIX(4);
76
+ return;
77
+ }
78
+ }
79
+
80
+ /* AABB overlap of two world points in the XY plane within `r`. */
81
+ static int hit(fix ax, fix ay, fix bx, fix by, fix r)
82
+ {
83
+ fix dx = ax - bx, dy = ay - by;
84
+ if (dx < 0) dx = -dx; if (dy < 0) dy = -dy;
85
+ return dx < r && dy < r;
86
+ }
87
+
88
+ static void update(void)
89
+ {
90
+ unsigned int pad = psx_pad();
91
+ int i, j;
92
+
93
+ /* move ship */
94
+ if (pad & PAD_LEFT) px -= FIXF(0.18f);
95
+ if (pad & PAD_RIGHT) px += FIXF(0.18f);
96
+ if (pad & PAD_UP) py += FIXF(0.14f);
97
+ if (pad & PAD_DOWN) py -= FIXF(0.14f);
98
+ if (px < FIX(-4)) px = FIX(-4); if (px > FIX(4)) px = FIX(4);
99
+ if (py < FIX(-3)) py = FIX(-3); if (py > FIX(2)) py = FIX(2);
100
+
101
+ /* fire on CROSS edge */
102
+ if ((pad & PAD_CROSS) && !(prev_pad & PAD_CROSS)) fire();
103
+
104
+ /* bullets fly into Z */
105
+ for (i = 0; i < MAX_B; i++) if (bullet[i].alive) {
106
+ bullet[i].z += FIX(2);
107
+ if (bullet[i].z > FIX(45)) bullet[i].alive = 0;
108
+ }
109
+
110
+ /* enemies approach */
111
+ if ((psx_rand() & 31) == 0) spawn_enemy();
112
+ for (i = 0; i < MAX_E; i++) if (enemy[i].alive) {
113
+ enemy[i].z -= FIXF(0.5f);
114
+ if (enemy[i].z < FIX(3)) { /* reached the ship plane */
115
+ enemy[i].alive = 0;
116
+ if (--lives <= 0) { if (score > hi) hi = score; state = OVER; }
117
+ }
118
+ /* bullet collisions */
119
+ for (j = 0; j < MAX_B; j++) if (bullet[j].alive) {
120
+ fix dz = enemy[i].z - bullet[j].z; if (dz < 0) dz = -dz;
121
+ if (dz < FIX(2) && hit(enemy[i].x, enemy[i].y, bullet[j].x, bullet[j].y, FIX(1))) {
122
+ enemy[i].alive = 0; bullet[j].alive = 0; score += 10;
123
+ }
124
+ }
125
+ }
126
+ prev_pad = pad;
127
+ }
128
+
129
+ static void render(void)
130
+ {
131
+ int i;
132
+ psx_clear(RGB(6, 8, 24));
133
+
134
+ /* starfield: a few far cubes as backdrop sparkle */
135
+ for (i = 0; i < 12; i++) {
136
+ fix sx = (fix)(((i * 53) % 9) - 4) << 16;
137
+ fix sy = (fix)(((i * 37) % 7) - 3) << 16;
138
+ psx_model(sx, sy, FIX(50), 0);
139
+ draw_cube(FIXF(0.15f), RGB(40, 40, 70));
140
+ }
141
+ /* enemies (draw far -> near for painter order) */
142
+ for (i = 0; i < MAX_E; i++) if (enemy[i].alive) {
143
+ psx_model(enemy[i].x, enemy[i].y, enemy[i].z, enemy[i].z << 2);
144
+ draw_cube(FIXF(0.7f), RGB(230, 60, 60));
145
+ }
146
+ /* bullets */
147
+ for (i = 0; i < MAX_B; i++) if (bullet[i].alive) {
148
+ psx_model(bullet[i].x, bullet[i].y, bullet[i].z, 0);
149
+ draw_cube(FIXF(0.18f), RGB(255, 240, 80));
150
+ }
151
+ /* player ship near the camera */
152
+ psx_model(px, py, FIX(3), 0);
153
+ draw_cube(FIXF(0.5f), RGB(80, 200, 255));
154
+
155
+ /* HUD: score + lives */
156
+ psx_number(8, 6, (unsigned)score, RGB(255, 255, 255));
157
+ for (i = 0; i < lives; i++) psx_rect(290 - i*10, 8, 6, 6, RGB(80, 200, 255));
158
+ }
159
+
160
+ int main(void)
161
+ {
162
+ psx_init();
163
+ psx_srand(0xC0FFEE);
164
+ psx_camera(0, 0, FIX(-2), 0, FIXF(-0.08f)); /* slight downward tilt over the field */
165
+ state = TITLE;
166
+ prev_pad = 0;
167
+
168
+ for (;;) {
169
+ unsigned int pad = psx_pad();
170
+ if (state == TITLE) {
171
+ psx_clear(RGB(10, 10, 40));
172
+ /* a spinning hero cube as the title art */
173
+ static fix t; t += FIX(2);
174
+ psx_model(0, 0, FIX(6), t);
175
+ draw_cube(FIX(1), RGB(80, 200, 255));
176
+ psx_number(120, 200, 0, RGB(255, 255, 255)); /* press start cue: shows "0" */
177
+ if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_game(); state = PLAY; }
178
+ prev_pad = pad;
179
+ } else if (state == PLAY) {
180
+ update();
181
+ render();
182
+ } else { /* OVER */
183
+ psx_clear(RGB(40, 8, 8));
184
+ psx_number(110, 100, (unsigned)score, RGB(255, 80, 80));
185
+ psx_number(120, 130, (unsigned)hi, RGB(255, 255, 120));
186
+ if ((pad & PAD_START) && !(prev_pad & PAD_START)) { reset_game(); state = PLAY; }
187
+ prev_pad = pad;
188
+ }
189
+ psx_vsync();
190
+ }
191
+ return 0;
192
+ }