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

Nintendo and Paradigm Simulation made Pilotwings 64: its credits list a Nintendo staff and a Paradigm staff1, and its manual gives both the copyright2. The atlas reads it through the decompilation by Garrett Smith and contributors, C that builds the US ROM. Most of its functions and fields still carry address names (func_80307EA8, unkB4), so the names given to terms here are the atlas’s reading, marked in each claim. Nothing has been measured in the game yet.

The gyrocopter’s flight model adds five terms each update and, in one of its states, adds the sum straight to its velocity. By the atlas’s reading the terms are gravity of 9.83, a drag curve on the square of its airspeed, a share of the wind, and two lift terms, from the rotor and the engine3,4. The engine’s push fades out above a height of 7004 and is gone by 8004. Its fuel drains while the engine runs, faster at higher power, up to 0.005 of a tank per second5,5.

Thermals are cylinders of air whose upward part is strongest on the axis and whose sideways part, around the axis, is fastest halfway out, and the hang glider scales the air it reads by 1.2 for Lark and Kiwi, 1.0 for Goose and Ibis, and 0.8 for Hawk and Robin6. In one Pilot class hang glider test they switch off at 250 s7,7.

Mechanics

MechanicKindHow
Flight modelForce sumthe gyrocopter sums gravity, a drag curve of squared airspeed, a share of the wind, rotor lift, and thrust that fades out between heights of 700 and 800
GravityOne gravitythe gyrocopter adds a gravity of 9.8 on the world's vertical every update, scaled by the frame time
Fuel gaugeBy throttlethe gyrocopter's tank drains while the engine runs, from 0.001 a second at no power to 0.005 at full
ThermalColumneach thermal is a cylinder of rising, swirling air, strongest on its axis, which the hang glider scales by 1.2 for Lark and Kiwi, 1.0 for Goose and Ibis, and 0.8 for Hawk and Robin
Every claim about this game, by concept (7)
generalThe North American instruction booklet for Pilotwings 64 gives the game's copyright to Nintendo and Paradigm Simulation, Inc. (1996); it names no developer as such.Manual
In a recording of the PAL version, Pilotwings 64's ending credits list a "Nintendo Staff" group, directed by Makoto Wada, then a separate "Paradigm Staff" group under project manager Dave Gatchel, and end on "Presented by Nintendo"; the credits never name Nintendo EAD.Video
Flight modelIn the US version of Pilotwings 64, the gyrocopter's update routine func_8030399C clears a vector (unk31C) and has five routines add terms to it: func_80307A9C, func_80307E2C, func_80308478, func_80307BAC, and func_80307EA8. A routine chosen by state can add to it after them (func_80304B98, for states 1 and 4, does), and func_80309090 copies the result to unk1AC. In state 1 (unkC0), the routine adds unk1AC times the frame time to the craft's velocity (unk190), with no mass dividing it; in state 0 it first combines unk1AC with cross terms of the velocity and the turn rates (unk1B8). func_80307E2C adds (0, 0, −9.8), z being the world's vertical, turned into the craft's frame through the inverse of its matrix (by the atlas's reading, gravity). func_80307A9C adds, on each of the craft's axes, a curve of the square of unk364's component on that axis, signed like the component: 0 at 0, 4.2 at 2661.2188, and 7.56 at 6944.389 and above, linear between (by the atlas's reading, air resistance). Those squares are speeds of about 51.59 and 83.33 (computed). func_80308478 sets unk364 to the vector env_802E1754 returns for the craft's position, in the craft's frame, less the craft's velocity, and adds 0.21 of that vector as a term of its own; by the atlas's reading the vector is the air's velocity and unk364 the airspeed. Below a speed (unk1A8) of 51.587303 the vector is scaled by the square of the speed over 51.587303, and outside state 0 it's taken as zero. The other two routines are in pw64/gyrocopter-lift.Code
In the US version of Pilotwings 64, two of the terms the gyrocopter's func_8030399C sums give it lift, by the atlas's reading. The one the atlas calls the rotor term (func_80307BAC) is 0.3 times the square of the airspeed's component on the z axis of the matrix unk25C, signed like that component and capped at 200 when positive, times the square of the airspeed's y component over 25, held between 0.05 and 1; it's added along the z axis of the matrix unk21C. The one it calls the engine term (func_80307EA8), while usingFuel is set, is 35 × power (unkB4) × a factor of height (spD8, the atlas's altitude factor), split 0.24 onto y and 0.76 onto z, the z part also times a curve of the negated y airspeed: 0 at 0, 0.15 at 10, 0.4 at 20, 1.2 at 50, and 1.5 at 100 and above. It too goes through unk21C, and its y component is cut to 0.9 when unk1F0 is nonzero, unk190.y is positive, and unk7C is 0. That factor is 1 up to a height of 700 and falls by 0.01 per unit above it, held at 0 from 800 up; that hold is under #if !defined(VERSION_JP), so a Japanese build lets the factor go negative. Neither term applies while unkC1 is set or in state 2 (unkC0).Code
Fuel gaugeIn the US version of Pilotwings 64, the gyrocopter's fuel is a number from 0 to 1, starting at the value the task's first start pad holds, or 0 if the task has no start pad (db_getstart). While usingFuel is set, func_80307EA8 takes off each update the frame time times a rate read from a curve of the power (unkB4, the throttle that A raises and B lowers, which the HUD shows as power; in the throttle's case 0 A sets usingFuel, and in cases 1 and 2 A sets it only with fuel above 0): 0.001 at 0, 0.002 at 0.5, and 0.005 at full, linear between. The frame time (D_8034F854) is the time since the last frame held between 0.01 and 0.1, so the rate is per second, and a full tank lasts 200 s at full power and 1,000 s at none (computed). At 0 it clears usingFuel. In a Class B task, each update that func_802E08F4 returns nonzero for the craft's position sets the fuel to 1, and SFX_UI_FUEL_ADDED plays on the first. func_80307EA8 does none of this, refill or drain, while unkC1 is set or in state 2 (unkC0). Separately, the HUD update in code_7CF30.c warns below 0.15 (TEXT_FUEL_WAR) and at 0 (TEXT_FUEL_OUT).Code
ThermalIn the US version of Pilotwings 64, a thermal is a vertical cylinder from the task's data (TaskTHER): a base position, a radius (scale), and a height. therm_80346C08 sums a vector from each thermal whose cylinder holds the point (by the atlas's reading, its air velocity): its horizontal distance d from the axis at most the radius r, and its height z above the base between 0 and the thermal's height. In therm_80346E04 the vector's vertical part (the atlas's updraft) is the thermal's unk24 × √((r − d)/r), full on the axis and 0 at the edge. Beyond 1 unit from the axis its horizontal part is at right angles to the axis direction, of length unk20 × (1 − 2|r/2 − d|/r) × d, longest halfway out (the atlas's swirl). Both fade in from 0 at the base to full at height unk18, and from unk1C fade out to 0 at the top. env_802E1754 adds the sum to the vector wind_8034D550 gives for the point (by the atlas's reading, the wind), along with, unless the task's unk8 is set, a further wind and a push from beyond 2050 units outside the map's bounds. The hang glider (hangGlider_802F391C) multiplies the total by its pilot's unk2F8: 1.2 for Lark and Kiwi, 1.0 for Goose and Ibis, and 0.8 for Hawk and Robin.Code
In the US version of Pilotwings 64, the thermals switch off in one test: the hang glider's test with index 0 in the Pilot class (cls 3, CLASS_PILOT). There, from 240 on the clock D_8034F850 (by the decompilers' comment, the current time; the test's setup, gameUpdateStateTestSetup, sets it to 0, and each time func_80313D74 runs it adds the time since the last frame held between 0.01 and 0.1, so it counts seconds of game time), therm_8034695C narrows the drawn cylinders to nothing over the next 10, and at 250 (THERM_DISABLE_TIME) removes the thermals and shows TEXT_THERM_OFF. The narrowing scales only the model's matrix: the updraft reads the task's radius until the thermals are gone.Code

References

  1. ^In a recording of the PAL version, Pilotwings 64's ending credits list a "Nintendo Staff" group, directed by Makoto Wada, then a separate "Paradigm Staff" group under project manager Dave Gatchel, and end on "Presented by Nintendo"; the credits never name Nintendo EAD.VideoPilotwings 64 PAL longplay · Nintendo Life, Making of Pilotwings 64
    How we know
    NINTENDO STAFF
    1:15:27 · archived copy ↗
    PARADIGM STAFF
    1:16:09 · archived copy ↗
    PRESENTED BY / NINTENDO
    1:18:49 · archived copy ↗
    Nintendo handled the game design while Paradigm was responsible for the technology
    · archived copy ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    In the claim ledger

  2. ^The North American instruction booklet for Pilotwings 64 gives the game's copyright to Nintendo and Paradigm Simulation, Inc. (1996); it names no developer as such.ManualPilotwings 64 US manual
    How we know
    © 1996 Nintendo / Paradigm Simulation, Inc.
    PDF p. 2 (inside the front cover) · archived copy ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-27.

    In the claim ledger

  3. ^ abIn the US version of Pilotwings 64, the gyrocopter's update routine func_8030399C clears a vector (unk31C) and has five routines add terms to it: func_80307A9C, func_80307E2C, func_80308478, func_80307BAC, and func_80307EA8. A routine chosen by state can add to it after them (func_80304B98, for states 1 and 4, does), and func_80309090 copies the result to unk1AC. In state 1 (unkC0), the routine adds unk1AC times the frame time to the craft's velocity (unk190), with no mass dividing it; in state 0 it first combines unk1AC with cross terms of the velocity and the turn rates (unk1B8). func_80307E2C adds (0, 0, −9.8), z being the world's vertical, turned into the craft's frame through the inverse of its matrix (by the atlas's reading, gravity). func_80307A9C adds, on each of the craft's axes, a curve of the square of unk364's component on that axis, signed like the component: 0 at 0, 4.2 at 2661.2188, and 7.56 at 6944.389 and above, linear between (by the atlas's reading, air resistance). Those squares are speeds of about 51.59 and 83.33 (computed). func_80308478 sets unk364 to the vector env_802E1754 returns for the craft's position, in the craft's frame, less the craft's velocity, and adds 0.21 of that vector as a term of its own; by the atlas's reading the vector is the air's velocity and unk364 the airspeed. Below a speed (unk1A8) of 51.587303 the vector is scaled by the square of the speed over 51.587303, and outside state 0 it's taken as zero. The other two routines are in pw64/gyrocopter-lift.Coden64decomp/pw64revised
    How we know
    251void func_8030399C(GyrocopterData* gcData) {
    ⋮
    265    gcData->unk31C.x = 0.0f;
    ⋮
    274    func_80307A9C(gcData);
    275    func_80307E2C(gcData);
    276    func_80308478(gcData);
    277    func_80307BAC(gcData);
    278    func_80307EA8(gcData);
    
    src/app/gyrocopter.c lines 251–279 at cb66cb2 · upstream ↗
    349    uvMat4Copy(&gcData->unk150, &gcData->unk10);
    350    uvMat4InvertTranslationRotation(&gcData->unk110, &gcData->unk150);
    351    if (gcData->unkC0 == 1) {
    352        gcData->unk190.x += D_8034F854 * gcData->unk1AC.x;
    ⋮
    355        uvMat4LocalToWorld(&gcData->unk150, &gcData->unk19C, &gcData->unk190);
    356    } else {
    357        gcData->unk19C.x += D_8034F854 * gcData->unk1B8.x;
    ⋮
    360        uvMat4LocalToWorld(&gcData->unk110, &gcData->unk190, &gcData->unk19C);
    ⋮
    363    gcData->unk10.m[3][0] = (gcData->unk19C.x * D_8034F854) + spF4;
    
    src/app/gyrocopter.c lines 349–365 at cb66cb2 · upstream ↗
    280    switch (gcData->unkC0) {
    281    case 0:
    282        func_80304738(gcData);
    283        break;
    284    case 1:
    285    case 4:
    286        func_80304B98(gcData);
    287        break;
    ⋮
    290        break;
    ⋮
    294        break;
    ⋮
    296    func_80309090(gcData);
    297    if ((gcData->unkC0 == 0) && (gcData->unk190.z < gcData->unk190.y) && (gcData->unk6D == 0)) {
    ⋮
    
    src/app/gyrocopter.c lines 280–310 at cb66cb2 · upstream ↗
    1818void func_80309090(GyrocopterData* gcData) {
    ⋮
    1826        gcData->unk1AC.x = gcData->unk31C.x;
    ⋮
    1835    gcData->unk1AC.x = gcData->unk31C.x;
    
    src/app/gyrocopter.c lines 1818–1837 at cb66cb2 · upstream ↗
    566void func_80304B98(GyrocopterData* gcData) {
    ⋮
    623        uvVec3Add(&gcData->unk31C, &gcData->unk31C, &sp11C);
    
    src/app/gyrocopter.c lines 566–623 at cb66cb2 · upstream ↗
    1442void func_80307E2C(GyrocopterData* gcData) {
    ⋮
    1447    sp1C.z = -9.8f;
    1448    uvMat4LocalToWorld(&gcData->unk110, &gcData->unk358, &sp1C);
    1449    gcData->unk31C.x += gcData->unk358.x;
    
    src/app/gyrocopter.c lines 1442–1452 at cb66cb2 · upstream ↗
    122    D_80369528.count = 3;
    123    D_80369528.unk4[0].x = 0.f, D_80369528.unk4[0].y = 0.0f;
    124    D_80369528.unk4[1].x = 2661.2188f, D_80369528.unk4[1].y = 4.2f;
    125    D_80369528.unk4[2].x = 6944.389f, D_80369528.unk4[2].y = 7.5599995f;
    
    src/app/gyrocopter.c lines 122–125 at cb66cb2 · upstream ↗
    1360void func_80307A9C(GyrocopterData* gcData) {
    ⋮
    1363    sp24.x = func_80313F08(&D_80369528, SQ(gcData->unk364.x));
    1364    if (gcData->unk364.x < 0.0f) {
    1365        sp24.x = -sp24.x;
    ⋮
    1377    gcData->unk31C.x += sp24.x;
    
    src/app/gyrocopter.c lines 1360–1380 at cb66cb2 · upstream ↗
    257f32 func_80313F08(Unk803599D0* arg0, f32 arg1) {
    ⋮
    263    if (arg1 <= arg0->unk4[0].f[0]) {
    264        return arg0->unk4[0].f[1];
    ⋮
    267    if (arg0->unk4[arg0->count - 1].f[0] <= arg1) {
    268        return arg0->unk4[arg0->count - 1].f[1];
    ⋮
    278    temp_fv1 = (arg1 - prev->f[0]) / (cur->f[0] - prev->f[0]);
    279    return (cur->f[1] * temp_fv1) + (prev->f[1] * (1.0f - temp_fv1));
    
    src/app/code_9A960.c lines 257–280 at cb66cb2 · upstream ↗
    1563void func_80308478(GyrocopterData* gcData) {
    ⋮
    1568    if (gcData->unkC0 == 0) {
    1569        env_802E1754(gcData->unk10.m[3][0], gcData->unk10.m[3][1], gcData->unk10.m[3][2], &sp38);
    1570        uvMat4LocalToWorld(&gcData->unk110, &sp44, &sp38);
    ⋮
    1577        } else {
    ⋮
    1580    } else {
    1581        sp44.x = sp44.y = sp44.z = 0.0f;
    ⋮
    1586    if (gcData->unk1A8 < 51.587303f) {
    1587        temp_fv0 = gcData->unk1A8 / 51.587303f;
    1588        temp_fv0 = SQ(temp_fv0);
    1589        sp44.x *= temp_fv0;
    ⋮
    1596    gcData->unk364.x = sp44.x - gcData->unk190.x;
    ⋮
    
    src/app/gyrocopter.c lines 1563–1605 at cb66cb2 · upstream ↗
    410    gcData->unk1A8 = uvVec3Len(&gcData->unk19C);
    
    src/app/gyrocopter.c line 410 at cb66cb2 · upstream ↗
    471            hud->altSeaLevel = gcData->unk10.m[3][2] * 0.7f;
    
    src/app/code_7CF30.c line 471 at cb66cb2 · 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

  4. ^ abcIn the US version of Pilotwings 64, two of the terms the gyrocopter's func_8030399C sums give it lift, by the atlas's reading. The one the atlas calls the rotor term (func_80307BAC) is 0.3 times the square of the airspeed's component on the z axis of the matrix unk25C, signed like that component and capped at 200 when positive, times the square of the airspeed's y component over 25, held between 0.05 and 1; it's added along the z axis of the matrix unk21C. The one it calls the engine term (func_80307EA8), while usingFuel is set, is 35 × power (unkB4) × a factor of height (spD8, the atlas's altitude factor), split 0.24 onto y and 0.76 onto z, the z part also times a curve of the negated y airspeed: 0 at 0, 0.15 at 10, 0.4 at 20, 1.2 at 50, and 1.5 at 100 and above. It too goes through unk21C, and its y component is cut to 0.9 when unk1F0 is nonzero, unk190.y is positive, and unk7C is 0. That factor is 1 up to a height of 700 and falls by 0.01 per unit above it, held at 0 from 800 up; that hold is under #if !defined(VERSION_JP), so a Japanese build lets the factor go negative. Neither term applies while unkC1 is set or in state 2 (unkC0).Coden64decomp/pw64revised
    How we know
    1382void func_80307BAC(GyrocopterData* gcData) {
    ⋮
    1403    if ((gcData->unkC1 != 0) || (gcData->unkC0 == 2)) {
    1404        gcData->unkE8 = func_80313AF4(0.0f, gcData->unkE8, 0.5f);
    1405        return;
    ⋮
    1407    uvMat4LocalToWorld(&gcData->unk25C, &sp34, &gcData->unk364);
    1408    sp54 = -(0.3f * sp34.z * sp34.z);
    1409    if (-sp34.z < 0.0f) {
    1410        sp54 = -sp54;
    1411        if (sp54 < -10.0f) {
    1412            sp54 = -10.0f;
    ⋮
    1414        if (sp54 > 200.0f) {
    1415            sp54 = 200.0f;
    ⋮
    
    src/app/gyrocopter.c lines 1382–1440 at cb66cb2 · upstream ↗
    1454void func_80307EA8(GyrocopterData* gcData) {
    ⋮
    1460    if ((gcData->unkC1 != 0) || (gcData->unkC0 == 2)) {
    1461        gcData->unkF0 = func_80313AF4(0.0f, gcData->unkF0, 0.5f);
    1462        return;
    
    src/app/gyrocopter.c lines 1454–1463 at cb66cb2 · upstream ↗
    1490    if (gcData->unk10.m[3][2] > 700.0f) {
    1491        spD8 = 1.0f - ((gcData->unk10.m[3][2] - 700.0f) / 100.0f);
    1492#if !defined(VERSION_JP)
    1493        if (spD8 < 0.0f) {
    1494            spD8 = 0.0f;
    ⋮
    1496#endif
    1497    } else {
    1498        spD8 = 1.0f;
    ⋮
    1500    if (gcData->usingFuel) {
    1501        temp_fv0 = func_80313F08(&D_803692C0, -gcData->unk364.y);
    1502        spDC.x = 0.0f;
    1503        spDC.y = gcData->unkB4 * 0.24f * spD8 * 35.0f;
    ⋮
    
    src/app/gyrocopter.c lines 1490–1516 at cb66cb2 · upstream ↗
    74    D_803692C0.count = 5;
    75    D_803692C0.unk4[0].x = 0.0f, D_803692C0.unk4[0].y = 0.f;
    76    D_803692C0.unk4[1].x = 10.0f, D_803692C0.unk4[1].y = 0.15f;
    77    D_803692C0.unk4[2].x = 20.0f, D_803692C0.unk4[2].y = 0.4f;
    78    D_803692C0.unk4[3].x = 50.0f, D_803692C0.unk4[3].y = 1.2f;
    79    D_803692C0.unk4[4].x = 100.0f, D_803692C0.unk4[4].y = 1.5f;
    
    src/app/gyrocopter.c lines 74–79 at cb66cb2 · upstream ↗
    471            hud->altSeaLevel = gcData->unk10.m[3][2] * 0.7f;
    ⋮
    473            if (hud->fuel > 0.0f) {
    474                hud->power = gcData->unkB4;
    
    src/app/code_7CF30.c lines 470–474 at cb66cb2 · 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

  5. ^ abIn the US version of Pilotwings 64, the gyrocopter's fuel is a number from 0 to 1, starting at the value the task's first start pad holds, or 0 if the task has no start pad (db_getstart). While usingFuel is set, func_80307EA8 takes off each update the frame time times a rate read from a curve of the power (unkB4, the throttle that A raises and B lowers, which the HUD shows as power; in the throttle's case 0 A sets usingFuel, and in cases 1 and 2 A sets it only with fuel above 0): 0.001 at 0, 0.002 at 0.5, and 0.005 at full, linear between. The frame time (D_8034F854) is the time since the last frame held between 0.01 and 0.1, so the rate is per second, and a full tank lasts 200 s at full power and 1,000 s at none (computed). At 0 it clears usingFuel. In a Class B task, each update that func_802E08F4 returns nonzero for the craft's position sets the fuel to 1, and SFX_UI_FUEL_ADDED plays on the first. func_80307EA8 does none of this, refill or drain, while unkC1 is set or in state 2 (unkC0). Separately, the HUD update in code_7CF30.c warns below 0.15 (TEXT_FUEL_WAR) and at 0 (TEXT_FUEL_OUT).Coden64decomp/pw64revised
    How we know
    85    D_803694D0.count = 3;
    86    D_803694D0.unk4[0].x = 0.0f, D_803694D0.unk4[0].y = 0.001f;
    87    D_803694D0.unk4[1].x = 0.5f, D_803694D0.unk4[1].y = 0.002f;
    88    D_803694D0.unk4[2].x = 1.f, D_803694D0.unk4[2].y = 0.005f;
    
    src/app/gyrocopter.c lines 84–88 at cb66cb2 · upstream ↗
    1454void func_80307EA8(GyrocopterData* gcData) {
    ⋮
    1460    if ((gcData->unkC1 != 0) || (gcData->unkC0 == 2)) {
    ⋮
    1462        return;
    ⋮
    1464    if (D_8036957E == CLASS_B) {
    1465        if (func_802E08F4(gcData->unk10.m[3][0], gcData->unk10.m[3][1], gcData->unk10.m[3][2]) != 0) {
    1466            if (gcData->unk67E == 0) {
    1467                gcData->unk67E = 1;
    1468                sndPlaySfx(SFX_UI_FUEL_ADDED);
    ⋮
    1473            gcData->fuel = 1.0f;
    ⋮
    1478    if ((gcData->fuel > 0.0f) && gcData->usingFuel) {
    1479        gcData->fuel -= func_80313F08(&D_803694D0, gcData->unkB4) * D_8034F854;
    1480        if (gcData->fuel <= 0.0f) {
    ⋮
    
    src/app/gyrocopter.c lines 1454–1488 at cb66cb2 · upstream ↗
    257f32 func_80313F08(Unk803599D0* arg0, f32 arg1) {
    ⋮
    278    temp_fv1 = (arg1 - prev->f[0]) / (cur->f[0] - prev->f[0]);
    279    return (cur->f[1] * temp_fv1) + (prev->f[1] * (1.0f - temp_fv1));
    
    src/app/code_9A960.c lines 257–280 at cb66cb2 · upstream ↗
    9f32 D_8034F854 = 0.1000000015f; // actual time since last frame
    
    src/app/code_9A960.c line 9 at cb66cb2 · upstream ↗
    213    temp_fv0 = uvGfxGetFrameTime();
    214    if (temp_fv0 < 0.01f) {
    215        D_8034F854 = 0.01f;
    216    } else if (temp_fv0 > 0.1f) {
    217        D_8034F854 = 0.1f;
    218    } else {
    219        D_8034F854 = temp_fv0;
    
    src/app/code_9A960.c lines 210–220 at cb66cb2 · upstream ↗
    95    db_getstart(&gcData->unk10, &gcData->unk190, &sp5F, &gcData->fuel);
    
    src/app/code_7CF30.c line 95 at cb66cb2 · upstream ↗
    260    temp_v1 = taskGetTPAD(&sp44);
    261    if (temp_v1 == 0) {
    ⋮
    271        if (fuel != NULL) {
    272            *fuel = 0.0f;
    ⋮
    274    } else {
    ⋮
    278        first = &sp44[0];
    ⋮
    292        if (fuel != NULL) {
    293            *fuel = first->unk2C;
    
    src/app/code_66F70.c lines 260–294 at cb66cb2 · upstream ↗
    248        switch (gcData->unk5F) {
    249        case 0:
    250            if (buttons & A_BUTTON) {
    251                gcData->unkB0 += gcData->unk654 * D_8034F854;
    252                if (!gcData->usingFuel) {
    253                    gcData->usingFuel = TRUE;
    ⋮
    256            if (buttons & B_BUTTON) {
    257                gcData->unkB0 -= gcData->unk658 * D_8034F854;
    ⋮
    274        case 2:
    ⋮
    279                if (buttons & A_BUTTON) {
    280                    gcData->unkB0 += gcData->unk648 * D_8034F854;
    281                    if (!gcData->usingFuel && (gcData->fuel > 0.0f)) {
    ⋮
    
    src/app/code_7CF30.c lines 248–309 at cb66cb2 · upstream ↗
    473            if (hud->fuel > 0.0f) {
    474                hud->power = gcData->unkB4;
    
    src/app/code_7CF30.c lines 473–474 at cb66cb2 · upstream ↗
    494                if ((gcData->fuel <= 0.0f) && !gcData->fuelEmpty) {
    495                    hudWarningText(TEXT_FUEL_OUT, 1.5f, 8.0f);
    ⋮
    497                } else if ((gcData->fuel < 0.15f) && !gcData->hasLowFuel) {
    498                    hudWarningText(TEXT_FUEL_WAR, 1.5f, 8.0f);
    
    src/app/code_7CF30.c lines 494–499 at cb66cb2 · 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

  6. ^In the US version of Pilotwings 64, a thermal is a vertical cylinder from the task's data (TaskTHER): a base position, a radius (scale), and a height. therm_80346C08 sums a vector from each thermal whose cylinder holds the point (by the atlas's reading, its air velocity): its horizontal distance d from the axis at most the radius r, and its height z above the base between 0 and the thermal's height. In therm_80346E04 the vector's vertical part (the atlas's updraft) is the thermal's unk24 × √((r − d)/r), full on the axis and 0 at the edge. Beyond 1 unit from the axis its horizontal part is at right angles to the axis direction, of length unk20 × (1 − 2|r/2 − d|/r) × d, longest halfway out (the atlas's swirl). Both fade in from 0 at the base to full at height unk18, and from unk1C fade out to 0 at the top. env_802E1754 adds the sum to the vector wind_8034D550 gives for the point (by the atlas's reading, the wind), along with, unless the task's unk8 is set, a further wind and a push from beyond 2050 units outside the map's bounds. The hang glider (hangGlider_802F391C) multiplies the total by its pilot's unk2F8: 1.2 for Lark and Kiwi, 1.0 for Goose and Ibis, and 0.8 for Hawk and Robin.Coden64decomp/pw64revised
    How we know
    274typedef struct {
    275    Vec3F pos;
    276    f32 scale;
    277    f32 height;
    ⋮
    283} TaskTHER; // size = 0x28
    
    src/app/task.h lines 274–283 at cb66cb2 · upstream ↗
    156void therm_80346C08(f32 x, f32 y, f32 z, Vec3F* dst) {
    ⋮
    174            continue;
    ⋮
    177        dx = x - therm->pos.x;
    178        dy = y - therm->pos.y;
    179        dz = z - therm->pos.z;
    180        dist = uvLength2D(dx, dy);
    ⋮
    182        if (taskTherm->scale < dist) {
    183            continue;
    ⋮
    185        if (taskTherm->height < dz) {
    186            continue;
    ⋮
    188        if (dz < 0.0f) {
    189            continue;
    ⋮
    
    src/app/thermals.c lines 156–199 at cb66cb2 · upstream ↗
    206    temp_fv0 = therm->scale * 0.5f;
    207    if (dist > 1.0f) {
    208        if (dist < temp_fv0) {
    209            var_fa0 = temp_fv0 - dist;
    210        } else {
    211            var_fa0 = -(temp_fv0 - dist);
    ⋮
    213        var_fv1 = 1.0f - ((2.0f * var_fa0) / therm->scale);
    214        dst->x = therm->unk20 * var_fv1 * dist * (y / dist);
    215        dst->y = -var_fv1 * therm->unk20 * dist * (x / dist);
    216    } else {
    ⋮
    219    var_fv1 = uvSqrtF((therm->scale - dist) / therm->scale);
    ⋮
    221        var_fv1 = 1.0f;
    ⋮
    
    src/app/thermals.c lines 201–235 at cb66cb2 · upstream ↗
    338void env_802E1754(f32 arg0, f32 arg1, f32 arg2, Vec3F* arg3) {
    ⋮
    347    wind_8034D550(arg0, arg1, arg2, &sp44);
    348    therm_80346C08(arg0, arg1, arg2, &sp38);
    ⋮
    355    } else {
    356        temp_fv0 = (temp_v0->unk0.unk0 - 2050.0f) - arg0;
    357        if (temp_fv0 > 0.0f) {
    358            sp2C = (50.0f * temp_fv0) / 2050.0f;
    ⋮
    361        if (temp_fv0 > 0.0f) {
    ⋮
    365        if (temp_fv0 > 0.0f) {
    ⋮
    369        if (temp_fv0 > 0.0f) {
    ⋮
    374    if (D_80362690->unkC[D_80362690->unk9C].unk8 == 0) {
    375        arg3->x = sp44.x + sp38.x + D_80359D80.unk0.x + sp2C;
    ⋮
    
    src/app/environment.c lines 338–382 at cb66cb2 · upstream ↗
    574STATIC_FUNC void hangGlider_802F391C(HangGliderData* hgData) {
    ⋮
    578    if (hgData->unk8C != 2) {
    579        env_802E1754(hgData->unk10.m[3][0], hgData->unk10.m[3][1], hgData->unk10.m[3][2], &sp20);
    580        sp20.x *= hgData->unk2F8;
    
    src/app/hang_glider2.c lines 574–591 at cb66cb2 · upstream ↗
    519void hangGliderLoadPilot(u8 pilot, HangGliderData* hgData) {
    520    switch (pilot) {
    521    case PILOT_LARK:
    ⋮
    569        hgData->unk2F8 = 1.2f;
    
    src/app/hang_glider1.c lines 519–569 at cb66cb2 · upstream ↗
    572    case PILOT_GOOSE:
    ⋮
    618        hgData->unk2F8 = 1.0f;
    
    src/app/hang_glider1.c lines 572–618 at cb66cb2 · upstream ↗
    623    case PILOT_HAWK:
    ⋮
    669        hgData->unk2F8 = 0.8f;
    
    src/app/hang_glider1.c lines 623–669 at cb66cb2 · upstream ↗
    674    case PILOT_KIWI:
    ⋮
    722        hgData->unk2F8 = 1.2f;
    
    src/app/hang_glider1.c lines 674–722 at cb66cb2 · upstream ↗
    725    case PILOT_IBIS:
    ⋮
    771        hgData->unk2F8 = 1.0f;
    
    src/app/hang_glider1.c lines 725–771 at cb66cb2 · upstream ↗
    776    case PILOT_ROBIN:
    ⋮
    822        hgData->unk2F8 = 0.8f;
    
    src/app/hang_glider1.c lines 776–822 at cb66cb2 · 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

  7. ^ abIn the US version of Pilotwings 64, the thermals switch off in one test: the hang glider's test with index 0 in the Pilot class (cls 3, CLASS_PILOT). There, from 240 on the clock D_8034F850 (by the decompilers' comment, the current time; the test's setup, gameUpdateStateTestSetup, sets it to 0, and each time func_80313D74 runs it adds the time since the last frame held between 0.01 and 0.1, so it counts seconds of game time), therm_8034695C narrows the drawn cylinders to nothing over the next 10, and at 250 (THERM_DISABLE_TIME) removes the thermals and shows TEXT_THERM_OFF. The narrowing scales only the model's matrix: the updraft reads the task's radius until the thermals are gone.Coden64decomp/pw64revised
    How we know
    14#define THERM_ENABLE_DURATION (4 * 60) // 4 minutes in sec
    15#define THERM_WEAKEN_DURATION (10)
    16#define THERM_DISABLE_TIME (THERM_ENABLE_DURATION + THERM_WEAKEN_DURATION)
    
    src/app/thermals.c lines 14–18 at cb66cb2 · upstream ↗
    98            sThermShouldDisable = 0;
    99            temp_v1_2 = &D_80362690->unkC[D_80362690->unk9C];
    100            if ((temp_v1_2->veh == VEHICLE_HANG_GLIDER) && (temp_v1_2->cls == 3) && (temp_v1_2->test == 0)) {
    101                sThermShouldDisable = 1;
    
    src/app/thermals.c lines 98–101 at cb66cb2 · upstream ↗
    53    CLASS_BEGINNER = 0,
    ⋮
    56    CLASS_PILOT = 3,
    
    src/app/task.h lines 53–56 at cb66cb2 · upstream ↗
    114    var_fs0 = 1.0f;
    ⋮
    116    if ((D_8034F850 >= THERM_DISABLE_TIME) && (gThermalCount != 0) && sThermShouldDisable) {
    117        thermDeinit();
    118        gThermalCount = 0;
    119        gThermReady = 0;
    120        hudWarningText(TEXT_THERM_OFF, 1.5f, 8.0f);
    ⋮
    127        if ((D_8034F850 >= THERM_ENABLE_DURATION) && (gThermalCount != 0) && sThermShouldDisable) {
    128            var_fs0 = (f32)(1.0 - (f64)((D_8034F850 - THERM_ENABLE_DURATION) / THERM_WEAKEN_DURATION));
    ⋮
    132        sp74.m[0][0] = taskTherm->scale * var_fs0;
    133        sp74.m[1][1] = taskTherm->scale * var_fs0;
    ⋮
    140            uvDobjPosm(therm->unk0, 0, &sp74);
    
    src/app/thermals.c lines 114–140 at cb66cb2 · upstream ↗
    182        if (taskTherm->scale < dist) {
    183            continue;
    
    src/app/thermals.c lines 182–184 at cb66cb2 · upstream ↗
    8f32 D_8034F850 = 0.0f;          // current time
    
    src/app/code_9A960.c line 8 at cb66cb2 · upstream ↗
    210void func_80313D74(void) {
    ⋮
    213    temp_fv0 = uvGfxGetFrameTime();
    214    if (temp_fv0 < 0.01f) {
    215        D_8034F854 = 0.01f;
    216    } else if (temp_fv0 > 0.1f) {
    217        D_8034F854 = 0.1f;
    ⋮
    221    D_8034F850 += D_8034F854;
    
    src/app/code_9A960.c lines 210–221 at cb66cb2 · upstream ↗
    431s32 gameUpdateStateTestSetup(Unk80362690* arg0) {
    ⋮
    537    func_80313E0C(0.0f);
    ⋮
    545    return GAME_STATE_TEST_UPDATE;
    
    src/app/game.c lines 431–545 at cb66cb2 · upstream ↗
    225void func_80313E0C(f32 arg0) {
    226    D_8034F850 = arg0;
    
    src/app/code_9A960.c lines 225–227 at cb66cb2 · 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

Sources

Code

Primary

Secondary