Pilotwings 64
Nintendo's 1996 flight game for the Nintendo 64, read through the n64decomp decompilation.
On this page
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
| Mechanic | Kind | How |
|---|---|---|
| Flight model | Force sum | the 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 |
| Gravity | One gravity | the gyrocopter adds a gravity of 9.8 on the world's vertical every update, scaled by the frame time |
| Fuel gauge | By throttle | the gyrocopter's tank drains while the engine runs, from 0.001 a second at no power to 0.005 at full |
| Thermal | Column | each 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)
References
- ^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.
- ^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.
- ^ abIn the US version of Pilotwings 64, the gyrocopter's update routine
func_8030399Cclears a vector (unk31C) and has five routines add terms to it:func_80307A9C,func_80307E2C,func_80308478,func_80307BAC, andfunc_80307EA8. A routine chosen by state can add to it after them (func_80304B98, for states 1 and 4, does), andfunc_80309090copies the result tounk1AC. In state 1 (unkC0), the routine addsunk1ACtimes the frame time to the craft's velocity (unk190), with no mass dividing it; in state 0 it first combinesunk1ACwith cross terms of the velocity and the turn rates (unk1B8).func_80307E2Cadds (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_80307A9Cadds, on each of the craft's axes, a curve of the square ofunk364'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_80308478setsunk364to the vectorenv_802E1754returns 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 andunk364the 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/pw64revisedHow 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.
- ^ abcIn the US version of Pilotwings 64, two of the terms the gyrocopter's
func_8030399Csums 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 matrixunk25C, 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 matrixunk21C. The one it calls the engine term (func_80307EA8), whileusingFuelis 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 throughunk21C, and its y component is cut to 0.9 whenunk1F0is nonzero,unk190.yis positive, andunk7Cis 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 whileunkC1is set or in state 2 (unkC0).Coden64decomp/pw64revisedHow 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.
- ^ 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). WhileusingFuelis set,func_80307EA8takes 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 setsusingFuel, 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 clearsusingFuel. In a Class B task, each update thatfunc_802E08F4returns nonzero for the craft's position sets the fuel to 1, andSFX_UI_FUEL_ADDEDplays on the first.func_80307EA8does none of this, refill or drain, whileunkC1is 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/pw64revisedHow 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 framesrc/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 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_80346C08sums 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. Intherm_80346E04the vector's vertical part (the atlas's updraft) is the thermal'sunk24× √((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 lengthunk20× (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 heightunk18, and fromunk1Cfade out to 0 at the top.env_802E1754adds the sum to the vectorwind_8034D550gives for the point (by the atlas's reading, the wind), along with, unless the task'sunk8is 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'sunk2F8: 1.2 for Lark and Kiwi, 1.0 for Goose and Ibis, and 0.8 for Hawk and Robin.Coden64decomp/pw64revisedHow 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.
- ^ 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 (
cls3,CLASS_PILOT). There, from 240 on the clockD_8034F850(by the decompilers' comment, the current time; the test's setup,gameUpdateStateTestSetup, sets it to 0, and each timefunc_80313D74runs it adds the time since the last frame held between 0.01 and 0.1, so it counts seconds of game time),therm_8034695Cnarrows the drawn cylinders to nothing over the next 10, and at 250 (THERM_DISABLE_TIME) removes the thermals and showsTEXT_THERM_OFF. The narrowing scales only the model's matrix: the updraft reads the task's radius until the thermals are gone.Coden64decomp/pw64revisedHow 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 timesrc/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.
Sources
Code
- Smith, Garrett and pw64 contributors. 2026. “pw64: a decompilation of Pilotwings 64 (n64decomp/pw64).” Commit cb66cb2. n64decomp/pw64 @ cb66cb2 · licence, all uses
Primary
- America, Nintendo of. 1996. “Pilotwings 64 instruction booklet (Nintendo 64, North America).” Nintendo of America. original · archived · licence, all uses
Secondary
- Roberts, Joe. 1997. “PilotWings 64 (N64, PAL) full-game longplay, with the ending credits.” Internet Archive. original · archived · licence, all uses
- McFerran, Damien. 2013. “The Making of Pilotwings 64.” Nintendo Life. original · archived · licence, all uses