aero-agent-roles 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/NOTICE +18 -0
  2. package/README.md +49 -0
  3. package/bin/aero-agent-roles.js +127 -0
  4. package/lib/catalog.js +97 -0
  5. package/lib/install.js +48 -0
  6. package/lib/mcp.js +199 -0
  7. package/manifest.json +516 -0
  8. package/package.json +44 -0
  9. package/roles/aerodynamics-engineer/ROLE.md +153 -0
  10. package/roles/aerodynamics-engineer/SOURCES.md +12 -0
  11. package/roles/aerodynamics-engineer/cli.py +143 -0
  12. package/roles/aerodynamics-engineer/core/aerodynamics_core.py +1218 -0
  13. package/roles/aerodynamics-engineer/templates/aero-design-report-template.md +72 -0
  14. package/roles/aerodynamics-engineer/tests/test_aerodynamics_core.py +227 -0
  15. package/roles/aerodynamics-engineer/tests/test_role_aerodynamics.py +94 -0
  16. package/roles/aircraft-design-engineer/ROLE.md +138 -0
  17. package/roles/aircraft-design-engineer/SOURCES.md +12 -0
  18. package/roles/aircraft-design-engineer/cli.py +152 -0
  19. package/roles/aircraft-design-engineer/core/aircraft_design_core.py +1278 -0
  20. package/roles/aircraft-design-engineer/templates/concept-design-template.md +99 -0
  21. package/roles/aircraft-design-engineer/tests/test_aircraft_design_core.py +269 -0
  22. package/roles/aircraft-design-engineer/tests/test_role_aircraft_design_engineer.py +106 -0
  23. package/roles/airworthiness-compliance-engineer/ROLE.md +122 -0
  24. package/roles/airworthiness-compliance-engineer/SOURCES.md +16 -0
  25. package/roles/airworthiness-compliance-engineer/cli.py +146 -0
  26. package/roles/airworthiness-compliance-engineer/core/airworthiness_core.py +894 -0
  27. package/roles/airworthiness-compliance-engineer/templates/compliance-matrix-template.md +46 -0
  28. package/roles/airworthiness-compliance-engineer/tests/test_airworthiness_core.py +224 -0
  29. package/roles/airworthiness-compliance-engineer/tests/test_role_airworthiness.py +85 -0
  30. package/roles/as9100-quality-auditor/ROLE.md +128 -0
  31. package/roles/as9100-quality-auditor/SOURCES.md +16 -0
  32. package/roles/as9100-quality-auditor/cli.py +136 -0
  33. package/roles/as9100-quality-auditor/core/as9100_core.py +857 -0
  34. package/roles/as9100-quality-auditor/templates/findings-report-template.md +119 -0
  35. package/roles/as9100-quality-auditor/tests/test_as9100_cli_profile.py +105 -0
  36. package/roles/as9100-quality-auditor/tests/test_as9100_core.py +204 -0
  37. package/roles/as9100-quality-auditor/tests/test_role_as9100.py +90 -0
  38. package/roles/data-bus-avionics-engineer/ROLE.md +146 -0
  39. package/roles/data-bus-avionics-engineer/SOURCES.md +23 -0
  40. package/roles/data-bus-avionics-engineer/cli.py +311 -0
  41. package/roles/data-bus-avionics-engineer/core/data_bus_avionics_core.py +1121 -0
  42. package/roles/data-bus-avionics-engineer/templates/bus-assessment-template.md +134 -0
  43. package/roles/data-bus-avionics-engineer/tests/test_bundle_protocol.py +137 -0
  44. package/roles/data-bus-avionics-engineer/tests/test_data_bus_avionics_core.py +261 -0
  45. package/roles/data-bus-avionics-engineer/tests/test_role_data_bus_avionics_engineer.py +121 -0
  46. package/roles/do160-environmental-engineer/ROLE.md +136 -0
  47. package/roles/do160-environmental-engineer/SOURCES.md +30 -0
  48. package/roles/do160-environmental-engineer/cli.py +147 -0
  49. package/roles/do160-environmental-engineer/core/do160_environmental_core.py +1246 -0
  50. package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md +114 -0
  51. package/roles/do160-environmental-engineer/tests/test_do160_environmental_core.py +312 -0
  52. package/roles/do160-environmental-engineer/tests/test_role_do160_environmental_engineer.py +281 -0
  53. package/roles/do178c-cert-engineer/ROLE.md +120 -0
  54. package/roles/do178c-cert-engineer/SOURCES.md +16 -0
  55. package/roles/do178c-cert-engineer/cli.py +129 -0
  56. package/roles/do178c-cert-engineer/core/do178c_core.py +411 -0
  57. package/roles/do178c-cert-engineer/templates/psac-template.md +74 -0
  58. package/roles/do178c-cert-engineer/tests/test_do178c_cert_engineer_core.py +91 -0
  59. package/roles/do178c-cert-engineer/tests/test_role_do178c.py +109 -0
  60. package/roles/do254-hardware-engineer/ROLE.md +127 -0
  61. package/roles/do254-hardware-engineer/SOURCES.md +19 -0
  62. package/roles/do254-hardware-engineer/cli.py +308 -0
  63. package/roles/do254-hardware-engineer/core/do254_hardware_core.py +761 -0
  64. package/roles/do254-hardware-engineer/templates/phac-template.md +87 -0
  65. package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +152 -0
  66. package/roles/do254-hardware-engineer/tests/test_do254_hardware_core.py +200 -0
  67. package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +108 -0
  68. package/roles/engineering-analysis-engineer/ROLE.md +154 -0
  69. package/roles/engineering-analysis-engineer/SOURCES.md +15 -0
  70. package/roles/engineering-analysis-engineer/cli.py +151 -0
  71. package/roles/engineering-analysis-engineer/core/engineering_analysis_engineer_core.py +866 -0
  72. package/roles/engineering-analysis-engineer/templates/analysis-memo-template.md +73 -0
  73. package/roles/engineering-analysis-engineer/tests/test_engineering_analysis_engineer_core.py +367 -0
  74. package/roles/engineering-analysis-engineer/tests/test_role_engineering_analysis_engineer.py +113 -0
  75. package/roles/flight-management-engineer/ROLE.md +160 -0
  76. package/roles/flight-management-engineer/SOURCES.md +30 -0
  77. package/roles/flight-management-engineer/cli.py +258 -0
  78. package/roles/flight-management-engineer/core/flight_management_core.py +1473 -0
  79. package/roles/flight-management-engineer/templates/route-assessment-template.md +94 -0
  80. package/roles/flight-management-engineer/tests/test_flight_management_core.py +437 -0
  81. package/roles/flight-management-engineer/tests/test_role_flight_management_engineer.py +123 -0
  82. package/roles/flight-mechanics-engineer/ROLE.md +162 -0
  83. package/roles/flight-mechanics-engineer/SOURCES.md +51 -0
  84. package/roles/flight-mechanics-engineer/cli.py +149 -0
  85. package/roles/flight-mechanics-engineer/core/flight_mechanics_core.py +1507 -0
  86. package/roles/flight-mechanics-engineer/templates/perf-sc-report-template.md +82 -0
  87. package/roles/flight-mechanics-engineer/tests/test_flight_mechanics_core.py +277 -0
  88. package/roles/flight-mechanics-engineer/tests/test_role_flight_mechanics_engineer.py +141 -0
  89. package/roles/flight-test-engineer/ROLE.md +137 -0
  90. package/roles/flight-test-engineer/SOURCES.md +24 -0
  91. package/roles/flight-test-engineer/cli.py +135 -0
  92. package/roles/flight-test-engineer/core/flight_test_core.py +931 -0
  93. package/roles/flight-test-engineer/templates/flight-test-template.md +143 -0
  94. package/roles/flight-test-engineer/tests/test_flight_test_engineer_core.py +227 -0
  95. package/roles/flight-test-engineer/tests/test_role_flight_test_engineer.py +150 -0
  96. package/roles/flight-test-performance-engineer/ROLE.md +165 -0
  97. package/roles/flight-test-performance-engineer/SOURCES.md +20 -0
  98. package/roles/flight-test-performance-engineer/cli.py +319 -0
  99. package/roles/flight-test-performance-engineer/core/flight_test_performance_core.py +1398 -0
  100. package/roles/flight-test-performance-engineer/templates/performance-report-template.md +105 -0
  101. package/roles/flight-test-performance-engineer/tests/test_flight_test_performance_engineer_core.py +308 -0
  102. package/roles/flight-test-performance-engineer/tests/test_role_flight_test_performance_engineer.py +165 -0
  103. package/roles/gnc-engineer/ROLE.md +141 -0
  104. package/roles/gnc-engineer/SOURCES.md +35 -0
  105. package/roles/gnc-engineer/cli.py +141 -0
  106. package/roles/gnc-engineer/core/gnc_core.py +1074 -0
  107. package/roles/gnc-engineer/templates/gnc-report-template.md +105 -0
  108. package/roles/gnc-engineer/tests/test_gnc_engineer_core.py +238 -0
  109. package/roles/gnc-engineer/tests/test_role_gnc_engineer.py +142 -0
  110. package/roles/guidance-engineer/ROLE.md +156 -0
  111. package/roles/guidance-engineer/SOURCES.md +43 -0
  112. package/roles/guidance-engineer/cli.py +280 -0
  113. package/roles/guidance-engineer/core/guidance_core.py +820 -0
  114. package/roles/guidance-engineer/templates/guidance-report-template.md +70 -0
  115. package/roles/guidance-engineer/tests/test_guidance_engineer_core.py +269 -0
  116. package/roles/guidance-engineer/tests/test_role_guidance_engineer.py +146 -0
  117. package/roles/high-speed-aerodynamics-engineer/ROLE.md +161 -0
  118. package/roles/high-speed-aerodynamics-engineer/SOURCES.md +18 -0
  119. package/roles/high-speed-aerodynamics-engineer/cli.py +282 -0
  120. package/roles/high-speed-aerodynamics-engineer/core/high_speed_aero_core.py +1566 -0
  121. package/roles/high-speed-aerodynamics-engineer/templates/high-speed-memo-template.md +75 -0
  122. package/roles/high-speed-aerodynamics-engineer/tests/test_bundle_protocol.py +103 -0
  123. package/roles/high-speed-aerodynamics-engineer/tests/test_high_speed_aero_core.py +280 -0
  124. package/roles/high-speed-aerodynamics-engineer/tests/test_role_high_speed_aerodynamics.py +125 -0
  125. package/roles/ndt-engineer/ROLE.md +157 -0
  126. package/roles/ndt-engineer/SOURCES.md +18 -0
  127. package/roles/ndt-engineer/cli.py +316 -0
  128. package/roles/ndt-engineer/core/ndt_core.py +1590 -0
  129. package/roles/ndt-engineer/templates/ndt-plan-template.md +109 -0
  130. package/roles/ndt-engineer/tests/test_ndt_cli_bundle.py +183 -0
  131. package/roles/ndt-engineer/tests/test_ndt_core.py +437 -0
  132. package/roles/ndt-engineer/tests/test_role_ndt_engineer.py +148 -0
  133. package/roles/numerical-analysis-engineer/ROLE.md +158 -0
  134. package/roles/numerical-analysis-engineer/SOURCES.md +19 -0
  135. package/roles/numerical-analysis-engineer/cli.py +414 -0
  136. package/roles/numerical-analysis-engineer/core/numerical_analysis_core.py +1220 -0
  137. package/roles/numerical-analysis-engineer/templates/verification-memo-template.md +120 -0
  138. package/roles/numerical-analysis-engineer/tests/test_bundle_protocol.py +157 -0
  139. package/roles/numerical-analysis-engineer/tests/test_numerical_analysis_core.py +337 -0
  140. package/roles/numerical-analysis-engineer/tests/test_role_numerical_analysis_engineer.py +117 -0
  141. package/roles/propulsion-engineer/ROLE.md +163 -0
  142. package/roles/propulsion-engineer/SOURCES.md +15 -0
  143. package/roles/propulsion-engineer/cli.py +168 -0
  144. package/roles/propulsion-engineer/core/propulsion_core.py +1291 -0
  145. package/roles/propulsion-engineer/templates/propulsion-report-template.md +92 -0
  146. package/roles/propulsion-engineer/tests/test_propulsion_engineer_core.py +294 -0
  147. package/roles/propulsion-engineer/tests/test_role_propulsion_engineer.py +106 -0
  148. package/roles/space-systems-engineer/ROLE.md +161 -0
  149. package/roles/space-systems-engineer/SOURCES.md +49 -0
  150. package/roles/space-systems-engineer/cli.py +173 -0
  151. package/roles/space-systems-engineer/core/space_systems_core.py +980 -0
  152. package/roles/space-systems-engineer/templates/space-systems-template.md +88 -0
  153. package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +117 -0
  154. package/roles/space-systems-engineer/tests/test_space_systems_engineer_core.py +247 -0
  155. package/roles/state-estimation-engineer/ROLE.md +154 -0
  156. package/roles/state-estimation-engineer/SOURCES.md +27 -0
  157. package/roles/state-estimation-engineer/cli.py +326 -0
  158. package/roles/state-estimation-engineer/core/state_estimation_core.py +958 -0
  159. package/roles/state-estimation-engineer/templates/nav-state-estimator-design-template.md +76 -0
  160. package/roles/state-estimation-engineer/tests/test_bundle_protocol.py +110 -0
  161. package/roles/state-estimation-engineer/tests/test_role_state_estimation_engineer.py +127 -0
  162. package/roles/state-estimation-engineer/tests/test_state_estimation_engineer_core.py +231 -0
  163. package/roles/structures-loads-engineer/ROLE.md +171 -0
  164. package/roles/structures-loads-engineer/SOURCES.md +44 -0
  165. package/roles/structures-loads-engineer/cli.py +219 -0
  166. package/roles/structures-loads-engineer/core/structures_loads_core.py +1112 -0
  167. package/roles/structures-loads-engineer/templates/loads-strength-report-template.md +96 -0
  168. package/roles/structures-loads-engineer/tests/test_bundle_protocol.py +101 -0
  169. package/roles/structures-loads-engineer/tests/test_role_structures_loads_engineer.py +147 -0
  170. package/roles/structures-loads-engineer/tests/test_structures_loads_core.py +261 -0
