Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/PC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,17 @@ past the host header) and the event scripts' function records and stacks
`int`, is recovered by `CCharacter::ClothStep` with `PortImagePointer`, which
widens it against the image's own address.

## Arithmetic the EE does differently

The EE's floating-point unit is not IEEE: it has no NaN or infinity, and a
division by zero gives a finite number. Where retail relies on that, the host's
IEEE result is replaced by a copy of the function with the one case guarded and
nothing else changed:

| Function | Unit | Change |
|---|---|---|
| `FishLineStep` | `fishing.cpp` | a float pinned on its line point (every cast) has a length of 0 between them; it is taken as 1, so the 0/0 that leaves both still on the EE moves them by 0 instead of making them NaN, which ran through the line into Toan's position and crashed the town (#219) |

## What is still a stub

`tools/kazarov/src/cpp/sce/stubs/` holds the remaining SDK stubs: libdma, libgraph (all but
Expand Down
253 changes: 253 additions & 0 deletions port/src/fishing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ gfx::Vertex2D LineVertex(const int *screen) {
0x80, 0x80);
}


// ps2/src/fishing.cpp's pairs of hook and float points held apart by a fixed length, static there.
constexpr int kHookLink[3][2] = {
{0, 1},
{0, 2},
{1, 2}
};
constexpr int kUkiLink[6][2] = {
{0, 1},
{0, 2},
{0, 3},
{1, 2},
{2, 3},
{3, 1}
};
} // namespace

// The line is a GS line strip in which an XYZF3 write moves the pen without drawing: a segment
Expand Down Expand Up @@ -196,3 +211,241 @@ bool ListBaitForScript(RS_STACKDATA *choice, int bait) {
choice[1].i = bait;
return true;
}

