mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
Merge origin/CTR-Netplay into integrate/game-codegen-hooks-into-rollback
Keep both psx_bss.h (BSS rings) and nd_intro_ot.h from CTR-Netplay. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+17
-2
@@ -19,6 +19,11 @@ git checkout 5759277b # "audit pass" — last C++-tree base we target
|
||||
patch -p1 < ../docs/beetle_wtrace_hook.patch
|
||||
patch -p1 < ../docs/beetle_sio_trace_hook.patch
|
||||
patch -p1 < ../docs/beetle_cdcmd_trace_hook.patch
|
||||
# cdcmd re-inserts the wtrace globals + typedefs — drop the duplicate block
|
||||
# in libretro.cpp / mednafen/psx/psx.h if the build errors on redefinition.
|
||||
# Also needed (not yet in docs/*.patch): rtrace/irq callbacks, guest-cycle
|
||||
# accumulator, PS_CDC::PSXRecomp_GetDecodeVolume, cdc/dma irq peeks. Apply
|
||||
# from a prior local beetle-psx tree or re-land those hooks by hand.
|
||||
```
|
||||
|
||||
`beetle_cdcmd_trace_hook.patch` adds a CD-command trace callback (fires per
|
||||
@@ -41,6 +46,16 @@ cd ../runtime
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPSX_RECOMP_UI=OFF -DPSX_DEBUG_TOOLS=ON
|
||||
ninja -C build psx-beetle
|
||||
|
||||
# Headless run (picks the next free port from 4380 if taken; watch stderr)
|
||||
xvfb-run -a ./runtime/build/psx-beetle ../bios/SCPH1001.BIN --disc <game.cue>
|
||||
# Run (SDL window; use software renderer if GL context is flaky).
|
||||
# Beetle looks for lowercase scph5501.bin in the BIOS directory (SHA1
|
||||
# 0555C6FA… — SCPH-5501). Symlink or copy next to SCPH1001.BIN.
|
||||
# Default debug port is 4380 (override with --port).
|
||||
SDL_RENDER_DRIVER=software ./runtime/build/psx-beetle ../bios/SCPH1001.BIN --disc <game.cue> --port 4380
|
||||
|
||||
# Headless (if xvfb is installed):
|
||||
# xvfb-run -a ./runtime/build/psx-beetle ../bios/SCPH1001.BIN --disc <game.cue>
|
||||
```
|
||||
|
||||
Wire smoke: `{"cmd":"ping"}` → `backend=beetle`. Screenshot via `screenshot`.
|
||||
`gpu_frame_dump` is a **native** (psx-runtime) command — Beetle does not
|
||||
expose GP0 OT dumps yet; compare visuals via `screenshot` / `vram_peek`.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/* nd_intro_ot.h — CTR Naughty Dog intro OT helpers (debug / accuracy shims). */
|
||||
#ifndef PSXRECOMP_ND_INTRO_OT_H
|
||||
#define PSXRECOMP_ND_INTRO_OT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Last WoodEmit batch OT base (model+228). Prefer digit-rain tags. */
|
||||
void psx_nd_note_wood_batch_ot(uint32_t base);
|
||||
void psx_nd_note_wood_batch_ot_tagged(uint32_t base, uint32_t tag);
|
||||
uint32_t psx_nd_wood_batch_ot(void);
|
||||
|
||||
/* Sibling-scene bump hint at s4+0xb4 (captured at NdIntroSiblingRtptEmit). */
|
||||
void psx_nd_note_sibling_ot_hint(uint32_t base);
|
||||
uint32_t psx_nd_sibling_ot_hint(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1282,3 +1282,46 @@ extern "C" int beetle_history_get_snapshot(int slot, uint32_t *out_addr, int *ou
|
||||
if (out_active) *out_active = s_beetle_snap_active[slot];
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- cyc_watch / exc_ring stubs ----
|
||||
* Full per-instruction sampling lives in a beetle-psx DebugMode CPUHook that
|
||||
* was never checked into docs/*.patch. GPU/OT oracle work only needs these
|
||||
* symbols to link; wire returns empty/unarmed until the hook patch lands. */
|
||||
static uint32_t s_cw_anchor_raw = 0, s_cw_anchor_phys = 0;
|
||||
static uint32_t s_cw_end_raw = 0, s_cw_end_phys = 0;
|
||||
static uint32_t s_cw_max_hits = 0, s_cw_hits = 0;
|
||||
static int s_cw_armed = 0;
|
||||
|
||||
extern "C" void beetle_cyc_watch_arm(uint32_t anchor_raw, uint32_t end_raw, int n) {
|
||||
s_cw_anchor_raw = anchor_raw;
|
||||
s_cw_anchor_phys = anchor_raw & 0x1FFFFFFFu;
|
||||
s_cw_end_raw = end_raw;
|
||||
s_cw_end_phys = end_raw & 0x1FFFFFFFu;
|
||||
s_cw_max_hits = (n > 0) ? (uint32_t)n : 16u;
|
||||
s_cw_hits = 0;
|
||||
s_cw_armed = 1;
|
||||
}
|
||||
extern "C" void beetle_cyc_watch_clear(void) {
|
||||
s_cw_armed = 0; s_cw_hits = 0;
|
||||
}
|
||||
extern "C" void beetle_cyc_watch_get_state(uint32_t *anchor_raw, uint32_t *anchor_phys,
|
||||
uint32_t *end_raw, uint32_t *end_phys,
|
||||
uint32_t *max_hits, uint32_t *hits, int *armed) {
|
||||
if (anchor_raw) *anchor_raw = s_cw_anchor_raw;
|
||||
if (anchor_phys) *anchor_phys = s_cw_anchor_phys;
|
||||
if (end_raw) *end_raw = s_cw_end_raw;
|
||||
if (end_phys) *end_phys = s_cw_end_phys;
|
||||
if (max_hits) *max_hits = s_cw_max_hits;
|
||||
if (hits) *hits = s_cw_hits;
|
||||
if (armed) *armed = s_cw_armed;
|
||||
}
|
||||
extern "C" int beetle_cyc_watch_get(uint32_t i, uint32_t *hit_index, uint32_t *pc,
|
||||
unsigned long long *cyc) {
|
||||
(void)i; (void)hit_index; (void)pc; (void)cyc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int retro_psxref_exc_ring_dump(char *out, int cap) {
|
||||
if (!out || cap < 32) return -1;
|
||||
return std::snprintf(out, (size_t)cap, "{\"ok\":true,\"entries\":[]}");
|
||||
}
|
||||
|
||||
+566
-2
@@ -17,6 +17,7 @@
|
||||
#include <time.h>
|
||||
#include "debug_server.h"
|
||||
#include "psx_bss.h"
|
||||
#include "nd_intro_ot.h"
|
||||
#include "latency_ring.h"
|
||||
#include "overlay_loader.h"
|
||||
#include "overlay_capture.h"
|
||||
@@ -2268,6 +2269,398 @@ static inline void cyc_watch_observe(uint32_t block_leader_phys)
|
||||
if (s_cyc_watch_hits >= s_cyc_watch_max_hits) s_cyc_watch_armed = 0;
|
||||
}
|
||||
|
||||
/* ---- pc_probe: multi-PC block-leader counters + reg samples (default-off) ----
|
||||
* Arm via TCP `pc_probe_arm` or env PSX_PC_PROBE / PSX_ND_INTRO_PROBE.
|
||||
* Samples $t0/$fp/$v0/frame at matched leaders. Disarmed = one branch.
|
||||
* nd_intro=2 also fills mode/depth/ot_base/ot_index from GP + game struct. */
|
||||
#define PC_PROBE_MAX_PCS 16
|
||||
#define PC_PROBE_SAMPLE_CAP 64
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
uint64_t count;
|
||||
uint32_t last_t0;
|
||||
uint32_t last_fp;
|
||||
uint32_t last_v0;
|
||||
uint32_t last_mode;
|
||||
uint32_t last_depth;
|
||||
uint32_t last_ot_base;
|
||||
uint32_t last_ot_index;
|
||||
uint32_t last_frame;
|
||||
uint32_t t0_zero;
|
||||
uint32_t t0_nonzero;
|
||||
} PcProbeSlot;
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
uint32_t frame;
|
||||
uint32_t t0;
|
||||
uint32_t fp;
|
||||
uint32_t v0;
|
||||
uint32_t mode;
|
||||
uint32_t depth;
|
||||
uint32_t ot_base;
|
||||
uint32_t ot_index;
|
||||
} PcProbeSample;
|
||||
static volatile int s_pc_probe_armed = 0;
|
||||
static int s_pc_probe_n = 0;
|
||||
static PcProbeSlot s_pc_probe[PC_PROBE_MAX_PCS];
|
||||
static PcProbeSample s_pc_probe_samples[PC_PROBE_SAMPLE_CAP];
|
||||
static uint32_t s_pc_probe_sample_n = 0;
|
||||
static uint32_t s_pc_probe_sample_max = 32;
|
||||
|
||||
static void pc_probe_clear_state(void)
|
||||
{
|
||||
s_pc_probe_armed = 0;
|
||||
s_pc_probe_n = 0;
|
||||
s_pc_probe_sample_n = 0;
|
||||
memset(s_pc_probe, 0, sizeof(s_pc_probe));
|
||||
memset(s_pc_probe_samples, 0, sizeof(s_pc_probe_samples));
|
||||
}
|
||||
|
||||
static int pc_probe_add_pc(uint32_t raw)
|
||||
{
|
||||
uint32_t phys = raw & 0x1FFFFFFFu;
|
||||
if (phys == 0) return 0;
|
||||
for (int i = 0; i < s_pc_probe_n; i++) {
|
||||
if (s_pc_probe[i].pc == phys) return 1;
|
||||
}
|
||||
if (s_pc_probe_n >= PC_PROBE_MAX_PCS) return 0;
|
||||
s_pc_probe[s_pc_probe_n].pc = phys;
|
||||
s_pc_probe_n++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Comma/space-separated hex list, e.g. "0x80044D10,0x80044E58". */
|
||||
static int pc_probe_parse_list(const char *list)
|
||||
{
|
||||
if (!list || !*list) return 0;
|
||||
char tmp[512];
|
||||
snprintf(tmp, sizeof(tmp), "%s", list);
|
||||
int added = 0;
|
||||
for (char *p = tmp, *tok; (tok = strtok(p, ", \t\n")) != NULL; p = NULL) {
|
||||
uint32_t raw = (uint32_t)strtoul(tok, NULL, 0);
|
||||
if (pc_probe_add_pc(raw)) added++;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
static void pc_probe_arm_nd_intro_defaults(void)
|
||||
{
|
||||
/* NdIntroMeshDraw post-RTPT funnel (block leaders with cyc_observe). */
|
||||
static const uint32_t k[] = {
|
||||
0x80044580u, /* after dispatch: $fp = v0 OT ptr */
|
||||
0x80044C80u, /* face clip start */
|
||||
0x80044CB0u, /* passed neg outcode */
|
||||
0x80044CDCu, /* passed hi outcode → alloc */
|
||||
0x80044D10u, /* $t0 == 0 ? skip : emit */
|
||||
0x80044D18u, /* emit continue */
|
||||
0x80044DA4u, /* PolyG4 build */
|
||||
0x80044E58u, /* clip skip */
|
||||
0x80044EC8u, /* $t0==0 / OOM skip */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* OT/depth leafs of NdIntroDrawDispatch — which path returns the OT slot. */
|
||||
static void pc_probe_arm_nd_intro_ot_defaults(void)
|
||||
{
|
||||
static const uint32_t k[] = {
|
||||
0x80044580u, /* after jal: v0 → $fp OT slot */
|
||||
0x800440A0u, /* dispatch entry (mode half @ gp+0x4DE) */
|
||||
0x80044120u, /* mode0 depth scale using *(game+0x1D04) */
|
||||
0x80044154u, /* return OT base via *(game+buf*4+0x18C8) */
|
||||
0x800441A8u, /* return *(game+0x25C)+0xFFC (near-ish) */
|
||||
0x800441C0u, /* mode2 entry */
|
||||
0x80044268u, /* mode1/ shared OT-base return */
|
||||
0x80044DA4u, /* PolyG4 emit: confirm $fp sticky */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* Textured wood funnel — live path is func_8006A52C (not dead twin 0x8006A6B8). */
|
||||
static void pc_probe_arm_nd_intro_wood_defaults(void)
|
||||
{
|
||||
static const uint32_t k[] = {
|
||||
0x8006A52Cu, /* NdIntroWoodEmit entry */
|
||||
0x8006A564u, /* RTPT GTE caller_ra (sticky) */
|
||||
0x8006A57Cu, /* post-RTPT face loop */
|
||||
0x8006A590u, /* FLAG read / face setup */
|
||||
0x8006A5B4u, /* NCLIP */
|
||||
0x8006A610u, /* next-face hub (skip + post-emit) */
|
||||
0x8006A69Cu, /* RTPS single-vert return (bgez $0 → loop) */
|
||||
0x8006A6B8u, /* dead twin entry — expect count=0 */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* Depth compare: wood OTZ vs digit/glow OT link ptrs vs MeshDraw OtFar.
|
||||
* Only block-leader PCs (cyc_observe); mid-block AddPrim sites never fire. */
|
||||
static void pc_probe_arm_nd_intro_depth_defaults(void)
|
||||
{
|
||||
static const uint32_t k[] = {
|
||||
0x8006A608u, /* wood jalr emit — GTE OTZ */
|
||||
0x80023094u, /* DigitGt4 entry — $t7 already OT slot */
|
||||
0x80053E10u, /* glow color ori — $a2=prim; OT via game+0x147C later */
|
||||
0x800440A0u, /* DrawDispatch entry — GP mode/depth */
|
||||
0x80044580u, /* MeshDraw after dispatch */
|
||||
0x800441A8u, /* OtFar leaf */
|
||||
0x80052F98u, /* DigitFx entry */
|
||||
0x800444ECu, /* MeshDraw entry — sample $ra */
|
||||
0x80044DA4u, /* PolyG4 emit hub — lighting bytes on stack */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* Wood DL / helper selection: batch setup + which emit helper is bound. */
|
||||
static void pc_probe_arm_nd_intro_wood_dl_defaults(void)
|
||||
{
|
||||
static const uint32_t k[] = {
|
||||
0x8006AAF0u, /* wood batch setup: a0=stream, ra=material desc */
|
||||
0x8006AB58u, /* fallthrough past flag gates (accepted batch) */
|
||||
0x8006ACE0u, /* after lw s5,96(ra) — helper bound */
|
||||
0x8006A52Cu, /* wood emit entry */
|
||||
0x8006A608u, /* wood jalr emit — also GTE OTZ/SXY via wood_ot */
|
||||
0x8006AE90u, /* alt helper — expect 0 in ND */
|
||||
0x8006AD20u, /* wood batch epilogue / next-opcode hub */
|
||||
0x80044580u, /* MeshDraw post-RTPT — compare SX band */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* Resolve double-buffered OT base: *(*(0x8008D2AC) + 0x18C8 + (*(+0xC)<<2)). */
|
||||
static void pc_probe_ot_context(CPUState *cpu, uint32_t v0_ot,
|
||||
uint32_t *mode_out, uint32_t *depth_out,
|
||||
uint32_t *ot_base_out, uint32_t *ot_index_out)
|
||||
{
|
||||
uint32_t mode = 0, depth = 0, ot_base = 0, ot_index = 0xFFFFFFFFu;
|
||||
if (cpu) {
|
||||
uint32_t gp = cpu->gpr[28];
|
||||
mode = psx_read_word(gp + 0x4D4u);
|
||||
/* Depth countdown half lives at gp+0x4D8 (signed). */
|
||||
depth = (uint32_t)(int32_t)(int16_t)(psx_read_word(gp + 0x4D8u) & 0xFFFFu);
|
||||
uint32_t game = psx_read_word(0x8008D2ACu);
|
||||
if (game) {
|
||||
uint32_t buf = psx_read_word(game + 0xCu);
|
||||
ot_base = psx_read_word(game + (buf << 2) + 0x18C8u);
|
||||
if (ot_base && v0_ot) {
|
||||
int32_t delta = (int32_t)(v0_ot - ot_base);
|
||||
if ((delta & 3) == 0 && delta >= 0 && delta < (1 << 20))
|
||||
ot_index = (uint32_t)(delta >> 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode_out) *mode_out = mode;
|
||||
if (depth_out) *depth_out = depth;
|
||||
if (ot_base_out) *ot_base_out = ot_base;
|
||||
if (ot_index_out) *ot_index_out = ot_index;
|
||||
}
|
||||
|
||||
static inline void pc_probe_observe(uint32_t block_leader_phys)
|
||||
{
|
||||
if (!s_pc_probe_armed || s_pc_probe_n <= 0) return;
|
||||
extern CPUState *debug_cpu_ptr;
|
||||
CPUState *cpu = debug_cpu_ptr;
|
||||
for (int i = 0; i < s_pc_probe_n; i++) {
|
||||
if (s_pc_probe[i].pc != block_leader_phys) continue;
|
||||
PcProbeSlot *s = &s_pc_probe[i];
|
||||
s->count++;
|
||||
if (cpu) {
|
||||
uint32_t t0 = cpu->gpr[8];
|
||||
uint32_t fp = cpu->gpr[30];
|
||||
uint32_t v0 = cpu->gpr[2];
|
||||
uint32_t a1 = cpu->gpr[5];
|
||||
uint32_t a2 = cpu->gpr[6];
|
||||
uint32_t t7 = cpu->gpr[15];
|
||||
uint32_t otz = cpu->gte_data[7] & 0xFFFFu;
|
||||
uint32_t mode = 0, depth = 0, ot_base = 0, ot_index = 0xFFFFFFFFu;
|
||||
/* Wood: a2 is stack-table value (often prim-ish); OT slot ≈ ot_base+OTZ.
|
||||
* Digit GT4 AddPrim @23180: $t7 = OT slot. Glow @53EB8: $a1 = OT head. */
|
||||
int wood_ot = (block_leader_phys == 0x0006A608u ||
|
||||
block_leader_phys == 0x0006A600u);
|
||||
int digit_ot = (block_leader_phys == 0x00023094u);
|
||||
int glow_ot = (block_leader_phys == 0x00053E10u);
|
||||
int digit_rain_ot = (block_leader_phys == 0x0006AE34u);
|
||||
int dispatch = (block_leader_phys == 0x000440A0u);
|
||||
uint32_t ot_ptr = 0;
|
||||
if (digit_ot)
|
||||
ot_ptr = t7; /* caller-supplied OT slot */
|
||||
else if (digit_rain_ot)
|
||||
ot_ptr = cpu->gpr[11]; /* $t3 OT slot from MAC0>>17 helper */
|
||||
else if (glow_ot) {
|
||||
/* OT head at game+0x147C (same as AddPrim a few insns later). */
|
||||
uint32_t game = psx_read_word(0x8008D2ACu);
|
||||
ot_ptr = game ? psx_read_word(game + 0x147Cu) : 0;
|
||||
} else if (block_leader_phys == 0x00044580u ||
|
||||
block_leader_phys == 0x000441A8u)
|
||||
ot_ptr = v0; /* dispatch result / OtFar v0 */
|
||||
else if (wood_ot)
|
||||
ot_ptr = 0; /* resolve via OTZ after ot_base known */
|
||||
else
|
||||
ot_ptr = fp ? fp : v0;
|
||||
pc_probe_ot_context(cpu, ot_ptr, &mode, &depth, &ot_base, &ot_index);
|
||||
if (wood_ot) {
|
||||
/* WoodEmit jalr $s5 @0x8006A608: MAC0 already swc2'd to 44($at);
|
||||
* OT base at 56($at) from model+228. Index = MAC0>>17 (== OTZ>>5). */
|
||||
uint32_t at = cpu->gpr[1];
|
||||
uint32_t mac0 = at ? psx_read_word(at + 44u) : 0;
|
||||
uint32_t wood_base = at ? psx_read_word(at + 56u) : 0;
|
||||
uint32_t widx = mac0 >> 17;
|
||||
if (!widx && otz)
|
||||
widx = otz >> 5;
|
||||
ot_base = wood_base;
|
||||
ot_index = widx;
|
||||
ot_ptr = wood_base ? (wood_base + (widx << 2)) : 0;
|
||||
t0 = ot_ptr; /* actual AddPrim OT slot */
|
||||
depth = otz;
|
||||
v0 = cpu->gpr[21]; /* $s5 helper */
|
||||
mode = wood_base;
|
||||
if (wood_base)
|
||||
psx_nd_note_wood_batch_ot(wood_base);
|
||||
} else if (digit_ot) {
|
||||
t0 = t7;
|
||||
v0 = ot_index;
|
||||
} else if (glow_ot) {
|
||||
t0 = ot_ptr;
|
||||
v0 = ot_index;
|
||||
depth = a2; /* prim ptr */
|
||||
} else if (dispatch) {
|
||||
/* Fresh GP mode/depth before leaf runs; v0 not OT yet. */
|
||||
t0 = mode;
|
||||
v0 = depth;
|
||||
} else if (block_leader_phys == 0x000444ECu) {
|
||||
/* MeshDraw entry: expose caller $ra (no jal-site in EXE). */
|
||||
t0 = cpu->gpr[31];
|
||||
v0 = cpu->gpr[2]; /* entry gate */
|
||||
} else if (block_leader_phys == 0x00044DA4u) {
|
||||
/* PolyG4 emit: sp+0x40/0x41 hold computed shade bytes. */
|
||||
uint32_t sp = cpu->gpr[29];
|
||||
t0 = psx_read_word(sp + 0x40u) & 0xFFFFu;
|
||||
v0 = fp; /* OT link ptr */
|
||||
} else if (block_leader_phys == 0x0006A52Cu) {
|
||||
/* Wood emit entry: $t9=face list, $at=ctx, $s5=emit helper. */
|
||||
t0 = cpu->gpr[25]; /* t9 */
|
||||
v0 = cpu->gpr[1]; /* at */
|
||||
depth = cpu->gpr[21]; /* s5 */
|
||||
} else if (block_leader_phys == 0x0006ACE0u) {
|
||||
/* Post helper-select: $s5 set, $a2=model.
|
||||
* ra may be clobbered by pre-emit jalr; trust s5 + model. */
|
||||
t0 = cpu->gpr[21]; /* s5 helper */
|
||||
v0 = cpu->gpr[6]; /* a2 model */
|
||||
if (v0) {
|
||||
mode = psx_read_word(v0 + 184u); /* flags */
|
||||
/* face-list ptr @a2+200; depth = first face word */
|
||||
uint32_t faces = psx_read_word(v0 + 200u);
|
||||
depth = faces ? psx_read_word(faces) : 0;
|
||||
ot_base = faces;
|
||||
ot_index = faces ? psx_read_word(faces + 4u) : 0;
|
||||
}
|
||||
} else if (block_leader_phys == 0x0006AAF0u) {
|
||||
/* Wood batch setup: $a0 stream, $ra = model (helpers at +92).
|
||||
* mode=flags@+184, depth=helper@+96, ot_base=name@+8,
|
||||
* ot_index=batch OT @+228 (live; model RAM is reused later). */
|
||||
t0 = cpu->gpr[4]; /* a0 */
|
||||
v0 = cpu->gpr[31]; /* model */
|
||||
if (v0) {
|
||||
mode = psx_read_word(v0 + 184u); /* draw flags */
|
||||
depth = psx_read_word(v0 + 96u); /* s5 helper */
|
||||
ot_base = psx_read_word(v0 + 8u); /* name/tag */
|
||||
ot_index = psx_read_word(v0 + 228u); /* batch OT */
|
||||
if (ot_index)
|
||||
psx_nd_note_wood_batch_ot_tagged(ot_index, ot_base);
|
||||
}
|
||||
} else if (block_leader_phys == 0x0006AB58u) {
|
||||
/* Fallthrough past flag bne — batch accepted. */
|
||||
t0 = cpu->gpr[6]; /* a2 */
|
||||
v0 = t0 ? psx_read_word(t0 + 184u) : 0;
|
||||
depth = cpu->gpr[31]; /* desc */
|
||||
} else if (block_leader_phys == 0x00044580u) {
|
||||
/* MeshDraw post-dispatch: also pack GTE max SX into mode. */
|
||||
{
|
||||
int32_t sx_max = -0x8000;
|
||||
for (int si = 12; si <= 14; si++) {
|
||||
int32_t sx = (int32_t)(int16_t)(cpu->gte_data[si] & 0xFFFFu);
|
||||
if (sx > sx_max) sx_max = sx;
|
||||
}
|
||||
mode = (uint32_t)sx_max;
|
||||
}
|
||||
} else if (block_leader_phys == 0x000444E8u) {
|
||||
/* True MeshDraw entry: lh gp+0x4DC → v0 gate (observe pre-lh). */
|
||||
t0 = cpu->gpr[28]; /* gp */
|
||||
v0 = cpu->gpr[2];
|
||||
} else if (block_leader_phys == 0x00069BB0u) {
|
||||
/* Sibling matrix/OT setup entry: a1=ctx (s4+360), a0=mesh.
|
||||
* ot_index = preferred wood batch OT cache.
|
||||
* mode = CODE model+228, depth = GLOW model+228 when those
|
||||
* ND intro models are resident (stable addrs from batch stream). */
|
||||
t0 = cpu->gpr[5]; /* a1 */
|
||||
v0 = cpu->gpr[4]; /* a0 */
|
||||
if (t0) {
|
||||
uint32_t main_ot = psx_read_word(t0 + 244u);
|
||||
ot_base = main_ot;
|
||||
/* s4+0xb4 = scene OT bump near WoodEmit batch pools. */
|
||||
if (t0 > 0xB4u)
|
||||
psx_nd_note_sibling_ot_hint(psx_read_word(t0 - 0xB4u));
|
||||
}
|
||||
ot_index = psx_nd_wood_batch_ot();
|
||||
{
|
||||
/* ND intro digit/glow models — confirmed stable during rain. */
|
||||
const uint32_t code_m = 0x800FF390u;
|
||||
const uint32_t glow_m = 0x800FF294u;
|
||||
uint32_t code_tag = psx_read_word(code_m + 8u);
|
||||
uint32_t glow_tag = psx_read_word(glow_m + 8u);
|
||||
mode = (code_tag == 0x45444F43u) ? psx_read_word(code_m + 228u) : 0;
|
||||
depth = (glow_tag == 0x574F4C47u) ? psx_read_word(glow_m + 228u) : 0;
|
||||
if (mode)
|
||||
psx_nd_note_wood_batch_ot_tagged(mode, 0x45444F43u);
|
||||
else if (depth)
|
||||
psx_nd_note_wood_batch_ot_tagged(depth, 0x574F4C47u);
|
||||
}
|
||||
} else if (block_leader_phys == 0x00069C34u ||
|
||||
block_leader_phys == 0x00069CC4u) {
|
||||
/* Face-loop jal / entry: a3 = OT origin (last slot). */
|
||||
t0 = cpu->gpr[7]; /* a3 */
|
||||
v0 = cpu->gpr[5]; /* a1 */
|
||||
mode = cpu->gpr[4]; /* a0 mesh */
|
||||
depth = t0;
|
||||
ot_base = (t0 >= 4092u) ? (t0 - 4092u) : 0;
|
||||
} else if (digit_rain_ot) {
|
||||
/* NdIntroDigitRainCode36: $t3 = OT slot; expose vs game ot_base. */
|
||||
t0 = ot_ptr;
|
||||
v0 = ot_index;
|
||||
depth = otz;
|
||||
}
|
||||
s->last_t0 = t0;
|
||||
s->last_fp = fp;
|
||||
s->last_v0 = v0;
|
||||
s->last_mode = mode;
|
||||
s->last_depth = depth;
|
||||
s->last_ot_base = ot_base;
|
||||
s->last_ot_index = ot_index;
|
||||
s->last_frame = (uint32_t)s_frame_count;
|
||||
if (t0 == 0) s->t0_zero++;
|
||||
else s->t0_nonzero++;
|
||||
if (s_pc_probe_sample_n < s_pc_probe_sample_max &&
|
||||
s_pc_probe_sample_n < PC_PROBE_SAMPLE_CAP) {
|
||||
PcProbeSample *sm = &s_pc_probe_samples[s_pc_probe_sample_n++];
|
||||
sm->pc = block_leader_phys;
|
||||
sm->frame = (uint32_t)s_frame_count;
|
||||
sm->t0 = t0;
|
||||
sm->fp = fp;
|
||||
sm->v0 = v0;
|
||||
sm->mode = mode;
|
||||
sm->depth = depth;
|
||||
sm->ot_base = ot_base;
|
||||
sm->ot_index = ot_index;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Exported per-basic-block-leader cycle observer. Emitted by the recompiler at
|
||||
* EVERY compiled block leader (under #ifndef PSX_NO_DEBUG_TOOLS, so prod builds
|
||||
* emit nothing — zero overhead) so native's cycle observation matches Beetle's
|
||||
@@ -2281,11 +2674,81 @@ void debug_server_cyc_observe(uint32_t block_leader_phys) {
|
||||
return;
|
||||
#else
|
||||
if (s_fmv_quiet) return;
|
||||
cyc_watch_observe(block_leader_phys & 0x1FFFFFFFu);
|
||||
uint32_t phys = block_leader_phys & 0x1FFFFFFFu;
|
||||
/* ND debug: PSX_ND_WOOD_CLEAR80=1 clears model+184 bit0x80 at the flag-load
|
||||
* leader so 0x5CF/'cras' models enter NdIntroWoodBatchSetup's textured path.
|
||||
* Fires before lw v1,184(a2) @ AB3C. Default-off. */
|
||||
if (phys == 0x0006AB3Cu) {
|
||||
static int s_clear80 = -1;
|
||||
if (s_clear80 < 0) {
|
||||
const char *e = getenv("PSX_ND_WOOD_CLEAR80");
|
||||
s_clear80 = (e && *e && *e != '0') ? 1 : 0;
|
||||
if (s_clear80)
|
||||
fprintf(stdout, "psxrecomp: PSX_ND_WOOD_CLEAR80 enabled\n");
|
||||
}
|
||||
if (s_clear80 && debug_cpu_ptr) {
|
||||
uint32_t a2 = debug_cpu_ptr->gpr[6];
|
||||
if (a2) {
|
||||
uint32_t fl = psx_read_word(a2 + 184u);
|
||||
if (fl & 0x80u)
|
||||
psx_write_word(a2 + 184u, fl & ~0x80u);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Cache WoodEmit batch OT at WoodBatchSetup (runs BEFORE sibling on ND
|
||||
* rain frames) and at scratch load. */
|
||||
if (phys == 0x0006AAF0u && debug_cpu_ptr) {
|
||||
uint32_t model = debug_cpu_ptr->gpr[31]; /* $ra = model */
|
||||
if (model) {
|
||||
uint32_t tag = psx_read_word(model + 8u);
|
||||
uint32_t base = psx_read_word(model + 228u);
|
||||
if (base)
|
||||
psx_nd_note_wood_batch_ot_tagged(base, tag);
|
||||
}
|
||||
}
|
||||
if ((phys == 0x0006ACFCu || phys == 0x0006AD08u) && debug_cpu_ptr) {
|
||||
uint32_t base = 0;
|
||||
if (phys == 0x0006ACFCu) {
|
||||
uint32_t a2 = debug_cpu_ptr->gpr[6];
|
||||
if (a2)
|
||||
base = psx_read_word(a2 + 228u);
|
||||
} else {
|
||||
base = debug_cpu_ptr->gpr[5]; /* a1 after lw model+228 */
|
||||
}
|
||||
if (base)
|
||||
psx_nd_note_wood_batch_ot(base);
|
||||
}
|
||||
/* ND debug: PSX_ND_SIB_OT_LIFT=<n> adds n*4 to $a3 at sibling face-loop
|
||||
* entry 0x80069CC4 (a3 = *(a1+244)+4092 = last OT slot). Sibling PolyFT3
|
||||
* right-flap faces use face_hi≈0 → farthest bucket (drawn before additive
|
||||
* 0x36 glow). Negative n (e.g. -800) moves them nearer in the 1024-entry OT.
|
||||
* Applied once per a3 value. */
|
||||
if (phys == 0x00069CC4u && debug_cpu_ptr) {
|
||||
static int s_sib_lift = -2; /* -2 unset, 0 disabled, else delta */
|
||||
static uint32_t s_sib_lifted_a3 = 0;
|
||||
if (s_sib_lift == -2) {
|
||||
const char *e = getenv("PSX_ND_SIB_OT_LIFT");
|
||||
if (e && *e) {
|
||||
s_sib_lift = atoi(e);
|
||||
fprintf(stdout, "psxrecomp: PSX_ND_SIB_OT_LIFT=%d\n", s_sib_lift);
|
||||
} else {
|
||||
s_sib_lift = 0;
|
||||
}
|
||||
}
|
||||
if (s_sib_lift != 0) {
|
||||
uint32_t a3 = debug_cpu_ptr->gpr[7];
|
||||
if (a3 && a3 != s_sib_lifted_a3) {
|
||||
debug_cpu_ptr->gpr[7] = a3 + (uint32_t)(int32_t)(s_sib_lift * 4);
|
||||
s_sib_lifted_a3 = debug_cpu_ptr->gpr[7];
|
||||
}
|
||||
}
|
||||
}
|
||||
cyc_watch_observe(phys);
|
||||
pc_probe_observe(phys);
|
||||
/* #2 lockstep comparator: per-basic-block compiled-vs-interp check. Self-gates
|
||||
* on the armed frame window; ~free (one branch) when disarmed. */
|
||||
{ extern void ls_at_leader(uint32_t, CPUState*); extern CPUState *debug_cpu_ptr;
|
||||
ls_at_leader(block_leader_phys & 0x1FFFFFFFu, debug_cpu_ptr); }
|
||||
ls_at_leader(phys, debug_cpu_ptr); }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -9035,6 +9498,81 @@ static void handle_cyc_watch_clear(int id, const char *json)
|
||||
send_ok(id);
|
||||
}
|
||||
|
||||
/* pc_probe_arm — {"pcs":"0xA,0xB", "n":32, "nd_intro":1..5} or empty pcs+nd_intro.
|
||||
* 1=PolyG4 clip 2=OT leaves 3=wood 4=depth 5=wood DL/helper bind. */
|
||||
static void handle_pc_probe_arm(int id, const char *json)
|
||||
{
|
||||
pc_probe_clear_state();
|
||||
int n = json_get_int(json, "n", 32);
|
||||
if (n < 1) n = 1;
|
||||
if (n > PC_PROBE_SAMPLE_CAP) n = PC_PROBE_SAMPLE_CAP;
|
||||
s_pc_probe_sample_max = (uint32_t)n;
|
||||
|
||||
int nd = json_get_int(json, "nd_intro", 0);
|
||||
char pcs[512];
|
||||
const char *have_pcs = json_get_str(json, "pcs", pcs, sizeof(pcs));
|
||||
if (nd == 5) pc_probe_arm_nd_intro_wood_dl_defaults();
|
||||
else if (nd == 4) pc_probe_arm_nd_intro_depth_defaults();
|
||||
else if (nd == 3) pc_probe_arm_nd_intro_wood_defaults();
|
||||
else if (nd == 2) pc_probe_arm_nd_intro_ot_defaults();
|
||||
else if (nd) pc_probe_arm_nd_intro_defaults();
|
||||
if (have_pcs) pc_probe_parse_list(pcs);
|
||||
if (s_pc_probe_n <= 0) {
|
||||
send_err(id, "pc_probe_arm needs pcs=... and/or nd_intro=1..5");
|
||||
return;
|
||||
}
|
||||
s_pc_probe_armed = 1;
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"armed\":1,\"n_pcs\":%d,\"sample_max\":%u,\"nd_intro\":%d}",
|
||||
id, s_pc_probe_n, s_pc_probe_sample_max, nd);
|
||||
}
|
||||
|
||||
static void handle_pc_probe_dump(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"{\"id\":%d,\"ok\":true,\"armed\":%d,\"n_pcs\":%d,\"sample_max\":%u,"
|
||||
"\"samples_n\":%u,\"slots\":[",
|
||||
id, s_pc_probe_armed ? 1 : 0, s_pc_probe_n, s_pc_probe_sample_max,
|
||||
s_pc_probe_sample_n);
|
||||
send_line(buf);
|
||||
for (int i = 0; i < s_pc_probe_n; i++) {
|
||||
const PcProbeSlot *s = &s_pc_probe[i];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%s{\"pc\":\"0x%08X\",\"count\":%llu,\"t0_zero\":%u,\"t0_nonzero\":%u,"
|
||||
"\"last_t0\":\"0x%08X\",\"last_fp\":\"0x%08X\",\"last_v0\":\"0x%08X\","
|
||||
"\"last_mode\":\"0x%08X\",\"last_depth\":\"0x%08X\","
|
||||
"\"last_ot_base\":\"0x%08X\",\"last_ot_index\":\"0x%08X\","
|
||||
"\"last_frame\":%u}",
|
||||
(i == 0) ? "" : ",",
|
||||
s->pc, (unsigned long long)s->count, s->t0_zero, s->t0_nonzero,
|
||||
s->last_t0, s->last_fp, s->last_v0,
|
||||
s->last_mode, s->last_depth, s->last_ot_base, s->last_ot_index,
|
||||
s->last_frame);
|
||||
send_line(buf);
|
||||
}
|
||||
send_line("],\"samples\":[");
|
||||
for (uint32_t i = 0; i < s_pc_probe_sample_n; i++) {
|
||||
const PcProbeSample *sm = &s_pc_probe_samples[i];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%s{\"pc\":\"0x%08X\",\"frame\":%u,\"t0\":\"0x%08X\","
|
||||
"\"fp\":\"0x%08X\",\"v0\":\"0x%08X\",\"mode\":\"0x%08X\","
|
||||
"\"depth\":\"0x%08X\",\"ot_base\":\"0x%08X\",\"ot_index\":\"0x%08X\"}",
|
||||
(i == 0) ? "" : ",",
|
||||
sm->pc, sm->frame, sm->t0, sm->fp, sm->v0,
|
||||
sm->mode, sm->depth, sm->ot_base, sm->ot_index);
|
||||
send_line(buf);
|
||||
}
|
||||
send_line("]}");
|
||||
}
|
||||
|
||||
static void handle_pc_probe_clear(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
pc_probe_clear_state();
|
||||
send_ok(id);
|
||||
}
|
||||
|
||||
/* Liveness/freeze diagnostic: returns a single snapshot of every counter
|
||||
* that distinguishes "stuck in a tight handler loop" from "just slow" or
|
||||
* "starved on TCP poll". Pass {"window":N} (default 256) to also include
|
||||
@@ -12735,6 +13273,9 @@ static const CmdEntry s_commands[] = {
|
||||
{ "cyc_watch", handle_cyc_watch },
|
||||
{ "cyc_watch_dump", handle_cyc_watch_dump },
|
||||
{ "cyc_watch_clear", handle_cyc_watch_clear },
|
||||
{ "pc_probe_arm", handle_pc_probe_arm },
|
||||
{ "pc_probe_dump", handle_pc_probe_dump },
|
||||
{ "pc_probe_clear", handle_pc_probe_clear },
|
||||
{ "mmio_dump", handle_mmio_dump },
|
||||
{ "mmio_clear", handle_mmio_clear },
|
||||
{ "capture_freeze", handle_capture_freeze },
|
||||
@@ -12934,6 +13475,29 @@ void debug_server_init(int port)
|
||||
if (s_rwatch_hi > s_rwatch_lo) g_ram_read_watch_active = 1;
|
||||
}
|
||||
}
|
||||
/* PSX_ND_INTRO_PROBE=1 PolyG4; =2 OT; =3 wood; =4 depth; =5 wood DL.
|
||||
* PSX_PC_PROBE="0xA,0xB" arms an explicit list (can combine). */
|
||||
{
|
||||
const char *nd = getenv("PSX_ND_INTRO_PROBE");
|
||||
const char *pcs = getenv("PSX_PC_PROBE");
|
||||
if ((nd && *nd && *nd != '0') || (pcs && *pcs)) {
|
||||
pc_probe_clear_state();
|
||||
s_pc_probe_sample_max = 48;
|
||||
if (nd && *nd && *nd != '0') {
|
||||
int ndv = (int)strtol(nd, NULL, 0);
|
||||
if (ndv == 5) pc_probe_arm_nd_intro_wood_dl_defaults();
|
||||
else if (ndv == 4) pc_probe_arm_nd_intro_depth_defaults();
|
||||
else if (ndv == 3) pc_probe_arm_nd_intro_wood_defaults();
|
||||
else if (ndv == 2) pc_probe_arm_nd_intro_ot_defaults();
|
||||
else pc_probe_arm_nd_intro_defaults();
|
||||
}
|
||||
if (pcs && *pcs) pc_probe_parse_list(pcs);
|
||||
if (s_pc_probe_n > 0) {
|
||||
s_pc_probe_armed = 1;
|
||||
fprintf(stdout, "psxrecomp: pc_probe armed (%d pcs)\n", s_pc_probe_n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
+82
-24
@@ -701,18 +701,45 @@ static uint32_t execute_ch2_gpu(void) {
|
||||
/* Linked-list mode: follow ordering table in RAM.
|
||||
* Each node: bits 24-31 = number of words following header,
|
||||
* bits 0-23 = next node address (0xFFFFFF = end).
|
||||
* The words following the header are sent to GP0. */
|
||||
gpu_ws_begin_linked_list();
|
||||
uint32_t addr =
|
||||
psx_mod_gpu_dma_resolve_address(channels[2].madr);
|
||||
gpu_ws_prepass_linked_list(addr);
|
||||
uint32_t safety = 0;
|
||||
const uint32_t MAX_NODES = 0x40000; /* prevent infinite loops */
|
||||
* The words following the header are sent to GP0.
|
||||
*
|
||||
* PSX_ND_SIB_FLAP_LAST=1 (opt-in): skip additive PolyGT3 (GP0 0x36)
|
||||
* whose signed 11-bit SX max is >= 280 AND whose OT rank is in the ND
|
||||
* digit-rain band (~1832). Was a flap-overpaint workaround; after the
|
||||
* AVSZ3 MAC0 fix it shreds the crate glow fountain — leave unset/0.
|
||||
*
|
||||
* OT-rank gate is mandatory: the same SX filter matches menu/char-select
|
||||
* 0x36 (e.g. ot~1066) and would strip Crash face/trophy semis. Rank is
|
||||
* the empty-node count used by gpu_set_gp0_linked_list_node.
|
||||
*
|
||||
* SX must be parsed as signed 11-bit (gpu.c parse_vertex). Legacy alias:
|
||||
* PSX_ND_OT_OPAQUE_LAST. */
|
||||
static int s_nd_flap_last = -1;
|
||||
if (s_nd_flap_last < 0) {
|
||||
const char *e = getenv("PSX_ND_SIB_FLAP_LAST");
|
||||
if (!e || !*e)
|
||||
e = getenv("PSX_ND_OT_OPAQUE_LAST");
|
||||
s_nd_flap_last = (e && *e && *e != '0') ? 1 : 0;
|
||||
if (s_nd_flap_last)
|
||||
fprintf(stdout, "psxrecomp: PSX_ND_SIB_FLAP_LAST enabled\n");
|
||||
}
|
||||
|
||||
gpu_ws_begin_linked_list();
|
||||
uint32_t start_addr =
|
||||
psx_mod_gpu_dma_resolve_address(channels[2].madr);
|
||||
gpu_ws_prepass_linked_list(start_addr);
|
||||
const uint32_t MAX_NODES = 0x40000; /* prevent infinite loops */
|
||||
uint32_t last_addr = start_addr;
|
||||
int hit_limit = 0;
|
||||
/* Mirror gpu.c empty-node OT rank (0 after first empty header). */
|
||||
uint32_t ot_rank = 0xffffffffu;
|
||||
|
||||
uint32_t addr = start_addr;
|
||||
uint32_t safety = 0;
|
||||
for (;;) {
|
||||
if (safety++ > MAX_NODES) {
|
||||
/* Abort this transfer — linked list has a cycle or is corrupt.
|
||||
* Don't crash; the shell may recover on the next frame. */
|
||||
hit_limit = 1;
|
||||
last_addr = addr;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -720,29 +747,60 @@ static uint32_t execute_ch2_gpu(void) {
|
||||
uint32_t num_words = (header >> 24) & 0xFF;
|
||||
uint32_t word_addr =
|
||||
psx_mod_gpu_dma_resolve_address(addr + 4u);
|
||||
gpu_set_gp0_linked_list_node(addr, num_words);
|
||||
actual_words += 1u;
|
||||
|
||||
for (uint32_t i = 0; i < num_words; i++) {
|
||||
uint32_t word = psx_read_word(word_addr);
|
||||
gpu_set_gp0_source(word_addr);
|
||||
gpu_write_gp0(word);
|
||||
word_addr =
|
||||
psx_mod_gpu_dma_resolve_address(word_addr + 4u);
|
||||
if (num_words == 0u)
|
||||
ot_rank = (ot_rank == 0xffffffffu) ? 0u : (ot_rank + 1u);
|
||||
|
||||
int emit = 1;
|
||||
if (s_nd_flap_last && num_words >= 6u &&
|
||||
ot_rank >= 1600u && ot_rank < 2100u) {
|
||||
uint32_t cmd = psx_read_word(word_addr);
|
||||
uint32_t op = (cmd >> 24) & 0xFFu;
|
||||
if (op == 0x36u) {
|
||||
/* Match gpu.c parse_vertex: signed 11-bit SX. Digit-rain
|
||||
* packets often stash junk in the high bits of the XY word;
|
||||
* int16 SX mis-classifies them. Skip glow whose s11 bbox
|
||||
* reaches the sibling right-flap band (sx_max >= 280). */
|
||||
int32_t sx_max = -0x8000;
|
||||
for (uint32_t vi = 1; vi <= 5; vi += 2) {
|
||||
uint32_t xy = psx_read_word(
|
||||
psx_mod_gpu_dma_resolve_address(word_addr +
|
||||
vi * 4u));
|
||||
int32_t sx = (int32_t)(xy & 0x7FFu);
|
||||
if (sx & 0x400)
|
||||
sx -= 0x800;
|
||||
if (sx > sx_max)
|
||||
sx_max = sx;
|
||||
}
|
||||
if (sx_max >= 280)
|
||||
emit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (emit) {
|
||||
gpu_set_gp0_linked_list_node(addr, num_words);
|
||||
actual_words += 1u;
|
||||
for (uint32_t i = 0; i < num_words; i++) {
|
||||
uint32_t word = psx_read_word(word_addr);
|
||||
gpu_set_gp0_source(word_addr);
|
||||
gpu_write_gp0(word);
|
||||
word_addr =
|
||||
psx_mod_gpu_dma_resolve_address(word_addr + 4u);
|
||||
}
|
||||
actual_words += num_words;
|
||||
}
|
||||
actual_words += num_words;
|
||||
|
||||
/* Next node */
|
||||
uint32_t next = header & 0xFFFFFFu;
|
||||
if (next == 0xFFFFFFu) {
|
||||
channels[2].madr = 0x00FFFFFFu;
|
||||
break; /* end of list */
|
||||
last_addr = 0x00FFFFFFu;
|
||||
break;
|
||||
}
|
||||
addr = psx_mod_gpu_dma_resolve_address(next);
|
||||
last_addr = addr;
|
||||
}
|
||||
if (safety > MAX_NODES) {
|
||||
channels[2].madr = addr;
|
||||
}
|
||||
(void)hit_limit;
|
||||
|
||||
channels[2].madr = last_addr;
|
||||
gpu_ws_end_linked_list();
|
||||
} else {
|
||||
/* Burst mode (sync_mode == 0) */
|
||||
|
||||
@@ -3247,6 +3247,24 @@ static void gp0_exec_mono_quad(void) {
|
||||
}
|
||||
if (draw_area_out_bbox(vx, vy, 4)) return;
|
||||
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
|
||||
/* Semi axis-aligned mono quads (UI boxes/borders): one rect, not two tris.
|
||||
* Thin semi borders (e.g. CTR name-entry OT-1144 teal 3×H) otherwise double-
|
||||
* blend their shared diagonal — nearly the whole strip — and overpaint 3D. */
|
||||
if (semi_trans && ws_axis_aligned_quad(vx, vy)) {
|
||||
int32_t min_x = vx[0], max_x = vx[0], min_y = vy[0], max_y = vy[0];
|
||||
for (int i = 1; i < 4; i++) {
|
||||
if (vx[i] < min_x) min_x = vx[i];
|
||||
if (vx[i] > max_x) max_x = vx[i];
|
||||
if (vy[i] < min_y) min_y = vy[i];
|
||||
if (vy[i] > max_y) max_y = vy[i];
|
||||
}
|
||||
int w = (int)(max_x - min_x);
|
||||
int h = (int)(max_y - min_y);
|
||||
if (w > 0 && h > 0) {
|
||||
gr_draw_flat_rect(min_x, min_y, w, h, color);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!rej_a) {
|
||||
int32_t tx[3] = { vx[0], vx[1], vx[2] };
|
||||
int32_t ty[3] = { vy[0], vy[1], vy[2] };
|
||||
|
||||
+87
-6
@@ -1,10 +1,13 @@
|
||||
#include "gte.h"
|
||||
#include "cpu_state.h"
|
||||
#include "nd_intro_ot.h"
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
extern "C" uint32_t psx_read_word(uint32_t addr);
|
||||
|
||||
namespace PSXRecomp {
|
||||
namespace GTE {
|
||||
|
||||
@@ -996,12 +999,19 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// AVSZ3 (0x2D) — Average Z (3 points)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hardware (psx-spx / DuckStation): MAC0 keeps ZSF3*(SZ1+SZ2+SZ3); only OTZ
|
||||
// gets MAC0/1000h. CTR NdIntroWoodEmitHelper indexes the batch OT with
|
||||
// MAC0>>17 (== OTZ>>5). Shifting MAC0 here collapsed every face into slot 0
|
||||
// (arms/trophy/chest z-fight + missing banner under later same-bucket paint).
|
||||
void gte_avsz3(GTEState* gte, uint32_t instr) {
|
||||
(void)instr;
|
||||
gte->FLAG = 0;
|
||||
int64_t mac0 = (int64_t)gte->ZSF3 * (gte->SZ[1] + gte->SZ[2] + gte->SZ[3]);
|
||||
const int64_t sum =
|
||||
(int64_t)(uint32_t)gte->SZ[1] + gte->SZ[2] + gte->SZ[3];
|
||||
const int64_t mac0 = (int64_t)gte->ZSF3 * sum;
|
||||
gte->check_mac0_overflow(mac0);
|
||||
gte->MAC0 = static_cast<int32_t>(mac0 >> 12);
|
||||
gte->OTZ = gte->saturate_sz(gte->MAC0);
|
||||
gte->MAC0 = static_cast<int32_t>(mac0);
|
||||
gte->OTZ = gte->saturate_sz(static_cast<int32_t>(mac0 >> 12));
|
||||
gte->set_error_flag();
|
||||
}
|
||||
|
||||
@@ -1009,11 +1019,14 @@ void gte_avsz3(GTEState* gte, uint32_t instr) {
|
||||
// AVSZ4 (0x2E) — Average Z (4 points)
|
||||
// ---------------------------------------------------------------------------
|
||||
void gte_avsz4(GTEState* gte, uint32_t instr) {
|
||||
(void)instr;
|
||||
gte->FLAG = 0;
|
||||
int64_t mac0 = (int64_t)gte->ZSF4 * (gte->SZ[0] + gte->SZ[1] + gte->SZ[2] + gte->SZ[3]);
|
||||
const int64_t sum = (int64_t)(uint32_t)gte->SZ[0] + gte->SZ[1] +
|
||||
gte->SZ[2] + gte->SZ[3];
|
||||
const int64_t mac0 = (int64_t)gte->ZSF4 * sum;
|
||||
gte->check_mac0_overflow(mac0);
|
||||
gte->MAC0 = static_cast<int32_t>(mac0 >> 12);
|
||||
gte->OTZ = gte->saturate_sz(gte->MAC0);
|
||||
gte->MAC0 = static_cast<int32_t>(mac0);
|
||||
gte->OTZ = gte->saturate_sz(static_cast<int32_t>(mac0 >> 12));
|
||||
gte->set_error_flag();
|
||||
}
|
||||
|
||||
@@ -1811,6 +1824,74 @@ extern "C" void gte_execute(CPUState* cpu, uint32_t cmd) {
|
||||
|
||||
gte_export_cpu_state(cpu, >e);
|
||||
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
/* ND digit-rain: NdIntroSiblingFaceLoop (jal 0x80069CC4; $ra stays 69C3C..
|
||||
* while FaceLoop runs). face_hi=0 → AddPrim at a3 (main OT last slot).
|
||||
* WoodEmit/glow: model+228 base + (MAC0>>17)*4 via 0x8006AD88.
|
||||
*
|
||||
* Frame order on rain: WoodBatchSetup (all models, incl. CODE/GLOW) →
|
||||
* sibling → WoodEmit. CODE model is stable at 0x800FF390.
|
||||
*
|
||||
* PSX_ND_SIB_OT_BATCH=1: $s2 = CODE/GLOW batch OT + index*4.
|
||||
* PSX_ND_SIB_OT_IDX=N: force index (else OTZ>>5 + PSX_ND_SIB_OT_BIAS).
|
||||
* Same-bucket as digits still loses: DMA walks flaps then 0x36 (digits on
|
||||
* top). Prefer correct AVSZ MAC0 OT indices; FLAP_LAST is opt-in only. */
|
||||
if (func == 0x30 && !s_gte_replay_sandbox) {
|
||||
static int s_sib_batch = -1;
|
||||
static int s_sib_avsz = -1;
|
||||
static int s_sib_bias = 0;
|
||||
static int s_sib_force_idx = -1;
|
||||
if (s_sib_batch < 0) {
|
||||
const char *e = std::getenv("PSX_ND_SIB_OT_BATCH");
|
||||
s_sib_batch = (e && *e && *e != '0') ? 1 : 0;
|
||||
const char *a = std::getenv("PSX_ND_SIB_OT_AVSZ");
|
||||
s_sib_avsz = (a && *a && *a != '0') ? 1 : 0;
|
||||
const char *b = std::getenv("PSX_ND_SIB_OT_BIAS");
|
||||
s_sib_bias = (b && *b) ? std::atoi(b) : 0;
|
||||
const char *ix = std::getenv("PSX_ND_SIB_OT_IDX");
|
||||
s_sib_force_idx = (ix && *ix) ? std::atoi(ix) : -1;
|
||||
if (s_sib_batch)
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: PSX_ND_SIB_OT_BATCH enabled (bias=%d idx=%d)\n",
|
||||
s_sib_bias, s_sib_force_idx);
|
||||
else if (s_sib_avsz)
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: PSX_ND_SIB_OT_AVSZ enabled (main OTZ>>5, bias=%d)\n",
|
||||
s_sib_bias);
|
||||
}
|
||||
if (s_sib_batch || s_sib_avsz) {
|
||||
const uint32_t ra = s_gte_caller_ra;
|
||||
if (ra == 0x80069C3Cu || ra == 0x80069C60u ||
|
||||
ra == 0x80069C84u || ra == 0x80069CA8u) {
|
||||
const uint32_t a3 = cpu->gpr[7];
|
||||
uint32_t ot_base = 0;
|
||||
if (s_sib_batch) {
|
||||
/* CODE model+228 preferred (digit rain); noted at 6AAF0
|
||||
* and refreshed at sibling entry from 0x800FF390. */
|
||||
ot_base = psx_nd_wood_batch_ot();
|
||||
if (!ot_base)
|
||||
ot_base = psx_nd_sibling_ot_hint();
|
||||
} else if (a3 >= 4092u) {
|
||||
ot_base = a3 - 4092u;
|
||||
}
|
||||
if (ot_base) {
|
||||
int32_t idx;
|
||||
if (s_sib_force_idx >= 0) {
|
||||
idx = s_sib_force_idx;
|
||||
} else {
|
||||
const int64_t mac0 = (int64_t)gte.ZSF3 *
|
||||
((int64_t)gte.SZ[1] + gte.SZ[2] + gte.SZ[3]);
|
||||
idx = (int32_t)(mac0 >> 17) + s_sib_bias;
|
||||
}
|
||||
if (idx < 0) idx = 0;
|
||||
if (idx > 2047) idx = 2047;
|
||||
cpu->gpr[18] = ot_base + ((uint32_t)idx << 2); /* $s2 */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PSX_ENABLE_BLOCK_CYCLES
|
||||
/* Faithful GTE command completion-stall: arm the per-command deadline
|
||||
* (serializing back-to-back ops). Any later COP2 register access stalls to
|
||||
|
||||
@@ -8954,6 +8954,12 @@ int main(int argc, char** argv) {
|
||||
game_id = gc.id;
|
||||
game_region = gc.region;
|
||||
game_players = gc.players;
|
||||
/* CTR ND intro: an older workaround (PSX_ND_SIB_FLAP_LAST=1) skipped
|
||||
* wide additive 0x36 in OT ranks 1600..2099 to unmask sibling flaps.
|
||||
* After the AVSZ3 MAC0 fix (unshifted product → correct MAC0>>17 OT
|
||||
* indices), flaps/rain sort without that skip — and FLAP_LAST=1
|
||||
* shreds the crate glow fountain. Default is off; opt in via env
|
||||
* (dma.c) only for experiments. */
|
||||
apply_offline_pad_count(game_players, multitap_enabled);
|
||||
game_has_disc_crc = gc.has_disc_crc;
|
||||
game_disc_crc = gc.disc_crc;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/* nd_intro_ot.c — CTR ND intro OT batch base cache for sibling flap depth. */
|
||||
#include "nd_intro_ot.h"
|
||||
|
||||
static uint32_t s_wood_batch_ot;
|
||||
static uint32_t s_wood_batch_ot_code;
|
||||
static uint32_t s_wood_batch_ot_glow;
|
||||
static uint32_t s_wood_batch_ot_box;
|
||||
static uint32_t s_sibling_ot_hint;
|
||||
|
||||
static int looks_like_ot_base(uint32_t base)
|
||||
{
|
||||
/* Wood batch OTs sit in gameplay RAM below the main OT windows. */
|
||||
return base >= 0x800F0000u && base < 0x800F8000u && (base & 3u) == 0u;
|
||||
}
|
||||
|
||||
void psx_nd_note_wood_batch_ot(uint32_t base)
|
||||
{
|
||||
if (looks_like_ot_base(base))
|
||||
s_wood_batch_ot = base;
|
||||
}
|
||||
|
||||
void psx_nd_note_wood_batch_ot_tagged(uint32_t base, uint32_t tag)
|
||||
{
|
||||
if (!looks_like_ot_base(base))
|
||||
return;
|
||||
s_wood_batch_ot = base;
|
||||
/* Prefer CODE (digit rain) > GLOW > BOX_ for sibling OT rewrite. */
|
||||
if (tag == 0x45444F43u) /* CODE */
|
||||
s_wood_batch_ot_code = base;
|
||||
else if (tag == 0x574F4C47u) /* GLOW */
|
||||
s_wood_batch_ot_glow = base;
|
||||
else if (tag == 0x5F584F42u) /* BOX_ */
|
||||
s_wood_batch_ot_box = base;
|
||||
}
|
||||
|
||||
uint32_t psx_nd_wood_batch_ot(void)
|
||||
{
|
||||
if (s_wood_batch_ot_code)
|
||||
return s_wood_batch_ot_code;
|
||||
if (s_wood_batch_ot_glow)
|
||||
return s_wood_batch_ot_glow;
|
||||
if (s_wood_batch_ot_box)
|
||||
return s_wood_batch_ot_box;
|
||||
return s_wood_batch_ot;
|
||||
}
|
||||
|
||||
void psx_nd_note_sibling_ot_hint(uint32_t base)
|
||||
{
|
||||
if (looks_like_ot_base(base))
|
||||
s_sibling_ot_hint = base;
|
||||
}
|
||||
|
||||
uint32_t psx_nd_sibling_ot_hint(void)
|
||||
{
|
||||
return s_sibling_ot_hint;
|
||||
}
|
||||
@@ -437,6 +437,29 @@ def build_cmd(args):
|
||||
if len(args) > 2:
|
||||
d["nth"] = int(args[2])
|
||||
return d, pretty_json
|
||||
elif cmd == "pc_probe_arm":
|
||||
# pc_probe_arm [nd_intro=1|2|3|nd|nd_ot|nd_wood] [pcs=0xA,0xB] [n=32]
|
||||
d = {"cmd": "pc_probe_arm"}
|
||||
for a in args[1:]:
|
||||
if "=" in a:
|
||||
k, v = a.split("=", 1)
|
||||
if k in ("n", "nd_intro") and v.lstrip("-").isdigit():
|
||||
d[k] = int(v)
|
||||
else:
|
||||
d[k] = v
|
||||
elif a in ("nd", "nd_intro"):
|
||||
d["nd_intro"] = 1
|
||||
elif a in ("nd_ot", "ot"):
|
||||
d["nd_intro"] = 2
|
||||
elif a in ("nd_wood", "wood"):
|
||||
d["nd_intro"] = 3
|
||||
elif a in ("nd_depth", "depth"):
|
||||
d["nd_intro"] = 4
|
||||
return d, pretty_json
|
||||
elif cmd == "pc_probe_dump":
|
||||
return {"cmd": "pc_probe_dump"}, pretty_json
|
||||
elif cmd == "pc_probe_clear":
|
||||
return {"cmd": "pc_probe_clear"}, pretty_json
|
||||
elif cmd == "fntrace_arm":
|
||||
# <target_hex|0xFFFFFFFF=all|0=clear>
|
||||
return {"cmd": "fntrace_arm", "target": args[1]}, pretty_json
|
||||
|
||||
Reference in New Issue
Block a user