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.
- package/NOTICE +18 -0
- package/README.md +49 -0
- package/bin/aero-agent-roles.js +127 -0
- package/lib/catalog.js +97 -0
- package/lib/install.js +48 -0
- package/lib/mcp.js +199 -0
- package/manifest.json +516 -0
- package/package.json +44 -0
- package/roles/aerodynamics-engineer/ROLE.md +153 -0
- package/roles/aerodynamics-engineer/SOURCES.md +12 -0
- package/roles/aerodynamics-engineer/cli.py +143 -0
- package/roles/aerodynamics-engineer/core/aerodynamics_core.py +1218 -0
- package/roles/aerodynamics-engineer/templates/aero-design-report-template.md +72 -0
- package/roles/aerodynamics-engineer/tests/test_aerodynamics_core.py +227 -0
- package/roles/aerodynamics-engineer/tests/test_role_aerodynamics.py +94 -0
- package/roles/aircraft-design-engineer/ROLE.md +138 -0
- package/roles/aircraft-design-engineer/SOURCES.md +12 -0
- package/roles/aircraft-design-engineer/cli.py +152 -0
- package/roles/aircraft-design-engineer/core/aircraft_design_core.py +1278 -0
- package/roles/aircraft-design-engineer/templates/concept-design-template.md +99 -0
- package/roles/aircraft-design-engineer/tests/test_aircraft_design_core.py +269 -0
- package/roles/aircraft-design-engineer/tests/test_role_aircraft_design_engineer.py +106 -0
- package/roles/airworthiness-compliance-engineer/ROLE.md +122 -0
- package/roles/airworthiness-compliance-engineer/SOURCES.md +16 -0
- package/roles/airworthiness-compliance-engineer/cli.py +146 -0
- package/roles/airworthiness-compliance-engineer/core/airworthiness_core.py +894 -0
- package/roles/airworthiness-compliance-engineer/templates/compliance-matrix-template.md +46 -0
- package/roles/airworthiness-compliance-engineer/tests/test_airworthiness_core.py +224 -0
- package/roles/airworthiness-compliance-engineer/tests/test_role_airworthiness.py +85 -0
- package/roles/as9100-quality-auditor/ROLE.md +128 -0
- package/roles/as9100-quality-auditor/SOURCES.md +16 -0
- package/roles/as9100-quality-auditor/cli.py +136 -0
- package/roles/as9100-quality-auditor/core/as9100_core.py +857 -0
- package/roles/as9100-quality-auditor/templates/findings-report-template.md +119 -0
- package/roles/as9100-quality-auditor/tests/test_as9100_cli_profile.py +105 -0
- package/roles/as9100-quality-auditor/tests/test_as9100_core.py +204 -0
- package/roles/as9100-quality-auditor/tests/test_role_as9100.py +90 -0
- package/roles/data-bus-avionics-engineer/ROLE.md +146 -0
- package/roles/data-bus-avionics-engineer/SOURCES.md +23 -0
- package/roles/data-bus-avionics-engineer/cli.py +311 -0
- package/roles/data-bus-avionics-engineer/core/data_bus_avionics_core.py +1121 -0
- package/roles/data-bus-avionics-engineer/templates/bus-assessment-template.md +134 -0
- package/roles/data-bus-avionics-engineer/tests/test_bundle_protocol.py +137 -0
- package/roles/data-bus-avionics-engineer/tests/test_data_bus_avionics_core.py +261 -0
- package/roles/data-bus-avionics-engineer/tests/test_role_data_bus_avionics_engineer.py +121 -0
- package/roles/do160-environmental-engineer/ROLE.md +136 -0
- package/roles/do160-environmental-engineer/SOURCES.md +30 -0
- package/roles/do160-environmental-engineer/cli.py +147 -0
- package/roles/do160-environmental-engineer/core/do160_environmental_core.py +1246 -0
- package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md +114 -0
- package/roles/do160-environmental-engineer/tests/test_do160_environmental_core.py +312 -0
- package/roles/do160-environmental-engineer/tests/test_role_do160_environmental_engineer.py +281 -0
- package/roles/do178c-cert-engineer/ROLE.md +120 -0
- package/roles/do178c-cert-engineer/SOURCES.md +16 -0
- package/roles/do178c-cert-engineer/cli.py +129 -0
- package/roles/do178c-cert-engineer/core/do178c_core.py +411 -0
- package/roles/do178c-cert-engineer/templates/psac-template.md +74 -0
- package/roles/do178c-cert-engineer/tests/test_do178c_cert_engineer_core.py +91 -0
- package/roles/do178c-cert-engineer/tests/test_role_do178c.py +109 -0
- package/roles/do254-hardware-engineer/ROLE.md +127 -0
- package/roles/do254-hardware-engineer/SOURCES.md +19 -0
- package/roles/do254-hardware-engineer/cli.py +308 -0
- package/roles/do254-hardware-engineer/core/do254_hardware_core.py +761 -0
- package/roles/do254-hardware-engineer/templates/phac-template.md +87 -0
- package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +152 -0
- package/roles/do254-hardware-engineer/tests/test_do254_hardware_core.py +200 -0
- package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +108 -0
- package/roles/engineering-analysis-engineer/ROLE.md +154 -0
- package/roles/engineering-analysis-engineer/SOURCES.md +15 -0
- package/roles/engineering-analysis-engineer/cli.py +151 -0
- package/roles/engineering-analysis-engineer/core/engineering_analysis_engineer_core.py +866 -0
- package/roles/engineering-analysis-engineer/templates/analysis-memo-template.md +73 -0
- package/roles/engineering-analysis-engineer/tests/test_engineering_analysis_engineer_core.py +367 -0
- package/roles/engineering-analysis-engineer/tests/test_role_engineering_analysis_engineer.py +113 -0
- package/roles/flight-management-engineer/ROLE.md +160 -0
- package/roles/flight-management-engineer/SOURCES.md +30 -0
- package/roles/flight-management-engineer/cli.py +258 -0
- package/roles/flight-management-engineer/core/flight_management_core.py +1473 -0
- package/roles/flight-management-engineer/templates/route-assessment-template.md +94 -0
- package/roles/flight-management-engineer/tests/test_flight_management_core.py +437 -0
- package/roles/flight-management-engineer/tests/test_role_flight_management_engineer.py +123 -0
- package/roles/flight-mechanics-engineer/ROLE.md +162 -0
- package/roles/flight-mechanics-engineer/SOURCES.md +51 -0
- package/roles/flight-mechanics-engineer/cli.py +149 -0
- package/roles/flight-mechanics-engineer/core/flight_mechanics_core.py +1507 -0
- package/roles/flight-mechanics-engineer/templates/perf-sc-report-template.md +82 -0
- package/roles/flight-mechanics-engineer/tests/test_flight_mechanics_core.py +277 -0
- package/roles/flight-mechanics-engineer/tests/test_role_flight_mechanics_engineer.py +141 -0
- package/roles/flight-test-engineer/ROLE.md +137 -0
- package/roles/flight-test-engineer/SOURCES.md +24 -0
- package/roles/flight-test-engineer/cli.py +135 -0
- package/roles/flight-test-engineer/core/flight_test_core.py +931 -0
- package/roles/flight-test-engineer/templates/flight-test-template.md +143 -0
- package/roles/flight-test-engineer/tests/test_flight_test_engineer_core.py +227 -0
- package/roles/flight-test-engineer/tests/test_role_flight_test_engineer.py +150 -0
- package/roles/flight-test-performance-engineer/ROLE.md +165 -0
- package/roles/flight-test-performance-engineer/SOURCES.md +20 -0
- package/roles/flight-test-performance-engineer/cli.py +319 -0
- package/roles/flight-test-performance-engineer/core/flight_test_performance_core.py +1398 -0
- package/roles/flight-test-performance-engineer/templates/performance-report-template.md +105 -0
- package/roles/flight-test-performance-engineer/tests/test_flight_test_performance_engineer_core.py +308 -0
- package/roles/flight-test-performance-engineer/tests/test_role_flight_test_performance_engineer.py +165 -0
- package/roles/gnc-engineer/ROLE.md +141 -0
- package/roles/gnc-engineer/SOURCES.md +35 -0
- package/roles/gnc-engineer/cli.py +141 -0
- package/roles/gnc-engineer/core/gnc_core.py +1074 -0
- package/roles/gnc-engineer/templates/gnc-report-template.md +105 -0
- package/roles/gnc-engineer/tests/test_gnc_engineer_core.py +238 -0
- package/roles/gnc-engineer/tests/test_role_gnc_engineer.py +142 -0
- package/roles/guidance-engineer/ROLE.md +156 -0
- package/roles/guidance-engineer/SOURCES.md +43 -0
- package/roles/guidance-engineer/cli.py +280 -0
- package/roles/guidance-engineer/core/guidance_core.py +820 -0
- package/roles/guidance-engineer/templates/guidance-report-template.md +70 -0
- package/roles/guidance-engineer/tests/test_guidance_engineer_core.py +269 -0
- package/roles/guidance-engineer/tests/test_role_guidance_engineer.py +146 -0
- package/roles/high-speed-aerodynamics-engineer/ROLE.md +161 -0
- package/roles/high-speed-aerodynamics-engineer/SOURCES.md +18 -0
- package/roles/high-speed-aerodynamics-engineer/cli.py +282 -0
- package/roles/high-speed-aerodynamics-engineer/core/high_speed_aero_core.py +1566 -0
- package/roles/high-speed-aerodynamics-engineer/templates/high-speed-memo-template.md +75 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_bundle_protocol.py +103 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_high_speed_aero_core.py +280 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_role_high_speed_aerodynamics.py +125 -0
- package/roles/ndt-engineer/ROLE.md +157 -0
- package/roles/ndt-engineer/SOURCES.md +18 -0
- package/roles/ndt-engineer/cli.py +316 -0
- package/roles/ndt-engineer/core/ndt_core.py +1590 -0
- package/roles/ndt-engineer/templates/ndt-plan-template.md +109 -0
- package/roles/ndt-engineer/tests/test_ndt_cli_bundle.py +183 -0
- package/roles/ndt-engineer/tests/test_ndt_core.py +437 -0
- package/roles/ndt-engineer/tests/test_role_ndt_engineer.py +148 -0
- package/roles/numerical-analysis-engineer/ROLE.md +158 -0
- package/roles/numerical-analysis-engineer/SOURCES.md +19 -0
- package/roles/numerical-analysis-engineer/cli.py +414 -0
- package/roles/numerical-analysis-engineer/core/numerical_analysis_core.py +1220 -0
- package/roles/numerical-analysis-engineer/templates/verification-memo-template.md +120 -0
- package/roles/numerical-analysis-engineer/tests/test_bundle_protocol.py +157 -0
- package/roles/numerical-analysis-engineer/tests/test_numerical_analysis_core.py +337 -0
- package/roles/numerical-analysis-engineer/tests/test_role_numerical_analysis_engineer.py +117 -0
- package/roles/propulsion-engineer/ROLE.md +163 -0
- package/roles/propulsion-engineer/SOURCES.md +15 -0
- package/roles/propulsion-engineer/cli.py +168 -0
- package/roles/propulsion-engineer/core/propulsion_core.py +1291 -0
- package/roles/propulsion-engineer/templates/propulsion-report-template.md +92 -0
- package/roles/propulsion-engineer/tests/test_propulsion_engineer_core.py +294 -0
- package/roles/propulsion-engineer/tests/test_role_propulsion_engineer.py +106 -0
- package/roles/space-systems-engineer/ROLE.md +161 -0
- package/roles/space-systems-engineer/SOURCES.md +49 -0
- package/roles/space-systems-engineer/cli.py +173 -0
- package/roles/space-systems-engineer/core/space_systems_core.py +980 -0
- package/roles/space-systems-engineer/templates/space-systems-template.md +88 -0
- package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +117 -0
- package/roles/space-systems-engineer/tests/test_space_systems_engineer_core.py +247 -0
- package/roles/state-estimation-engineer/ROLE.md +154 -0
- package/roles/state-estimation-engineer/SOURCES.md +27 -0
- package/roles/state-estimation-engineer/cli.py +326 -0
- package/roles/state-estimation-engineer/core/state_estimation_core.py +958 -0
- package/roles/state-estimation-engineer/templates/nav-state-estimator-design-template.md +76 -0
- package/roles/state-estimation-engineer/tests/test_bundle_protocol.py +110 -0
- package/roles/state-estimation-engineer/tests/test_role_state_estimation_engineer.py +127 -0
- package/roles/state-estimation-engineer/tests/test_state_estimation_engineer_core.py +231 -0
- package/roles/structures-loads-engineer/ROLE.md +171 -0
- package/roles/structures-loads-engineer/SOURCES.md +44 -0
- package/roles/structures-loads-engineer/cli.py +219 -0
- package/roles/structures-loads-engineer/core/structures_loads_core.py +1112 -0
- package/roles/structures-loads-engineer/templates/loads-strength-report-template.md +96 -0
- package/roles/structures-loads-engineer/tests/test_bundle_protocol.py +101 -0
- package/roles/structures-loads-engineer/tests/test_role_structures_loads_engineer.py +147 -0
- 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.
|