// Retail's, with a pinned float's zero length taken as 1 (docs/PC.md, "Arithmetic the EE does differently").
PORT_OVERRIDE void FishLineStep(float *rod_position, float *unused) {
sceVu0FVECTOR hook_from;
sceVu0FVECTOR hook_to;
sceVu0FVECTOR uki_from;
sceVu0FVECTOR uki_to;
sceVu0FVECTOR hit;
sceVu0FVECTOR normal;
sceVu0FVECTOR diff;
sceVu0FVECTOR move0;
sceVu0FVECTOR move1;
float water;
float weight;
int i;
int iteration;

if (cpoly != NULL) {
sceVu0CopyVector(hook_from, point[23]);
sceVu0CopyVector(hook_to, point[23]);
hook_from[1] += 40.0f;
hook_to[1] -= 40.0f;
int hit_no = CheckHit(cpoly, cpoly_num, hook_from, hook_to, hit, 1, 0);

if (hit_no >= 0) {
sceVu0Normalize(normal, cpoly[hit_no].normal);
float slope = normal[1] < 0.0f ? -normal[1] : normal[1];

if (slope > 0.2f) {
HookGroundLevel = 0.2f + hit[1];
}
}

sceVu0CopyVector(uki_from, point[18]);
sceVu0CopyVector(uki_to, point[18]);
uki_from[1] += 40.0f;
uki_to[1] -= 40.0f;
hit_no = CheckHit(cpoly, cpoly_num, uki_from, uki_to, hit, 1, 0);

if (hit_no >= 0) {
sceVu0Normalize(normal, cpoly[hit_no].normal);
float slope = normal[1] < 0.0f ? -normal[1] : normal[1];

if (slope > 0.2f) {
UkiGroundLevel = 1.0f + hit[1];
}
}
}

if (UkiGroundLevel < HookGroundLevel) {
LineGroundLevel = UkiGroundLevel;
} else {
LineGroundLevel = HookGroundLevel;
}

water = WaterLevel;

sceVu0CopyVector(rod_top, rod_position);
rod_top[3] = 1.0f;
fishhook[3] = 1.0f;

for (i = 0; i < 24; i++) {
velo[i][1] += -0.1f;
velo[i][3] = 0.0f;
sceVu0AddVector(point[i], point[i], velo[i]);
}

weight = pull_hook;
point[23][1] += weight;

for (i = 0; i < 3; i++) {
hookv[i][1] += -0.1f + weight;
hookv[i][3] = 0.0f;
sceVu0AddVector(hookp[i], hookp[i], hookv[i]);
}

for (i = 0; i < 4; i++) {
ukiv[i][1] += -0.1f + weight;
ukiv[i][3] = 0.0f;
sceVu0AddVector(ukip[i], ukip[i], ukiv[i]);
}

for (iteration = 0; iteration < 16; iteration++) {
sceVu0CopyVector(point[0], rod_top);
sceVu0CopyVector(old_p[0], rod_top);

for (i = 1; i < 24; i++) {
weight = 0.5f;

if (i == 23) {
weight = 0.9f;
}

float rest = 1.0f - weight;
float *near = point[i - 1];
float *far = point[i];
sceVu0SubVector(diff, near, far);
float length = DistVector(diff);
float stretch = length - distp;
float scale = rest * stretch / length;
weight = weight * stretch / length;
sceVu0ScaleVector(move0, diff, scale);
sceVu0ScaleVector(move1, diff, weight);
sceVu0SubVector(near, near, move0);
sceVu0AddVector(far, far, move1);
}

sceVu0CopyVector(hookp[0], point[23]);

for (i = 0; i < 3; i++) {
sceVu0SubVector(diff, hookp[kHookLink[i][0]], hookp[kHookLink[i][1]]);
float length = DistVector(diff);
sceVu0ScaleVector(move0, diff, 0.5f * (length - hook_dist[i]) / length);
sceVu0SubVector(hookp[kHookLink[i][0]], hookp[kHookLink[i][0]], move0);
sceVu0AddVector(hookp[kHookLink[i][1]], hookp[kHookLink[i][1]], move0);
}

sceVu0SubVector(diff, point[18], ukip[0]);
float length = DistVector(diff);
// A pinned float sits on its line point, so length is 0: the EE's 0/0 leaves both where they are, a
// host's is NaN, which runs through the line into Toan's position. A length of 1 moves them by 0 too.
if (length == 0.0f) {
length = 1.0f;
}
weight = 0.2f;
float scale = length * (1.0f - weight) / length;
weight = length * weight / length;
sceVu0ScaleVector(move0, diff, scale);
sceVu0ScaleVector(move1, diff, weight);
sceVu0SubVector(point[18], point[18], move0);
sceVu0AddVector(ukip[0], ukip[0], move1);

for (i = 0; i < 6; i++) {
sceVu0SubVector(diff, ukip[kUkiLink[i][0]], ukip[kUkiLink[i][1]]);
float length = DistVector(diff);
sceVu0ScaleVector(move0, diff, 0.5f * (length - uki_dist[i]) / length);
sceVu0SubVector(ukip[kUkiLink[i][0]], ukip[kUkiLink[i][0]], move0);
sceVu0AddVector(ukip[kUkiLink[i][1]], ukip[kUkiLink[i][1]], move0);
}

if (set_hook_pos) {
sceVu0CopyVector(point[23], fishhook);
sceVu0CopyVector(hookp[0], fishhook);
}

if (set_uki_pos) {
sceVu0CopyVector(point[18], uki);
sceVu0CopyVector(ukip[0], uki);
}
}

for (i = 0; i < 24; i++) {
sceVu0SubVector(velo[i], point[i], old_p[i]);

if (i != 23) {
sceVu0ScaleVector(velo[i], velo[i], 0.96f);
} else {
sceVu0ScaleVector(velo[i], velo[i], 0.999f);
}

if (point[i][1] < LineGroundLevel) {
point[i][1] = LineGroundLevel;
velo[i][0] *= 0.4f;
velo[i][1] *= -0.4f;
velo[i][2] *= 0.4f;
}

if (point[i][1] < water) {
sceVu0ScaleVector(velo[i], velo[i], 0.4f);

if (i < 19) {
velo[i][1] += 0.1f;
}
}

sceVu0CopyVector(old_p[i], point[i]);
}

for (i = 0; i < 3; i++) {
sceVu0SubVector(hookv[i], hookp[i], hookop[i]);

if (hookp[i][1] < HookGroundLevel) {
hookp[i][1] = HookGroundLevel;
hookv[i][0] *= 0.8f;
hookv[i][1] *= -0.6f;
hookv[i][2] *= 0.8f;
}

sceVu0CopyVector(hookop[i], hookp[i]);
sceVu0ScaleVector(hookv[i], hookv[i], 0.99f);
}

for (i = 0; i < 4; i++) {
sceVu0SubVector(ukiv[i], ukip[i], ukiop[i]);

if (ukip[i][1] < UkiGroundLevel) {
ukip[i][1] = UkiGroundLevel;
ukiv[i][0] *= 0.6f;
ukiv[i][1] *= -0.8f;
ukiv[i][2] *= 0.6f;
}

if (i != 0 && ukip[i][1] < water && ukip[0][1] < water) {
sceVu0ScaleVector(ukiv[i], ukiv[i], 0.6f);
ukiv[i][1] += 0.3f;
}

if (ukip[0][1] < water && !(ukip[i][1] < water)) {
float height = ukip[0][1] - ukip[i][1];
float depth = ukip[0][1] - ukip[i][1];
height = depth < 0.0f ? -depth : height;
ukiv[i][0] *= 0.6f;
ukiv[i][2] *= 0.6f;

if ((depth < 0.0f ? -depth : depth) > 0.001f) {
ukiv[i][1] += 0.3f * (water - ukip[0][1]) / height;
}
}

if (ukip[i][1] < water && !(ukip[0][1] < water)) {
float height = ukip[i][1] - ukip[0][1];
height = height < 0.0f ? -height : height;
float depth = ukip[0][1] - ukip[i][1];

if ((depth < 0.0f ? -depth : depth) > 0.001f) {
ukiv[i][1] += 0.3f * (water - ukip[i][1]) / height;
}
}

sceVu0CopyVector(ukiop[i], ukip[i]);
sceVu0ScaleVector(ukiv[i], ukiv[i], 0.999f);
}

LineGroundLevel = HookGroundLevel = UkiGroundLevel = GroundLevel;
set_uki_pos = 0;
set_hook_pos = 0;
pull_hook = 0.0f;
}
23 changes: 23 additions & 0 deletions port/src/tests/fishing_line_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <gtest/gtest.h>

#include <cmath>

#include "fishing.hpp"

TEST(FishingLine, APinnedFloatStaysFinite) {
float rod[4] = {0.0f, 20.0f, 0.0f, 1.0f};
float uki[4] = {10.0f, 0.0f, 10.0f, 1.0f};
FishLineInit(rod);
FishLineSetUki(uki, 1.0f);
for (int frame = 0; frame < 4; frame++) {
FishLineStep(rod, rod);
}
FishLineSetUki(uki, -1.0f);
FishLineStep(rod, rod);

float after[4];
FishLineGetUki(after);
for (int i = 0; i < 3; i++) {
EXPECT_TRUE(std::isfinite(after[i])) << i;
}
}
Loading