@@ -0,0 +1,70 @@
1
+ # Guidance Law Design and Assessment Report
2
+
3
+ **Item:** Interceptor terminal homing loop (guided UAV class)
4
+ **Vehicle:** Air-to-air interceptor (UAV class)
5
+ **Status:** draft-for-review
6
+
7
+ ## 1. Scope and engagement definition
8
+
9
+ This report designs and assesses the terminal guidance law for Interceptor terminal homing loop (guided UAV class) (Air-to-air interceptor (UAV class)). Terminal guidance law design and assessment for a guided interceptor engaging an aerial target on a constant-velocity terminal segment.
10
+ The engagement-level requirements and sizing facts are stated project facts (verify against vehicle and target data):
11
+ - Required miss distance: <= 10.000 m.
12
+ - Vehicle lateral acceleration limit: 30 g (294.20 m/s^2).
13
+ - Target maneuver estimate for augmentation sizing: 20.000 m/s^2.
14
+ - Navigation constant band: 3 to 5 (4 baseline).
15
+
16
+ ## 2. Engagement geometry at terminal handover
17
+
18
+ The assessment opens at terminal handover with the interceptor at the origin at 300.000 m/s along +x and the target at 100.000 m/s along +x (project facts). Relative state: position (1000, 100) m, velocity (-200, 0) m/s.
19
+ - Range: 1004.988 m.
20
+ - Closing velocity: 199.007 m/s.
21
+ - Line-of-sight rate: 0.019802 rad/s.
22
+ - Line-of-sight angle: 5.711 deg.
23
+
24
+ ## 3. Guidance law candidates and commands
25
+
26
+ - **Proportional navigation** (terminal law): a_c = N * Vc * lam_dot = 15.763 m/s^2 (1.607 g) at N = 4.
27
+ - **Augmented proportional navigation** (maneuvering target): a_apn = N * (Vc * lam_dot + a_T/2) = 55.763 m/s^2 (5.686 g).
28
+ - **Pursuit family** (comparison): LOS angle 5.711 deg, heading error 5.711 deg; pure pursuit capture requires Vi > Vt (satisfied, Vi = 300 m/s vs Vt = 100 m/s).
29
+ - Pursuit lead for a collision triangle: -1.901 deg; tail-chase intercept time 5.025 s.
30
+
31
+ ## 4. Guidance gain selection
32
+
33
+ The navigation constant is N = 4, the common baseline within the 3-to-5 band used by the bound leaves. Augmented PN uses the same effective ratio N' = 4. Gain selection is a project decision; this report records the command levels produced at the design point.
34
+
35
+ ## 5. Acceleration limit check
36
+
37
+ - PN command utilization: 5.4% of the 30 g limit (within limit).
38
+ - APN command utilization: 19.0% of the 30 g limit (within limit).
39
+
40
+ ## 6. Miss-distance estimate
41
+
42
+ Zero-effort miss at the handover state (both vehicles hold velocity): 100.000 m at t_go = 5.000 s, against the 10.000 m requirement (10.0x the requirement). Guidance authority is required; proportional navigation commands acceleration proportional to the LOS rate and drives it to zero (the collision-course condition).
43
+
44
+ ## 7. Midcourse waypoint steering check
45
+
46
+ - Desired course to waypoint: 26.565 deg; course error 26.565 deg.
47
+ - Turn-rate limit 5 deg/s at 1.000 s steps: commanded turn clamped to 5.000 deg per step.
48
+ - Velocity to be gained along the desired course: 31.672 m/s.
49
+
50
+ ## 8. Findings and open items
51
+
52
+ Findings:
53
+ - PN command is 5.4% of the 30 g lateral-acceleration limit at the terminal handover design point (no saturation margin concern for the PN law).
54
+ - The unguided zero-effort miss is 100 m against the 10 m requirement; guidance authority is required and the PN/APN command levels are consistent with nulling the LOS rate.
55
+ - Augmented PN (APN) raises the command by N*a_T/2 = 40 m/s^2 for the 20 m/s^2 target-maneuver estimate; both laws remain inside the 30 g limit at this design point.
56
+ - Midcourse waypoint steering is turn-rate-limited at 5 deg/s: a 26.6 deg course error is applied at 5 deg per guidance step.
57
+
58
+ Open items before the human guidance lead closes this assessment:
59
+ - Seeker noise and LOS-rate filtering effects on effective navigation ratio (not modeled here).
60
+ - Autopilot lag and airframe response in the homing loop.
61
+ - Target maneuver beyond the 20 m/s^2 sizing estimate.
62
+
63
+ ## 9. Compliance and sign-off boundary
64
+
65
+ - Recommended law: proportional navigation (N = 4), with augmentation (recommended) for the maneuvering-target case.
66
+ - Guidance laws encoded here are paraphrases of the classical laws carried by the bound Aero Agent Skills leaves (gnc-autonomy/guidance); ARP4754A and FAR/CS-25 frame development assurance context (reference only).
67
+ - This document is a DRAFT for human review by the guidance lead. It is not an approval document and not flight software release.
68
+
69
+ ---
70
+ *Generated by Aero Agent Roles guidance-engineer core (2026-09-05). DRAFT for human guidance lead review. Not an approval document. Not flight software release.*
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env python3
2
+ """Test guidance_core: the executable engine of the Guidance Engineer role.
3
+
4
+ Proves the role can DO its job standalone (no AeroSkills needed):
5
+ PN/APN commands, pursuit geometry, waypoint steering, acceleration
6
+ limits, zero-effort miss, report generation, evidence-gate checks, and
7
+ the cli build --bundle + --profile evidence bundle. Every domain anchor
8
+ is the real anchor asserted by the bound leaf gate-3 contract tests.
9
+ """
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+ import tempfile
15
+ import unittest
16
+
17
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
18
+ "..", "core"))
19
+ from guidance_core import ( # noqa: E402
20
+ G0, accel_limit_m_s2, accel_utilization, accel_within_limit,
21
+ apn_command, build_report, capture_possible, check_report,
22
+ check_report_markdown, closing_velocity, commanded_acceleration,
23
+ commanded_accel_g, commanded_heading, course_error, desired_heading,
24
+ example_project, example_report_markdown, guidance_command,
25
+ handover_check, heading_error, intercept_time, lead_angle,
26
+ line_of_sight_angle, line_of_sight_rate, miss_within_requirement,
27
+ render_report_markdown, velocity_to_be_gained, zero_effort_miss,
28
+ )
29
+
30
+ # Real leaf anchors (proportional-navigation leaf gate-3 test):
31
+ # rx=1000, ry=100, vx=-200, vy=0, N=4
32
+ PN_RANGE = 1004.987562112089
33
+ PN_VC = 199.007438042
34
+ PN_LAM = 0.0198019801980198
35
+ PN_ACC = 15.762965390
36
+
37
+ ROLE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
38
+ ROLES_REPO = os.path.dirname(os.path.dirname(ROLE_DIR))
39
+
40
+
41
+ class TestGuidanceLawRules(unittest.TestCase):
42
+
43
+ def test_closing_velocity_anchor(self):
44
+ self.assertAlmostEqual(
45
+ closing_velocity(1000.0, 100.0, -200.0, 0.0), PN_VC, places=4)
46
+
47
+ def test_closing_velocity_pure(self):
48
+ self.assertAlmostEqual(
49
+ closing_velocity(1000.0, 0.0, -200.0, 0.0), 200.0, places=4)
50
+
51
+ def test_los_rate_anchor(self):
52
+ self.assertAlmostEqual(
53
+ line_of_sight_rate(1000.0, 100.0, -200.0, 0.0), PN_LAM, places=4)
54
+
55
+ def test_pn_command_anchor(self):
56
+ a = commanded_acceleration(1000.0, 100.0, -200.0, 0.0, 4.0)
57
+ self.assertAlmostEqual(a, PN_ACC, places=4)
58
+
59
+ def test_pn_linear_in_nav_constant(self):
60
+ a4 = commanded_acceleration(1000.0, 100.0, -200.0, 0.0, 4.0)
61
+ a6 = commanded_acceleration(1000.0, 100.0, -200.0, 0.0, 6.0)
62
+ self.assertAlmostEqual(a6 / a4, 1.5, places=4)
63
+
64
+ def test_pn_zero_on_collision_course(self):
65
+ # Pure closing: lam_dot = 0 -> no lateral command
66
+ self.assertAlmostEqual(
67
+ commanded_acceleration(1000.0, 0.0, -200.0, 0.0, 4.0), 0.0,
68
+ places=6)
69
+
70
+ def test_pn_invalid_inputs_raise(self):
71
+ with self.assertRaises(ValueError):
72
+ commanded_acceleration(0.0, 0.0, -200.0, 0.0, 4.0)
73
+ with self.assertRaises(ValueError):
74
+ commanded_acceleration(1000.0, 100.0, -200.0, 0.0, 0.0)
75
+ with self.assertRaises(ValueError):
76
+ commanded_acceleration(1000.0, 100.0, -200.0, 0.0, -2.0)
77
+
78
+ def test_guidance_command_bundle(self):
79
+ cmd = guidance_command(1000.0, 100.0, -200.0, 0.0, n_nav=4.0)
80
+ self.assertAlmostEqual(cmd["range"], PN_RANGE, places=4)
81
+ self.assertAlmostEqual(cmd["accel_cmd"], PN_ACC, places=4)
82
+ self.assertEqual(cmd["n_nav"], 4.0)
83
+
84
+ def test_apn_augmentation_term(self):
85
+ # a_apn = a_pn + N*a_T/2: with N=4, a_T=20 -> +40 m/s^2
86
+ vc = closing_velocity(1000.0, 100.0, -200.0, 0.0)
87
+ lam = line_of_sight_rate(1000.0, 100.0, -200.0, 0.0)
88
+ apn = apn_command(4.0, vc, lam, 20.0)
89
+ self.assertAlmostEqual(apn, PN_ACC + 40.0, places=4)
90
+
91
+ def test_accel_in_g(self):
92
+ self.assertAlmostEqual(commanded_accel_g(PN_ACC), PN_ACC / G0,
93
+ places=6)
94
+
95
+
96
+ class TestPursuitGeometry(unittest.TestCase):
97
+
98
+ def test_los_angle(self):
99
+ lam = line_of_sight_angle(1000.0, 100.0)
100
+ self.assertAlmostEqual(lam, 0.09966865249, places=6) # 5.7106 deg
101
+
102
+ def test_heading_error(self):
103
+ eta = heading_error(0.0, 1000.0, 100.0)
104
+ self.assertAlmostEqual(eta, 0.09966865249, places=6)
105
+
106
+ def test_lead_angle_speed_ratio(self):
107
+ # beta = -5.7106 deg, Vt/Vi = 1/3 -> lead ~ -1.9007 deg
108
+ lead = lead_angle(100.0, 300.0, -0.09966865249)
109
+ self.assertAlmostEqual(lead, -0.03317399, places=6)
110
+
111
+ def test_capture_condition(self):
112
+ self.assertTrue(capture_possible(300.0, 100.0))
113
+ self.assertFalse(capture_possible(90.0, 100.0))
114
+
115
+ def test_tail_chase_intercept_time(self):
116
+ # r = 1000, Vi = 300, Vt = 250 -> t = 20 s exactly
117
+ self.assertAlmostEqual(intercept_time(1000.0, 300.0, 250.0), 20.0,
118
+ places=6)
119
+ with self.assertRaises(ValueError):
120
+ intercept_time(1000.0, 100.0, 300.0) # never closes
121
+
122
+
123
+ class TestWaypointSteering(unittest.TestCase):
124
+
125
+ def test_desired_heading_anchor(self):
126
+ # (0,0) -> (1000,500) = 26.565 deg (leaf anchor)
127
+ psi_d = desired_heading((0.0, 0.0), (1000.0, 500.0))
128
+ self.assertAlmostEqual(psi_d, 0.4636476090, places=6)
129
+ self.assertAlmostEqual(psi_d, 26.5650511771 * 0.0174532925, places=3)
130
+
131
+ def test_course_error_wraps(self):
132
+ # heading 45 deg vs 26.565 deg course -> -18.435 deg (leaf anchor)
133
+ e = course_error((0.0, 0.0), (1000.0, 500.0), 0.7853981634)
134
+ self.assertAlmostEqual(e, -0.3217505544, places=6)
135
+
136
+ def test_turn_rate_limit_clamps(self):
137
+ # 5 deg/s, 1 s step: 26.565 deg error applied at 5 deg/step
138
+ psi_c = commanded_heading((0.0, 0.0), (1000.0, 500.0), 0.0,
139
+ 0.0872664626, 1.0)
140
+ self.assertAlmostEqual(psi_c, 0.0872664626, places=6)
141
+
142
+ def test_turn_rate_full_error_passes(self):
143
+ # 30 deg/s, 1 s step: full error passes -> psi_c = psi_d
144
+ psi_c = commanded_heading((0.0, 0.0), (1000.0, 500.0), 0.0,
145
+ 0.5235987756, 1.0)
146
+ self.assertAlmostEqual(psi_c, 0.4636476090, places=6)
147
+
148
+ def test_velocity_to_be_gained_anchor(self):
149
+ # leaf anchor: V=250, e=20 deg, V_target=300 -> 65.08 m/s
150
+ vgo = velocity_to_be_gained(250.0, 0.3490658504, 300.0)
151
+ self.assertAlmostEqual(vgo, 65.0768, places=2)
152
+
153
+
154
+ class TestMissDistanceAndLimits(unittest.TestCase):
155
+
156
+ def test_zem_leaf_anchor(self):
157
+ # interceptor (0,0) at 300 m/s +x vs stationary target (6000,150)
158
+ # -> t_go = 20 s, ZEM = 150 m (leaf anchor)
159
+ zem = zero_effort_miss((0.0, 0.0), (300.0, 0.0),
160
+ (6000.0, 150.0), (0.0, 0.0))
161
+ self.assertAlmostEqual(zem["time_to_go"], 20.0, places=6)
162
+ self.assertAlmostEqual(zem["zem"], 150.0, places=6)
163
+
164
+ def test_zem_zero_on_collision(self):
165
+ zem = zero_effort_miss((0.0, 0.0), (300.0, 0.0),
166
+ (9000.0, 0.0), (100.0, 0.0))
167
+ self.assertAlmostEqual(zem["zem"], 0.0, places=6)
168
+
169
+ def test_handover_check(self):
170
+ self.assertTrue(handover_check((0.0, 0.0), (8000.0, 0.0), 8000.0))
171
+ self.assertFalse(handover_check((0.0, 0.0), (9000.0, 0.0), 8000.0))
172
+
173
+ def test_accel_limit_and_utilization(self):
174
+ self.assertAlmostEqual(accel_limit_m_s2(30.0), 30.0 * G0, places=6)
175
+ # example PN command 15.763 m/s^2 -> 5.36% of a 30 g limit
176
+ util = accel_utilization(15.76296539, 30.0)
177
+ self.assertAlmostEqual(util, 0.0535792, places=6)
178
+ self.assertTrue(accel_within_limit(15.76296539, 30.0))
179
+ self.assertFalse(accel_within_limit(5000.0, 30.0))
180
+
181
+ def test_miss_requirement(self):
182
+ self.assertTrue(miss_within_requirement(5.0, 10.0))
183
+ self.assertFalse(miss_within_requirement(100.0, 10.0))
184
+
185
+
186
+ class TestReportBuilder(unittest.TestCase):
187
+
188
+ def test_example_report_model(self):
189
+ model = build_report(example_project())
190
+ self.assertEqual(model["recommended_law"], "proportional navigation")
191
+ self.assertEqual(model["n_nav"], 4.0)
192
+ self.assertEqual(model["status"], "draft-for-review")
193
+ self.assertAlmostEqual(model["pn"]["accel_pn_m_s2"], PN_ACC, places=4)
194
+ self.assertAlmostEqual(model["pn"]["accel_apn_m_s2"],
195
+ PN_ACC + 40.0, places=4)
196
+ self.assertAlmostEqual(model["miss"]["zem_m"], 100.0, places=4)
197
+ self.assertAlmostEqual(model["miss"]["time_to_go_s"], 5.0, places=4)
198
+
199
+ def test_example_has_all_sections(self):
200
+ md = example_report_markdown()
201
+ for n in range(1, 10):
202
+ self.assertIn("## %d. " % n, md, "section %d missing" % n)
203
+ self.assertIn("not an approval", md.lower())
204
+ self.assertIn("not flight software release", md.lower())
205
+
206
+ def test_core_gates_all_pass(self):
207
+ model = build_report(example_project())
208
+ gates = check_report(model)
209
+ self.assertTrue(gates["all_pass"], gates)
210
+
211
+ def test_markdown_gates_all_pass(self):
212
+ md = example_report_markdown()
213
+ gates = check_report_markdown(md)
214
+ self.assertTrue(gates["all_pass"], gates)
215
+
216
+ def test_standalone_no_skills_repo(self):
217
+ os.environ["AEROSKILLS_DEV"] = "/nonexistent"
218
+ model = build_report(example_project())
219
+ self.assertEqual(model["status"], "draft-for-review")
220
+ md = render_report_markdown(model)
221
+ self.assertGreater(len(md), 1000)
222
+
223
+ def test_cli_build_bundle_standalone(self):
224
+ """cli build --bundle exits 0 and emits valid evidence (PROTOCOL)."""
225
+ with tempfile.TemporaryDirectory() as td:
226
+ out = os.path.join(td, "report.md")
227
+ env = dict(os.environ, AEROSKILLS_DEV="/nonexistent")
228
+ r = subprocess.run(
229
+ [sys.executable, os.path.join(ROLE_DIR, "cli.py"), "build",
230
+ "--out", out, "--bundle"],
231
+ cwd=ROLE_DIR, env=env, capture_output=True, text=True)
232
+ self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
233
+ ev = os.path.join(td, "evidence")
234
+ for name in ("model.json", "gates.json", "provenance.json"):
235
+ self.assertTrue(os.path.exists(os.path.join(ev, name)),
236
+ name + " missing")
237
+ gates = json.load(open(os.path.join(ev, "gates.json")))
238
+ self.assertTrue(gates["all_pass"])
239
+ self.assertEqual(gates["exit_code"], 0)
240
+ model = json.load(open(os.path.join(ev, "model.json")))
241
+ self.assertEqual(model["status"], "draft-for-review")
242
+ self.assertEqual(model["miss"]["zem_m"], 100.0)
243
+ prov = json.load(open(os.path.join(ev, "provenance.json")))
244
+ self.assertGreaterEqual(len(prov["skills"]), 1)
245
+ # standalone: every row reports no dispatch, no crash
246
+ for row in prov["skills"]:
247
+ self.assertFalse(row["dispatched"])
248
+
249
+ def test_cli_build_with_profile(self):
250
+ """--profile tailors the deliverable and still passes gates."""
251
+ profile = os.path.join(ROLES_REPO, "profiles", "example-airframer.json")
252
+ if not os.path.exists(profile):
253
+ self.skipTest("example profile not present")
254
+ with tempfile.TemporaryDirectory() as td:
255
+ out = os.path.join(td, "report.md")
256
+ env = dict(os.environ, AEROSKILLS_DEV="/nonexistent")
257
+ r = subprocess.run(
258
+ [sys.executable, os.path.join(ROLE_DIR, "cli.py"), "build",
259
+ "--out", out, "--profile", profile],
260
+ cwd=ROLE_DIR, env=env, capture_output=True, text=True)
261
+ self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
262
+ md = open(out).read()
263
+ self.assertIn("Program context (profile)", md)
264
+ gates = check_report_markdown(md)
265
+ self.assertTrue(gates["all_pass"], gates)
266
+
267
+
268
+ if __name__ == "__main__":
269
+ unittest.main()
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env python3
2
+ """Role test: Guidance Engineer.
3
+
4
+ Verifies (offline, no network):
5
+ 1. Every skill bound in ROLE.md frontmatter RESOLVES to a real leaf in
6
+ the aero-agent-skills repo (the pinned skills_release band).
7
+ 2. The workflow stage order is deterministic and complete.
8
+ 3. The deliverable template is the FILLED worked example for the
9
+ reference item (zero blanks, draft markers) and passes the engine's
10
+ own markdown gates.
11
+ """
12
+ import os
13
+ import re
14
+ import sys
15
+ import unittest
16
+
17
+ ROLES_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
18
+ os.path.abspath(__file__))))) # repo root (4 up from tests/)
19
+ # Bound-skill resolution requires the Aero Agent Skills checkout. It is a
20
+ # cross-repo dev check: when the skills repo is absent (fresh public clone),
21
+ # skip resolution rather than fail - the role's bound list is verified in CI.
22
+ AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
23
+ HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
24
+ ROLE_DIR = os.path.join(ROLES_REPO, "roles", "guidance-engineer")
25
+ TEMPLATE = os.path.join(ROLE_DIR, "templates", "guidance-report-template.md")
26
+ CORE_DIR = os.path.join(ROLE_DIR, "core")
27
+
28
+ sys.path.insert(0, CORE_DIR)
29
+ import guidance_core # noqa: E402
30
+
31
+ EXPECTED_BOUND = [
32
+ "gnc-autonomy/guidance/proportional-navigation",
33
+ "gnc-autonomy/guidance/augmented-proportional-navigation",
34
+ "gnc-autonomy/guidance/pursuit-guidance",
35
+ "gnc-autonomy/guidance/collision-course-guidance",
36
+ "gnc-autonomy/guidance/command-to-line-of-sight",
37
+ "gnc-autonomy/guidance/midcourse-guidance",
38
+ "gnc-autonomy/guidance/impact-point-prediction",
39
+ "gnc-autonomy/guidance/dubins-path-planning",
40
+ "gnc-autonomy/guidance/coverage-path-planning",
41
+ ]
42
+
43
+ STAGES = [
44
+ "1. Engagement definition",
45
+ "2. Law trade space",
46
+ "3. Gain sizing",
47
+ "4. Acceleration limit check",
48
+ "5. Miss-distance estimate",
49
+ "6. Midcourse steering",
50
+ "7. Report and gates",
51
+ ]
52
+
53
+
54
+ def role_text():
55
+ p = os.path.join(ROLE_DIR, "ROLE.md")
56
+ if not os.path.exists(p):
57
+ return None
58
+ with open(p) as f:
59
+ return f.read()
60
+
61
+
62
+ class TestGuidanceEngineerRole(unittest.TestCase):
63
+
64
+ def test_bound_skills_resolve(self):
65
+ if not HAS_SKILLS:
66
+ self.skipTest("skills checkout absent (cross-repo dev check)")
67
+ for leaf in EXPECTED_BOUND:
68
+ self.assertTrue(
69
+ os.path.exists(os.path.join(AEROSKILLS, "skills", leaf,
70
+ "SKILL.md")),
71
+ "bound skill missing: %s" % leaf)
72
+
73
+ def test_stages_ordered(self):
74
+ role = role_text()
75
+ self.assertIsNotNone(role)
76
+ wf = role[role.find("## Workflow"):role.find("## Evidence gates")]
77
+ idx = [wf.find(s) for s in STAGES]
78
+ self.assertTrue(all(i >= 0 for i in idx), "stage missing in workflow")
79
+ self.assertEqual(idx, sorted(idx), "stage order not deterministic")
80
+
81
+ def test_template_present(self):
82
+ self.assertTrue(os.path.exists(TEMPLATE),
83
+ "deliverable template missing")
84
+
85
+ def test_template_is_filled_deliverable(self):
86
+ """100% standard: template is the FILLED worked example - all 9
87
+ numbered sections, zero blank fields, draft markers."""
88
+ self.assertTrue(os.path.exists(TEMPLATE))
89
+ with open(TEMPLATE) as f:
90
+ text = f.read()
91
+ for n in range(1, 10):
92
+ self.assertTrue(
93
+ re.search(r"^## %d\. " % n, text, re.M),
94
+ "report section %d missing" % n)
95
+ self.assertNotIn("___", text)
96
+ low = text.lower()
97
+ self.assertIn("draft", low)
98
+ self.assertIn("not an approval", low)
99
+ self.assertIn("not flight software release", low)
100
+ # key computed numbers of the worked example are present
101
+ self.assertIn("15.763", text) # PN command m/s^2
102
+ self.assertIn("100.000", text) # zero-effort miss m
103
+ self.assertIn("26.565", text) # waypoint desired course deg
104
+
105
+ def test_template_passes_core_gates(self):
106
+ """The shipped template must pass the engine's own markdown
107
+ evidence gates (standalone core, no skills repo needed)."""
108
+ with open(TEMPLATE) as f:
109
+ text = f.read()
110
+ gates = guidance_core.check_report_markdown(text)
111
+ self.assertTrue(gates["all_pass"], gates)
112
+
113
+ def test_core_and_cli_present(self):
114
+ self.assertTrue(os.path.exists(os.path.join(CORE_DIR,
115
+ "guidance_core.py")),
116
+ "core/guidance_core.py missing (100% standard)")
117
+ self.assertTrue(os.path.exists(os.path.join(ROLE_DIR, "cli.py")),
118
+ "cli.py missing (100% standard)")
119
+
120
+ def test_core_standalone_example_gates(self):
121
+ """Core produces a correct, gate-passing report with no skills."""
122
+ os.environ["AEROSKILLS_DEV"] = "/nonexistent"
123
+ model = guidance_core.build_report(guidance_core.example_project())
124
+ gates = guidance_core.check_report(model)
125
+ self.assertTrue(gates["all_pass"], gates)
126
+ md = guidance_core.example_report_markdown()
127
+ mgates = guidance_core.check_report_markdown(md)
128
+ self.assertTrue(mgates["all_pass"], mgates)
129
+
130
+ def test_boundaries(self):
131
+ role = role_text()
132
+ self.assertIsNotNone(role)
133
+ for phrase in ["sign_off_required: true", "forbidden"]:
134
+ self.assertIn(phrase, role)
135
+ for phrase in ["declare the weapon or vehicle ready for guided "
136
+ "release", "approve flight of the guidance software",
137
+ "claim certification approval"]:
138
+ self.assertIn(phrase, role, "missing boundary: %s" % phrase)
139
+
140
+ def test_sources(self):
141
+ self.assertTrue(os.path.exists(os.path.join(ROLE_DIR, "SOURCES.md")),
142
+ "SOURCES.md missing")
143
+
144
+
145
+ if __name__ == "__main__":
146
+ unittest.main()
@@ -0,0 +1,161 @@
1
+ ---
2
+ type: role
3
+ name: high-speed-aerodynamics-engineer
4
+ title: "High-Speed Aerodynamics Engineer"
5
+ status: draft
6
+ domain: aerodynamics
7
+ deliverable_type: "High-Speed Aerodynamic Analysis Memo"
8
+ standards_bound:
9
+ - id: naca-tr-824
10
+ tier: TIER-2
11
+ reference-only: true
12
+ skills_bound:
13
+ - aerodynamics/high-speed/isentropic-flow-relations
14
+ - aerodynamics/high-speed/normal-shock
15
+ - aerodynamics/high-speed/oblique-shock
16
+ - aerodynamics/high-speed/prandtl-meyer
17
+ - aerodynamics/high-speed/regular-shock-reflection
18
+ - aerodynamics/high-speed/shock-expansion-airfoil
19
+ - aerodynamics/high-speed/transonic-similarity
20
+ - aerodynamics/high-speed/swept-wing-aerodynamics
21
+ - aerodynamics/high-speed/supercritical-airfoil
22
+ - aerodynamics/high-speed/wave-drag-area-rule
23
+ - aerodynamics/high-speed/bow-shock-standoff
24
+ - aerodynamics/high-speed/aerodynamic-heating
25
+ - aerodynamics/high-speed/flat-plate-skin-friction-heating
26
+ - aerodynamics/high-speed/hypersonic-flow
27
+ - aerodynamics/boundary-layer/boundary-layer-theory
28
+ - aerodynamics/boundary-layer/boundary-layer-transition
29
+ - aerodynamics/boundary-layer/boundary-layer-separation
30
+ - aerodynamics/boundary-layer/rough-wall-skin-friction
31
+ - aerodynamics/boundary-layer/stagnation-flow-boundary-layer
32
+ tools_allowed: [stdlib, offline-file-processing]
33
+ forbidden:
34
+ - "claim certification approval"
35
+ - "declare a compliance finding"
36
+ - "reproduce proprietary standard text"
37
+ sign_off_required: true
38
+ license: Apache-2.0
39
+ compatibility: "agentskills.io ROLE.md; any SKILL.md host"
40
+ metadata:
41
+ version: 0.1.0
42
+ author: ashfordeOU
43
+ skills_release: "v1.3.0+"
44
+ ---
45
+
46
+ # High-Speed Aerodynamics Engineer
47
+
48
+ ## Role identity
49
+
50
+ Owns the compressible-flow analysis of a transonic/supersonic vehicle
51
+ at a flight condition: isentropic total conditions, Mach-area and
52
+ inlet capture, Prandtl-Glauert / Karman-Tsien compressibility
53
+ corrections, normal and oblique shock systems, Prandtl-Meyer
54
+ expansion, shock-expansion section analysis, transonic drag
55
+ divergence and wave drag, boundary-layer thickness/transition/skin
56
+ friction at high speed, stagnation-line heating, and bow-shock
57
+ standoff. Use when a high-speed vehicle's flight condition must
58
+ demonstrate its compressible-flow behavior with numbers (total
59
+ pressure recovery, shock attachment margins, critical/divergence
60
+ Mach, boundary-layer state, heating screen). Do NOT use for
61
+ subsonic airfoil/wing design (see aerodynamics-engineer), engine
62
+ thermodynamics (see propulsion-engineer), or aeroelastic clearance
63
+ (see structures/loads roles).
64
+
65
+ ## Deliverable contract
66
+
67
+ 1. **High-Speed Aerodynamic Analysis Memo** - one flight condition of
68
+ a transonic/supersonic vehicle: flow state, total conditions,
69
+ shock-system geometry and losses, transonic limits, boundary
70
+ layer, and heating screen with the real compressible-flow numbers.
71
+ 2. **Margin summary** - each margin (intake recovery, shock
72
+ attachment, drag-divergence, critical Mach, kinetic-heating) with
73
+ the workflow stage that produced its source numbers.
74
+ 3. **Evidence bundle** - model/gates/provenance JSON when run with
75
+ `--bundle` (docs/PROTOCOL.md); dispatch cross-checks the core
76
+ against the bound AeroSkills leaf logic when AeroSkills is present.
77
+
78
+ ## Workflow (stages -> bound skills)
79
+
80
+ | Stage | Loads skill | Produces |
81
+ |---|---|---|
82
+ | 1. Flow-state setup | (ISA, offline) | atmosphere, V, q, Reynolds environment |
83
+ | 2. Isentropic relations | isentropic-flow-relations | T0/T, p0/p, rho0/rho, A/A*, M-from-area, choked flow |
84
+ | 3. Compressibility corrections | transonic-similarity + swept-wing-aerodynamics | P-G and Karman-Tsien Cp, Cp*, section M_cr, M_eff = M cos(Lambda) |
85
+ | 4. Shock system | normal-shock + oblique-shock + regular-shock-reflection | nose normal shock, ramp oblique shock, reflection verdict |
86
+ | 5. Expansion | prandtl-meyer | aft-body/nozzle expansion state |
87
+ | 6. Supersonic section | shock-expansion-airfoil | diamond-airfoil cl/cd_wave/cm_le |
88
+ | 7. Transonic limit | supercritical-airfoil + wave-drag-area-rule | Korn M_DD, terminating shock, Sears-Haack zero-lift wave drag |
89
+ | 8. Boundary layer | boundary-layer-theory + boundary-layer-transition | flat-plate delta/delta*/theta/Cf, transition x_tr |
90
+ | 9. BL separation screen | boundary-layer-separation | Thwaites/Stratford margin when a pressure history is given |
91
+ | 10. Surface state | rough-wall-skin-friction + stagnation-flow-boundary-layer | k+ regime, Hiemenz attachment-line layer |
92
+ | 11. Heating screen | aerodynamic-heating + flat-plate-skin-friction-heating + bow-shock-standoff + hypersonic-flow | Sutton-Graves flux, T_aw, cold-wall flux, nose standoff |
93
+ | 12. Memo | (all above) | the High-Speed Aerodynamic Analysis Memo |
94
+
95
+ ## Evidence gates
96
+
97
+ - Stage 2 done = total conditions and the Mach-area relation produce
98
+ real numbers with a documented isentropic anchor (p0/p and A/A* at
99
+ the flight Mach).
100
+ - Stage 4 done = normal shock decelerates to subsonic (M2 < 1), the
101
+ intake ramp oblique shock is attached (theta < theta_max), and the
102
+ reflection verdict (regular/mach) is stated.
103
+ - Stage 7 done = drag-divergence Mach (Korn) and the wave drag area
104
+ are stated for the vehicle's section and equivalent body.
105
+ - Stage 8 done = flat-plate boundary-layer thickness, skin friction
106
+ and transition location are stated at the cruise station.
107
+ - FINAL = every margin in the memo traces to a stage output and the
108
+ memo is DRAFT for human review (never an approval).
109
+
110
+ ## Boundary / forbidden
111
+
112
+ - NEVER claim certification approval or a compliance finding.
113
+ - NEVER reproduce proprietary standard text (data references are
114
+ summary + cite).
115
+ - Heating numbers are a screening input for thermal-structures work;
116
+ kinetic-heating margins use recovery-temperature ceilings, not a
117
+ materials approval. Drag-divergence and sweep numbers are
118
+ section-level engineering estimates (Korn/Karman-Tsien/Blasius
119
+ class), not CFD or wind-tunnel results.
120
+
121
+ ## Verification
122
+
123
+ The role runs STANDALONE: `core/high_speed_aero_core.py` is an
124
+ executable engine that computes the memo numbers with the real
125
+ compressible-flow formulas of the bound AeroSkills leaves (isentropic
126
+ relations, Prandtl-Glauert/Karman-Tsien, normal/oblique shock,
127
+ Prandtl-Meyer, shock-expansion airfoil, Korn rule, Sears-Haack,
128
+ flat-plate boundary layer, Michel transition, Hiemenz attachment
129
+ line, Sutton-Graves heating, Billig standoff), BUILDS the memo, and
130
+ gate-checks deliverables. No AeroSkills checkout required.
131
+
132
+ Run the role:
133
+ ```bash
134
+ python3 cli.py build --out memo.md # reference flight condition
135
+ python3 cli.py build --mach 2.20 --altitude 18288 # Mach override
136
+ python3 cli.py check --file memo.md # gate-check a memo
137
+ ```
138
+
139
+ Tests:
140
+ - tests/test_high_speed_aero_core.py: formula correctness vs known
141
+ leaf anchors (isentropic ratios at M2, A/A* = 1.6875, normal shock
142
+ at M2, oblique shock at (M2, 10 deg), Prandtl-Meyer nu(2), KT
143
+ correction, Korn M_DD, boundary-layer thickness/transition,
144
+ stagnation heating), memo builder + gates + markdown completeness +
145
+ STANDALONE (no skills repo)
146
+ - tests/test_role_high_speed_aerodynamics.py: bound-skill resolution
147
+ (skips if the skills repo is absent) + workflow + template +
148
+ boundaries
149
+
150
+ Templates: templates/high-speed-memo-template.md is the FILLED
151
+ generated memo for the reference supersonic cruiser flight condition
152
+ (zero blank fields) - exactly what `cli.py build` produces.
153
+
154
+ Bound skills in Aero Agent Skills deepen individual stages when the
155
+ library is present; the core engine does not depend on them.
156
+
157
+ ## Compliance
158
+
159
+ - naca-tr-824 TIER-2 reference-only for the compressible-flow data
160
+ relations; data references summary-not-copy per STANDARDS.md.
161
+ - SOURCES.md records standards referenced.
@@ -0,0 +1,18 @@
1
+ # SOURCES.md - High-Speed Aerodynamics Engineer
2
+
3
+ | Source | Role use | Gated |
4
+ |---|---|---|
5
+ | NACA TR-824 | compressible-flow relations data source (reference-only) | true |
6
+ | Anderson, Modern Compressible Flow (textbook) | shock/expansion anchors (M2: p2/p1 4.5, oblique (M2,10deg) example) | true |
7
+ | Korn rule (supercritical airfoil practice) | drag-divergence Mach estimate | false |
8
+ | Whitcomb area rule / Sears-Haack (NACA Report 1273) | zero-lift wave drag estimate | false |
9
+ | Blasius / Schlichting flat-plate boundary layer | laminar/turbulent thickness and skin friction | false |
10
+ | Michel transition criterion / Thwaites method | transition location estimate | false |
11
+ | Sutton-Graves stagnation heating correlation | stagnation-point heat flux screen | false |
12
+ | Billig bow-shock standoff correlations | nose standoff estimate | false |
13
+ | ISA standard atmosphere | flight-condition state | false |
14
+
15
+ Summary-not-copy per STANDARDS.md. Data references cite sources, never
16
+ reproduce them. All numbers in the memo are engineering estimates
17
+ from these methods - screening inputs, not CFD/wind-tunnel results and
18
+ never an approval.