Sailing
Moving a boat by a sail, where the wind's direction against the heading sets the speed.
On this page
Sailing ties a boat’s speed to the wind. In The Legend of Zelda: The Wind Waker, read through the decompilers’ C++ for the boat, which doesn’t yet match the game’s bytes as a whole, the sail’s share of the top speed falls from all of it with the wind along the heading to three quarters across it and nothing past about 140°; the stick alone still moves the boat at up to 10, against the sail’s 551. The boat turns more slowly the faster it goes2.
- Wind angleThe Legend of Zelda: The Wind Waker (2002). The angle between the wind and the boat's facing scales a top speed of 55: full with the wind along the heading, three quarters across it, none past 140°.1,2,3
Games
| Game | Year | How | Note |
|---|---|---|---|
| The Legend of Zelda: The Wind Waker | 2002 | Wind angle | the angle between the wind and the boat's facing scales a top speed of 55: full with the wind along the heading, three quarters across it, none past 140° |
Every claim about this mechanic, by game (3)
References
- ^ abIn the decompilation's C++ for the boat (d_a_ship, which doesn't yet match the game's bytes as a whole; the USA GZLE01 build), procSteerMove, while the boat isn't airborne (daSFLG_FLY_e off), takes the gap between the wind vector's yaw (cM_atan2s of its x and z) and the boat's facing shape_angle.y, as a 16-bit angle from 0 to 0x8000, and turns it into a wind factor: 1 − gap/0x10000 below 0x4000 (90°), 0.75 − (gap − 0x4000) × 5.493164e-05 up to 0x6000 (135°), then 0.3 − (gap − 0x6000) × 0.00032967035, floored at 0. So the factor is 1 with the wind along the boat's facing, 0.875 at 45°, 0.75 at 90°, 0.3 at 135°, and 0 from 140° to 180° (computed). The target speed is the wind power divided by 0.5, capped at 1, times the factor times 55. The branches then run in this order, one per update. First, on the body animation's frame 7, if speedF is below twice the target (at most 80) it is raised to that and mTargetSpeed is set to the target (10000 if the boat is being moved by force), and nothing else changes speedF that update. Otherwise, while mTargetSpeed is below speedF, firstDecrementShipSpeed lowers speedF toward mTargetSpeed (a tenth of the gap, at most 5, at least 1) and sets mTargetSpeed to 10000 once the gap is under 3; this comes before the sail check, so it applies with the sail flag off too. Otherwise, with the sail-on flag off or a target of 0, decrementShipSpeed eases speedF toward 10 × the stick's value × the cosine of its angle (a twentieth of the gap, at most 0.1, at least 0.015). Otherwise decrementShipSpeed eases it down to a lower target, and cLib_addCalc up to a higher one (a tenth of the gap, at most 0.5, at least 0.1).Codezeldaret/twwrevised
How we know
1594 if (!checkStateFlg(daSFLG_FLY_e)) { 1595 cXyz* windVec = dKyw_get_wind_vec(); 1596 f32 targetSpeed = *dKyw_get_wind_power() / 0.5f; 1597 int angleDiff = abs((s16)(cM_atan2s(windVec->x, windVec->z) - shape_angle.y)); ⋮ 1600 if (angleDiff < 0x4000) { 1601 windFactor = 1.0f - angleDiff * (1.0f / 0x10000); 1602 } else if (angleDiff <= 0x6000) { 1603 windFactor = 0.75f - (angleDiff - 0x4000) * 5.493164e-05f; 1604 } else { 1605 windFactor = 0.3f - (angleDiff - 0x6000) * 0.00032967035f; 1606 if (windFactor < 0.0f) { 1607 windFactor = 0.0f; ⋮
src/d/actor/d_a_ship.cpp lines 1594–1637 at cb24f55 · upstream ↗ 1090void daShip_c::decrementShipSpeed(f32 i_targetSpeed) { 1091 cLib_addCalc(&speedF, i_targetSpeed, 0.05f, 0.1f, 0.015f); ⋮ 1095void daShip_c::firstDecrementShipSpeed(f32 i_targetSpeed) { 1096 if (cLib_addCalc(&speedF, i_targetSpeed, 0.1f, 5.0f, 1.0f) < 3.0f) { 1097 mTargetSpeed = 10000.0f;
src/d/actor/d_a_ship.cpp lines 1090–1098 at cb24f55 · upstream ↗ 22f32 cLib_addCalc(f32* pValue, f32 target, f32 scale, f32 maxStep, f32 minStep) { 23 if (*pValue != target) { 24 f32 step = scale * (target - *pValue); 25 if (step >= minStep || step <= -minStep) { 26 if (step > maxStep) { 27 step = maxStep; ⋮ 29 if (step < -maxStep) { 30 step = -maxStep; ⋮ 32 *pValue += step;
src/SSystem/SComponent/c_lib.cpp lines 22–32 at cb24f55 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
- ^ abIn the decompilation's C++ for the boat (d_a_ship, not yet matching as a whole; the USA build), procSteerMove moves the unnamed angle m0366, which the atlas reads as the tiller, toward mStickMVal × 0x2000 × −sin(mStickMAng) with cLib_addCalcAngleS(…, 4, l_HIO.tiller_speed, 0x100), tiller_speed being 700, and, unless the boat is airborne, passes it to setMoveAngle. Unless the boat is being moved by force (checkForceMove), setMoveAngle sets a rate of 4 − |speedF| / 55 × 3, held between 0.1 and 3.6, blends it toward 3.6 by the unnamed counter m0388 over 15 (3.6 once m0388 reaches 15), and turns the facing by −rate × (m0366 >> 6). With m0388 at 0 and a tiller of 0x2000, that is about 461 units (2.5°) an update at rest and 128 units (0.7°) at speed 55 (computed): the boat turns slower the faster it sails.Codezeldaret/tww
How we know
1586 cLib_addCalcAngleS(&m0366, mStickMVal * 0x2000 * -cM_ssin(mStickMAng), 4, l_HIO.tiller_speed, 0x100); 1587 if (!checkStateFlg(daSFLG_FLY_e)) { 1588 setMoveAngle(m0366);
src/d/actor/d_a_ship.cpp lines 1586–1589 at cb24f55 · upstream ↗ 71const s16 daShip_HIO_c0::tiller_speed = 700;src/d/actor/d_a_ship.cpp line 71 at cb24f55 · upstream ↗ 1113void daShip_c::setMoveAngle(s16 moveAngle) { 1114 if (!checkForceMove()) { 1115 s16 initialAngle = shape_angle.y; 1116 f32 turnRate = 4.0f - std::abs(speedF) / 55.0f * 3.0f; 1117 if (turnRate > 3.6f) { 1118 turnRate = 3.6f; ⋮ 1120 else if (turnRate < 0.1f) { 1121 turnRate = 0.1f; ⋮ 1123 if (m0388 >= 0xF) { 1124 turnRate = 3.6f; ⋮ 1126 else { 1127 turnRate += ((3.6f - turnRate) * m0388) / 15.0f; ⋮
src/d/actor/d_a_ship.cpp lines 1113–1130 at cb24f55 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^In the decompilation's C++ for the boat (d_a_ship, not yet matching as a whole; the USA build), procSteerMove allows a hop when no event runs, Link isn't barred from control (checkNoControll), the body animation is FN_MAST_ON2 at frame 7 or later, the boat isn't airborne, its forward speed mFwdVel is over 16.5, it isn't being moved by force, and the sailing target speed is at least 11. Then the R trigger's lock (mDoCPd_R_LOCK_TRIGGER) sets the airborne and jump flags and an upward speed equal to the target speed, held between 15 and 40, and sets mJumpRate to (speed.y − 11) × 0.014545455.Codezeldaret/tww
How we know
1638 if (!dComIfGp_event_runCheck() && 1639 !daPy_getPlayerLinkActorClass()->checkNoControll() && 1640 m0392 == dRes_INDEX_SHIP_BCK_FN_MAST_ON2_e && 1641 mpBodyAnm->getFrame() >= 7.0f && 1642 !checkStateFlg(daSFLG_FLY_e) && 1643 mFwdVel > 16.5f) { 1644 if (!checkForceMove() && targetSpeed >= 11.0f) { 1645 onStateFlg(daSFLG_JUMP_OK_e); 1646 if (mDoCPd_R_LOCK_TRIGGER(0)) { 1647 onStateFlg((daSHIP_SFLG)(daSFLG_FLY_e | daSFLG_JUMP_e)); 1648 speed.y = targetSpeed; 1649 if (speed.y < 15.0f) { ⋮
src/d/actor/d_a_ship.cpp lines 1638–1658 at cb24f55 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
Sources
Code
- zeldaret. 2026. “tww: a decompilation of The Legend of Zelda: The Wind Waker (zeldaret/tww).” Commit cb24f55. zeldaret/tww @ cb24f55 · licence, all uses