Repo cleanup: reference footage to docs/reference/, ignore local artifacts

- Move the two N64 reference screenshots out of the repo root into
  docs/reference/ (n64-footage-2026-08-24.jpg, n64-footage-gun-barrel.png);
  HANDOFF.md now points there.
- Gitignore screenshots/ (session captures) and tools_pc/mupen64/ (local
  Mupen64 Plus checkout used for N64 reference footage; 8 MB of binaries).
- Commit tools_pc/mtxtest.c, the standalone fast3d matrix-convention
  harness that HANDOFF/D72.3 docs reference as kept-for-reference.
- Refresh PORTING.md: status was still 'scaffolding / building once
  implementation lands'; now reflects Phase 2 in progress and points at
  docs/HANDOFF.md for current session state.
This commit is contained in:
James Dansereau committed 2026-08-27 19:27:05 -04:00
1 parent d3b3fe53ce
commit 0010270f1b
5 files changed
+229 -13

No files matched your search

+3
View File
@@ -125,6 +125,9 @@ ge007.crash.log
.pi/
run_out.log
ppm/
screenshots/
# local Mupen64 Plus checkout (N64 reference footage); binaries, do not commit
tools_pc/mupen64/
# PC port run/scratch logs
run_*.log
+14 -13
View File
@@ -8,9 +8,10 @@ It follows the approach of the
[Perfect Dark PC port](https://github.com/perfect-dark-pc-port/perfect_dark),
which ports the same Rare "Indy" engine (a later revision).
> **Status: scaffolding.** The directory structure, build system, and port-layer
> interfaces are in place. The actual implementation is being brought in phase
> by phase. See below.
> **Status: Phase 2 (rendering) in progress.** The game boots on PC, plays the
> intro music, and renders the entire intro (logo → gun barrel → cast);
> the current blocker is first stage load (BG-file endianness, finding D69).
> Current session state and immediate tasks: **[docs/HANDOFF.md](docs/HANDOFF.md)**.
## Read this first
@@ -45,23 +46,23 @@ docs/PCPortResearch.md
The N64 build (`Makefile`, `tools/`, `rsp/`, etc.) is **untouched**.
## Building (once implementation lands)
## Building
```sh
./build-pc.sh ntsc-final # or pal-final / jpn-final
```
Then put your ROM in `./data/` and run the binary. (The ROM is not
distributed; you must own the game.)
Then put your ROM in `./data/` and run the binary from the repo root.
(The ROM is not distributed; you must own the game.)
## Phased plan (summary)
| Phase | Goal |
|-------|------|
| 0 | Scaffolding (this change) |
| 1 | Boot to a window: system/fs/rom/config/video + OS shims |
| 2 | Rendering: bring in fast3d, add `G_SETTEX`, port scheduler |
| 3 | Audio + input |
| 4 | Saves + polish (widescreen, mouse-look, FPS, config) |
| Phase | Goal | Status |
|-------|------|--------|
| 0 | Scaffolding: build system + port-layer interfaces | done |
| 1 (+1.5) | Boot to window / first frame: ROM map, OS shims, ABI reconciliation | done |
| 2 | Rendering: fast3d software RSP, asset conversion/fixups | **in progress** (full intro renders; stage load = D69) |
| 3 | Audio + input (SDL device, mixer, gamepad) | not started |
| 4 | Saves + polish (file-backed EEPROM, widescreen, config) | not started |
Details and risks: [docs/PCPortResearch.md §8–9](docs/PCPortResearch.md).
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

+212
View File
@@ -0,0 +1,212 @@
/* Standalone numerical test of the fast3d matrix pipeline conventions.
* Replicates: GE guPerspectiveF (swapped), guLookAtF, guRotateF, guMtxF2L
* packing, gfx_sp_matrix unpacking + MUL order, and the row-vector vertex
* transform in gfx_pc.cpp. Compares against the standard column-vector
* pipeline P_std * MV * v for two scenes: gun barrel (known-good render)
* and rareware logo (D72.3 off-screen bug).
*/
#include <math.h>
#include <stdio.h>
#include <stdint.h>
typedef float f32;
/* ---- GE libultra functions (verbatim math) ---- */
static void guMtxIdentF(float mf[4][4]) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++) mf[i][j] = (i == j);
}
/* GE's SWAPPED perspective: mf[2][3]=-1, mf[3][2]=2nf/(n-f) */
static void guPerspectiveF_GE(float mf[4][4], float fovy, float aspect, float near_, float far_) {
float cot;
guMtxIdentF(mf);
fovy *= 3.1415926f / 180.0f;
cot = cosf(fovy / 2) / sinf(fovy / 2);
mf[0][0] = cot / aspect;
mf[1][1] = cot;
mf[2][2] = (near_ + far_) / (near_ - far_);
mf[2][3] = -1;
mf[3][2] = (2 * near_ * far_) / (near_ - far_);
mf[3][3] = 0;
}
/* standard libultra perspective (for the "true" pipeline) */
static void guPerspectiveF_STD(float mf[4][4], float fovy, float aspect, float near_, float far_) {
float cot;
guMtxIdentF(mf);
fovy *= 3.1415926f / 180.0f;
cot = cosf(fovy / 2) / sinf(fovy / 2);
mf[0][0] = cot / aspect;
mf[1][1] = cot;
mf[2][2] = (near_ + far_) / (near_ - far_);
mf[2][3] = (2 * near_ * far_) / (near_ - far_);
mf[3][2] = -1;
mf[3][3] = 0;
}
static void guLookAtF(float mf[4][4], float xEye, float yEye, float zEye,
float xAt, float yAt, float zAt,
float xUp, float yUp, float zUp) {
float len, xLook, yLook, zLook, xRight, yRight, zRight;
guMtxIdentF(mf);
xLook = xAt - xEye; yLook = yAt - yEye; zLook = zAt - zEye;
len = -1.0f / sqrtf(xLook*xLook + yLook*yLook + zLook*zLook);
xLook *= len; yLook *= len; zLook *= len;
xRight = yUp * zLook - zUp * yLook;
yRight = zUp * xLook - xUp * zLook;
zRight = xUp * yLook - yUp * xLook;
len = 1.0f / sqrtf(xRight*xRight + yRight*yRight + zRight*zRight);
xRight *= len; yRight *= len; zRight *= len;
xUp = yLook * zRight - zLook * yRight;
yUp = zLook * xRight - xLook * zRight;
zUp = xLook * yRight - yLook * xRight;
len = 1.0f / sqrtf(xUp*xUp + yUp*yUp + zUp*zUp);
xUp *= len; yUp *= len; zUp *= len;
mf[0][0] = xRight; mf[1][0] = yRight; mf[2][0] = zRight;
mf[3][0] = -(xEye * xRight + yEye * yRight + zEye * zRight);
mf[0][1] = xUp; mf[1][1] = yUp; mf[2][1] = zUp;
mf[3][1] = -(xEye * xUp + yEye * yUp + zEye * zUp);
mf[0][2] = xLook; mf[1][2] = yLook; mf[2][2] = zLook;
mf[3][2] = -(xEye * xLook + yEye * yLook + zEye * zLook);
mf[0][3] = 0; mf[1][3] = 0; mf[2][3] = 0; mf[3][3] = 1;
}
static void guRotateF(float mf[4][4], float a, float x, float y, float z) {
static float dtor = 3.1415926f / 180.0f;
float sine, cosine, ab, bc, ca, t;
float l = sqrtf(x*x + y*y + z*z);
if (l != 0) { x /= l; y /= l; z /= l; }
a *= dtor;
sine = sinf(a); cosine = cosf(a);
t = (1 - cosine);
ab = x * y * t; bc = y * z * t; ca = z * x * t;
guMtxIdentF(mf);
t = x * x;
mf[0][0] = t + cosine * (1 - t);
mf[2][1] = bc - x * sine;
mf[1][2] = bc + x * sine;
t = y * y;
mf[1][1] = t + cosine * (1 - t);
mf[2][0] = ca + y * sine;
mf[0][2] = ca - y * sine;
t = z * z;
mf[2][2] = t + cosine * (1 - t);
mf[1][0] = ab - z * sine;
mf[0][1] = ab + z * sine;
}
/* ---- guMtxF2L pack / gfx_sp_matrix unpack (bit-exact path) ---- */
static void mtxf2l(const float mf[4][4], int32_t m[16]) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 2; j++) {
int e1 = (int)(mf[i][j*2] * 65536.0f);
int e2 = (int)(mf[i][j*2+1] * 65536.0f);
/* real guMtxF2L uses *(ai++)/*(af++): slot = i*2+j */
m[i*2 + j] = (e1 & 0xffff0000) | ((e2 >> 16) & 0xffff);
m[8 + i*2 + j] = ((e1 << 16) & 0xffff0000) | (e2 & 0xffff);
}
}
static void sp_matrix_unpack(const int32_t addr[16], float matrix[4][4]) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j += 2) {
int32_t int_part = addr[i * 2 + j / 2];
uint32_t frac_part = addr[8 + i * 2 + j / 2];
matrix[i][j] = (int32_t)((int_part & 0xffff0000) | (frac_part >> 16)) / 65536.0f;
matrix[i][j + 1] = (int32_t)((int_part << 16) | (frac_part & 0xffff)) / 65536.0f;
}
}
/* ---- gfx_pc.cpp conventions ---- */
static void mmul(float res[4][4], const float a[4][4], const float b[4][4]) {
float tmp[4][4];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
tmp[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j] + a[i][3]*b[3][j];
for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) res[i][j] = tmp[i][j];
}
/* row-vector vertex transform exactly as in gfx_sp_vertex */
static void xform_row(const float MP[4][4], const float v[3], float c[4]) {
c[0] = v[0]*MP[0][0] + v[1]*MP[1][0] + v[2]*MP[2][0] + MP[3][0];
c[1] = v[0]*MP[0][1] + v[1]*MP[1][1] + v[2]*MP[2][1] + MP[3][1];
c[2] = v[0]*MP[0][2] + v[1]*MP[1][2] + v[2]*MP[2][2] + MP[3][2];
c[3] = v[0]*MP[0][3] + v[1]*MP[1][3] + v[2]*MP[2][3] + MP[3][3];
}
/* standard column-vector transform c = A*B*v */
static void xform_col(const float A[4][4], const float B[4][4], const float v[3], float c[4]) {
float M[4][4];
mmul(M, A, B);
for (int i = 0; i < 4; i++)
c[i] = M[i][0]*v[0] + M[i][1]*v[1] + M[i][2]*v[2] + M[i][3];
}
static void report(const char *tag, const float c[4]) {
float xw = c[3] != 0 ? c[0]/c[3] : 1e9f;
float yw = c[3] != 0 ? c[1]/c[3] : 1e9f;
printf(" %-34s clip=(%12.1f,%12.1f,%12.1f,%12.1f) x/w=%8.4f y/w=%8.4f %s\n",
tag, c[0], c[1], c[2], c[3], xw, yw,
(fabsf(xw) <= 1 && fabsf(yw) <= 1) ? "ON-SCREEN" : "off-screen");
}
/* pack+unpack round-trip so we exercise the exact bit path */
static void rt(const float mf[4][4], float out[4][4]) {
int32_t m[16];
mtxf2l(mf, m);
sp_matrix_unpack(m, out);
}
int main(void) {
float Pge[4][4], Pstd[4][4], MVla[4][4], Rot[4][4];
float MPours[4][4], MPalt[4][4], Mtot[4][4];
float c[4];
printf("== SCENE 1: gun barrel (eye 1758.3,220,684.3 dir -0.97,0,0.24 up 0,1,0; fov46 n10 f10000) ==\n");
guPerspectiveF_GE(Pge, 46.0f, 320.0f/240.0f, 10.0f, 10000.0f);
guPerspectiveF_STD(Pstd, 46.0f, 320.0f/240.0f, 10.0f, 10000.0f);
guLookAtF(MVla, 1758.2957f, 220.0f, 684.28143f,
1758.2957f - 0.97f, 220.0f, 684.28143f + 0.24f, 0, 1, 0);
rt(Pge, Pge); rt(MVla, MVla);
float v[3] = { -57.3f, -20.0f, 16.3f }; /* sample point offset from target */
mmul(MPours, MVla, Pge); /* our MP for LOAD-only */
xform_row(MPours, v, c);
report("OURS row: v.MV.P_ge", c);
xform_col(Pstd, MVla, v, c);
report("TRUE col: P_std*MV*v", c);
printf("\n== SCENE 2: rareware logo (eye 0,0,880 at 0,0,879 up 0,1,0; fov60 n100 f5000; rotY -40deg) ==\n");
guPerspectiveF_GE(Pge, 60.0f, 320.0f/240.0f, 100.0f, 5000.0f);
guPerspectiveF_STD(Pstd, 60.0f, 320.0f/240.0f, 100.0f, 5000.0f);
guLookAtF(MVla, 0, 0, 880.0f, 0, 0, 879.0f, 0, 1, 0);
guRotateF(Rot, -40.0f, 0, 1, 0);
rt(Pge, Pge); rt(MVla, MVla); rt(Rot, Rot);
float lv[3] = { 55.0f, 61.0f, 15.0f }; /* verts4258[0].ob */
mmul(MPours, Rot, MVla); /* our code: new = matrix*old */
mmul(MPours, MPours, Pge);
xform_row(MPours, lv, c);
report("OURS MUL (Rot*MV) then P", c);
mmul(MPalt, MVla, Rot); /* alt: new = old*matrix */
mmul(MPalt, MPalt, Pge);
xform_row(MPalt, lv, c);
report("ALT MUL (MV*Rot) then P", c);
/* true standard pipeline: P_std * (MVla*Rot) * v [Rot applied first] */
mmul(Mtot, MVla, Rot);
xform_col(Pstd, Mtot, lv, c);
report("TRUE col: P_std*MV*Rot*v", c);
/* and the other hardware-MUL interpretation: P_std * (Rot*MV) * v */
mmul(Mtot, Rot, MVla);
xform_col(Pstd, Mtot, lv, c);
report("TRUE col: P_std*Rot*MV*v", c);
return 0;
}