mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
fix CTR graphics
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`.
|
||||
|
||||
@@ -192,6 +192,7 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/runtime/src/gpu_gl_renderer.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gpu_vk_renderer.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/dma.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/nd_intro_ot.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/mdec.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/timers.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/interrupts.c
|
||||
|
||||
@@ -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\":[]}");
|
||||
}
|
||||
|
||||
+76
-22
@@ -16,6 +16,7 @@
|
||||
#endif
|
||||
#include <time.h>
|
||||
#include "debug_server.h"
|
||||
#include "nd_intro_ot.h"
|
||||
#include "latency_ring.h"
|
||||
#include "overlay_loader.h"
|
||||
#include "overlay_capture.h"
|
||||
@@ -2481,10 +2482,13 @@ static inline void pc_probe_observe(uint32_t block_leader_phys)
|
||||
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);
|
||||
@@ -2498,23 +2502,23 @@ static inline void pc_probe_observe(uint32_t block_leader_phys)
|
||||
ot_ptr = fp ? fp : v0;
|
||||
pc_probe_ot_context(cpu, ot_ptr, &mode, &depth, &ot_base, &ot_index);
|
||||
if (wood_ot) {
|
||||
/* Prefer OTZ as OT index (PsyQ AddPrim(ot+OTZ) style). */
|
||||
if (ot_base && otz < (1u << 20)) {
|
||||
ot_ptr = ot_base + (otz << 2);
|
||||
ot_index = otz;
|
||||
}
|
||||
t0 = a2;
|
||||
/* 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 = otz;
|
||||
/* Pack max screen-X of SXY0/1/2 into mode for extent compares. */
|
||||
{
|
||||
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;
|
||||
}
|
||||
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;
|
||||
@@ -2555,14 +2559,17 @@ static inline void pc_probe_observe(uint32_t block_leader_phys)
|
||||
}
|
||||
} 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 word@+8. */
|
||||
* 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 + 92u); /* pre-emit */
|
||||
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. */
|
||||
@@ -2584,13 +2591,32 @@ static inline void pc_probe_observe(uint32_t block_leader_phys)
|
||||
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. */
|
||||
/* 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) {
|
||||
mode = psx_read_word(t0 + 244u); /* OT base field before +4092 */
|
||||
depth = mode ? (mode + 4092u) : 0;
|
||||
ot_base = mode;
|
||||
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) {
|
||||
@@ -2600,6 +2626,11 @@ static inline void pc_probe_observe(uint32_t block_leader_phys)
|
||||
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;
|
||||
@@ -2663,6 +2694,29 @@ void debug_server_cyc_observe(uint32_t block_leader_phys) {
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 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
|
||||
|
||||
+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) */
|
||||
|
||||
@@ -3236,6 +3236,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
|
||||
|
||||
@@ -8883,6 +8883,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;
|
||||
|
||||
Reference in New Issue
Block a user