Mechanic · movement · flight simulation
Flight model
The rules that move an aircraft through the air, from its forces or from a simpler steer-and-speed scheme.
On this page
A flight model decides how a craft moves through the air. The Pilotwings 64 gyrocopter adds five terms each update, by the atlas’s reading gravity, a drag curve, a share of the wind, and rotor and engine lift, and in one state adds the sum straight to its velocity1,2.
- Force sumPilotwings 64 (1996). 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.1,2
Games
| Game | Year | How | Note |
|---|---|---|---|
| Pilotwings 64 | 1996 | 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 |
Every claim about this mechanic, by game (2)
References
- ^ 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.
- ^ abIn 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.
Sources
Code
- Smith, Garrett and pw64 contributors. 2026. “pw64: a decompilation of Pilotwings 64 (n64decomp/pw64).” Commit cb66cb2. n64decomp/pw64 @ cb66cb2 · licence, all uses