On this page
  1. Related
  2. Games
  3. References
  4. Sources

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

GameYearHowNote
The Legend of Zelda: The Wind Waker2002Wind anglethe 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)
The Legend of Zelda: The Wind Waker 2002In 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).Code
In 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.Code
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.Code

References

  1. ^ 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.

    In the claim ledger

  2. ^ 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 claim ledger

  3. ^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.

    In the claim ledger

Sources

Code