Eakempreet commited on
Commit
12bc208
·
1 Parent(s): cd92e0e

Docker + FastAPI backend + v11 HUD for HF Spaces deploy

Browse files
.dockerignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git/
2
+ __pycache__/
3
+ *.pyc
4
+ .ipynb_checkpoints/
5
+ notebooks/
6
+
7
+ # Project notes and personal vaults
8
+ ATAS_Notes/
9
+
10
+ # Local python environment
11
+ env/
12
+
13
+ # Large data folder (keep metadata CSV tracked)
14
+ data/
15
+ !data/aircraft_metadata.csv
16
+
17
+ # Logs, artifacts and model binaries
18
+ logs/
19
+ models/
20
+ release_models/
21
+ !release_models/aircraft_classifier/
22
+ !release_models/aircraft_classifier/atas_final_fine_tuned_aircraft_classifier_model.keras
23
+ !release_models/eta/
24
+ !release_models/eta/atas_final_eta_regressor_model.joblib
25
+ !release_models/hit/
26
+ !release_models/hit/atas_final_hit_classifier_model.joblib
27
+
28
+ # Prediction outputs and training history
29
+ predictions/
30
+ model_history/
31
+
32
+ # VS Code workspace config
33
+ .vscode/
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.keras filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ EXPOSE 7860
10
+
11
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
app/__pycache__/main.cpython-311.pyc ADDED
Binary file (4.72 kB). View file
 
app/__pycache__/main.cpython-313.pyc ADDED
Binary file (3.91 kB). View file
 
app/__pycache__/test_pipeline.cpython-311.pyc ADDED
Binary file (1.99 kB). View file
 
app/__pycache__/test_pipeline.cpython-313.pyc ADDED
Binary file (1.89 kB). View file
 
app/main.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ main.py
3
+ -----------------------------------------------------
4
+ # WHAT: FastAPI application entry point for the ATAS backend.
5
+ # Exposes a single endpoint - POST /analyze - that accepts an aircraft
6
+ # image and engagement parameters, runs the full pipeline, and returns
7
+ # a JSON response.
8
+ #
9
+ # WHY: All five src/ modules are isolated units. main.py is the glue layer
10
+ # that wires them together in the correct order and exposes the result
11
+ # to the outside world via HTTP.
12
+ #
13
+ # USE: Called by the HUD frontend via fetch(). Also used directly during
14
+ # testing via curl or a Python test script to verify end-to-end pipeline
15
+ # behavior before any UI work begins.
16
+
17
+ # User controls / HUD inputs: + 1 Image
18
+ # your_speed, your_altitude, enemy_altitude, countermeasure_deployed,
19
+ # launch_distance, remaining_distance, azimuth, elevation
20
+
21
+ # Comes from aircraft_metadata.csv:
22
+ # missile_speed, missile_range, enemy_generation, maneuverability
23
+
24
+ # Derived by build_feature_array() internally:
25
+ # closure_rate, missile_phase
26
+ """
27
+
28
+
29
+
30
+ # Importing the necessary libraries
31
+ from fastapi import FastAPI
32
+ from fastapi import UploadFile, File, Form
33
+ from fastapi.middleware.cors import CORSMiddleware
34
+ import tempfile
35
+ import os
36
+ from src.classifier import predict_aircraft
37
+ from src.metadata import get_aircraft_metadata
38
+ from src.physics_generator import build_feature_array
39
+ from src.models import make_predictions
40
+ from src.decision import get_recommendation
41
+
42
+
43
+ # Creating a server
44
+ app = FastAPI()
45
+
46
+
47
+ # Allow the browser to send requests to this server
48
+ # Without this, the HUD's fetch() call gets blocked by the browser's security rules
49
+ # CORS (Cross-Origin Resource Sharing) is the mechanism where your server tells the browser: "yes, I allow requests from other origins."
50
+ # allow_origins=["*"] → accept requests from any origin (local file, any URL)
51
+ # allow_methods=["*"] → accept any HTTP method (POST, GET, etc.)
52
+ # allow_headers=["*"] → accept any headers the browser sends with the request
53
+ app.add_middleware(
54
+ CORSMiddleware,
55
+ allow_origins=["*"],
56
+ allow_methods=["*"],
57
+ allow_headers=["*"],
58
+ )
59
+
60
+
61
+ # Function to take the inputs
62
+ # image -> as temp file
63
+ # 8 features -> form object
64
+ @app.post("/analyze")
65
+ async def analyze(
66
+ image: UploadFile = File(...), # The ... just means required. No default value.
67
+ your_speed: float = Form(...), # name : type = where_it_comes_from(required_or_default)
68
+ your_altitude: float = Form(...),
69
+ enemy_altitude: float = Form(...),
70
+ countermeasure_deployed: int = Form(...),
71
+ launch_distance: float = Form(...),
72
+ remaining_distance: float = Form(...),
73
+ azimuth: float = Form(...),
74
+ elevation: float = Form(...),
75
+ friendly_aircraft: str = Form(...)
76
+ ):
77
+
78
+ # Browser sends image as raw bytes, not a file path
79
+ # predict_aircraft() needs a real file path on disk
80
+ # So we write the bytes to a temp file and grab its path
81
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as tmp:
82
+ tmp.write(await image.read()) # dump bytes from memory to disk
83
+ tmp_path = tmp.name # grab the path e.g. /tmp/abc123.jpg
84
+
85
+
86
+ # 1. Calling the function to predict the aircraft
87
+ aircraft_name = predict_aircraft(image_path=tmp_path)
88
+
89
+ # 2. Get the metadata of predicted aircraft
90
+ metadata_dict = get_aircraft_metadata(aircraft_name=aircraft_name)
91
+ friendly_metadata = get_aircraft_metadata(aircraft_name=friendly_aircraft)
92
+
93
+ # 3. Build the features to feed the models
94
+ feature_dict = build_feature_array(
95
+ # From HUD sliders
96
+ launch_distance=launch_distance,
97
+ remaining_distance=remaining_distance,
98
+ azimuth=azimuth,
99
+ elevation=elevation,
100
+ your_altitude=your_altitude,
101
+ your_speed=your_speed,
102
+ enemy_altitude=enemy_altitude,
103
+ countermeasure_deployed=countermeasure_deployed,
104
+ # From metadata lookup (enemy aircraft)
105
+ missile_speed=metadata_dict['missile_speed'],
106
+ missile_range=metadata_dict['missile_range'],
107
+ enemy_generation=metadata_dict['enemy_generation'],
108
+ # From metadata lookup (friendly aircraft)
109
+ your_maneuverability=friendly_metadata['maneuverability']
110
+ )
111
+
112
+ # 4. Make predictions using models
113
+ predictions_dict = make_predictions(feature_dict=feature_dict)
114
+
115
+ # 5. Providing recommendations to the pilot
116
+ suggestion = get_recommendation(
117
+ eta_seconds=predictions_dict['eta_seconds'],
118
+ hit_probability=predictions_dict['hit_probability'],
119
+ no_aa_capability=metadata_dict['no_aa_capability'],
120
+ enemy_generation=metadata_dict['enemy_generation'],
121
+ friendly_generation=friendly_metadata['enemy_generation']
122
+ )
123
+
124
+ # 6. Delete the temparary file since work is done
125
+ os.remove(tmp_path)
126
+
127
+ # 7. Return the value predicted and derived
128
+ return {
129
+ 'aircraft_name' : aircraft_name,
130
+ 'missile_speed' : metadata_dict['missile_speed'],
131
+ 'missile_range' : metadata_dict['missile_range'],
132
+ 'enemy_generation' : metadata_dict['enemy_generation'],
133
+ 'maneuverability' : metadata_dict['maneuverability'],
134
+ 'no_aa_capability' : metadata_dict['no_aa_capability'],
135
+ 'eta_seconds' : predictions_dict['eta_seconds'],
136
+ 'hit_probability' : predictions_dict['hit_probability'],
137
+ 'recommendation' : suggestion
138
+ }
139
+
140
+
141
+
142
+ """
143
+ Request comes in
144
+
145
+ Image saved to temp file
146
+
147
+ predict_aircraft() → aircraft_name
148
+
149
+ get_aircraft_metadata() x 2 → enemy + friendly metadata
150
+
151
+ build_feature_array() → 14 feature dict
152
+
153
+ make_predictions() → eta + hit probability
154
+
155
+ get_recommendation() → tactical suggestion
156
+
157
+ Temp file deleted
158
+
159
+ JSON returned to HUD
160
+ """
app/test_pipeline.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ test_pipeline.py -- End-to-End Pipeline Verification Script
3
+
4
+ WHAT:
5
+ Sends a real POST request to the running FastAPI server at /analyze.
6
+ Uses a real aircraft image and hardcoded engagement parameters.
7
+
8
+ WHY:
9
+ Confirms the full pipeline runs without errors before any UI work begins.
10
+ If this script returns clean JSON with all expected keys, the backend is ready.
11
+ If it fails, we fix the backend here, not inside the HUD.
12
+
13
+ HOW TO USE:
14
+ 1. Start the FastAPI server in one terminal:
15
+ uvicorn app.main:app --reload
16
+
17
+ 2. Run this script in a second terminal:
18
+ python app/test_pipeline.py
19
+
20
+ EXPECTED OUTPUT:
21
+ A JSON response containing:
22
+ aircraft_name, missile_speed, missile_range, enemy_generation,
23
+ maneuverability, no_aa_capability, eta_seconds, hit_probability, recommendation
24
+ """
25
+
26
+
27
+ # Import the necessary libraries
28
+ import requests
29
+ from pathlib import Path
30
+
31
+ # Define image test path
32
+ IMAGE_PATH = str(Path(__file__).parent.parent / 'assets'/ 'testing_images' / 'Su57.jpg')
33
+ print(IMAGE_PATH)
34
+
35
+ # Define Testing parameters
36
+ params = {
37
+ "your_speed": 500.0,
38
+ "your_altitude": 10000.0,
39
+ "enemy_altitude": 8000.0,
40
+ "countermeasure_deployed": 0,
41
+ "launch_distance": 15000.0,
42
+ "remaining_distance": 10000.0,
43
+ "azimuth": 45.0,
44
+ "elevation": 5.0,
45
+ "friendly_aircraft": "Tejas"
46
+ }
47
+
48
+
49
+ # Send the data to the server
50
+ # and
51
+ # store what the server sends back
52
+ response = requests.post(
53
+ url='http://localhost:8000/analyze', # Address of the running FastAPI server
54
+ data=params, # Form fields - speed, altitude, etc.
55
+ files={
56
+ "image": ( # "image" matches the field name in main.py
57
+ "Su57.jpg", # Filename the server will see
58
+ open(IMAGE_PATH, "rb"), # Open the image in read-binary mode
59
+ "image/jpeg" # Tell the server what type of file this is
60
+ )
61
+ }
62
+ )
63
+
64
+ print(response.status_code) # should be 200 if everything worked
65
+ print(response.json()) # the actual pipeline output
66
+
67
+
68
+
69
+
70
+ """
71
+ VERIFIED OUTPUT - June 8, 2026
72
+ End-to-end pipeline confirmed working. Status 200.
73
+
74
+ Test image : assets/Su57.jpg
75
+ Server : uvicorn app.main:app (no --reload)
76
+ Run with : python -m app.test_pipeline
77
+
78
+ Response:
79
+ {
80
+ "aircraft_name" : "Su57",
81
+ "missile_speed" : 2058,
82
+ "missile_range" : 400000,
83
+ "enemy_generation": 5.0,
84
+ "maneuverability" : 2,
85
+ "no_aa_capability": 0,
86
+ "eta_seconds" : 3.77,
87
+ "hit_probability" : 0.969,
88
+ "recommendation" : "BREAK HARD + DEPLOY CM + DISENGAGE IMMEDIATELY"
89
+ }
90
+
91
+ All 6 src/ modules executed without error.
92
+ Aircraft correctly identified from image.
93
+ Recommendation matches expected threat profile for Gen-5 enemy at 10km.
94
+ """
data/aircraft_metadata.csv ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aircraft,aircraft_max_speed,missile_speed,missile_range,enemy_generation,maneuverability,no_aa_capability
2
+ A10,222,857,35000,4,1,0
3
+ A400M,255,-1,-1,4,0,1
4
+ AG600,155,-1,-1,4,0,1
5
+ AH64,101,750,8000,4,1,0
6
+ AKINCI,100,1372,65000,4,1,0
7
+ AV8B,300,1372,160000,4,1,0
8
+ An124,240,-1,-1,4,0,1
9
+ An22,205,-1,-1,3.5,0,1
10
+ An225,236,-1,-1,4,0,1
11
+ An72,196,-1,-1,4,0,1
12
+ B1,370,-1,-1,4,0,1
13
+ B2,280,-1,-1,4.5,0,1
14
+ B21,277,-1,-1,5,0,1
15
+ B52,290,-1,-1,3.5,0,1
16
+ Be200,194,-1,-1,4,0,1
17
+ C1,224,-1,-1,3.5,0,1
18
+ C130,186,-1,-1,3.5,0,1
19
+ C17,264,-1,-1,4,0,1
20
+ C2,255,-1,-1,4,0,1
21
+ C390,274,-1,-1,4.5,0,1
22
+ C5,237,-1,-1,3.5,0,1
23
+ CH47,87,-1,-1,3.5,0,1
24
+ CH53,87,-1,-1,3.5,0,1
25
+ CL415,100,-1,-1,4,0,1
26
+ E2,180,-1,-1,4,0,1
27
+ E7,265,-1,-1,4.5,0,1
28
+ EF2000,590,1372,200000,4.5,2,0
29
+ EMB314,164,857,35000,4,1,0
30
+ F117,289,-1,-1,4,0,1
31
+ F14,690,1475,190000,4,2,0
32
+ F15,737,1372,160000,4,2,0
33
+ F16,590,1372,160000,4,2,0
34
+ F18,532,1372,160000,4.5,2,0
35
+ F2,590,1544,105000,4.5,2,0
36
+ F22,669,1372,160000,5,2,0
37
+ F35,544,1372,160000,5,2,0
38
+ F4,658,1372,50000,3.5,1,0
39
+ FCK1,532,2058,100000,4,2,0
40
+ H6,292,-1,-1,3.5,0,1
41
+ Il76,250,-1,-1,3.5,0,1
42
+ J10,650,1715,300000,4.5,2,0
43
+ J20,590,1715,300000,5,2,0
44
+ J35,590,1715,300000,5,2,0
45
+ J36,590,1715,400000,5,2,0
46
+ J50,590,1715,400000,5,2,0
47
+ JAS39,590,1372,200000,4.5,2,0
48
+ JF17,544,1372,100000,4.5,2,0
49
+ JH7,502,1372,100000,4,1,0
50
+ KAAN,532,1372,65000,5,2,0
51
+ KC135,259,-1,-1,3.5,0,1
52
+ KF21,532,1372,200000,4.5,2,0
53
+ KIZILELMA,305,1372,65000,4.5,2,0
54
+ KJ600,166,-1,-1,4.5,0,1
55
+ Ka27,75,-1,-1,4,1,1
56
+ Ka52,87,857,40000,4,1,0
57
+ MQ20,205,-1,-1,4.5,1,1
58
+ MQ25,172,-1,-1,5,0,1
59
+ MQ28,277,1372,160000,5,1,0
60
+ MQ9,134,857,35000,4,1,0
61
+ Mi24,93,847,8000,3.5,1,0
62
+ Mi26,82,-1,-1,4,0,1
63
+ Mi28,89,857,40000,4,1,0
64
+ Mi8,78,-1,-1,3.5,0,1
65
+ Mig29,667,1372,110000,4,2,0
66
+ Mig31,833,2058,400000,4,1,0
67
+ Mirage2000,649,1372,80000,4,2,0
68
+ NH90,83,-1,-1,4,1,1
69
+ P3,208,-1,-1,3.5,0,1
70
+ RQ4,174,-1,-1,4,0,1
71
+ Rafale,531,1372,200000,4.5,2,0
72
+ SR71,983,-1,-1,3.5,0,1
73
+ Su24,444,847,8000,3.5,1,0
74
+ Su25,271,857,40000,3.5,1,0
75
+ Su34,527,1372,110000,4,1,0
76
+ Su47,611,1372,110000,4.5,2,0
77
+ Su57,590,2058,400000,5,2,0
78
+ T50,510,857,18000,4,1,0
79
+ TB001,78,-1,-1,4,1,1
80
+ TB2,61,-1,-1,4,1,1
81
+ Tejas,550,1544,110000,4.5,2,0
82
+ Tornado,667,1372,160000,4,1,0
83
+ Tu160,616,-1,-1,4,0,1
84
+ Tu22M,638,-1,-1,3.5,0,1
85
+ Tu95,255,-1,-1,3.5,0,1
86
+ U2,224,-1,-1,3.5,0,1
87
+ UH60,99,-1,-1,4,1,1
88
+ US2,155,-1,-1,4,0,1
89
+ V22,157,-1,-1,4,0,1
90
+ V280,154,-1,-1,5,1,1
91
+ Vulcan,288,-1,-1,3.5,0,1
92
+ WZ10,83,686,8000,4,1,0
93
+ WZ7,208,-1,-1,4,0,1
94
+ WZ9,85,686,8000,4,1,0
95
+ X29,536,857,35000,4,2,0
96
+ X32,536,-1,-1,5,2,1
97
+ XB70,919,-1,-1,3.5,0,1
98
+ XQ58,292,1372,160000,5,1,0
99
+ Y20,255,-1,-1,4,0,1
100
+ YF23,648,1372,160000,5,2,0
101
+ Z10,83,686,8000,4,1,0
102
+ Z19,78,686,8000,4,1,0
103
+ Z21,83,686,8000,4,1,0
frontend/atas_hud_v11.html ADDED
@@ -0,0 +1,776 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ATAS v2.5 Tactical MFD</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600;700&display=swap');
9
+
10
+ *, *::before, *::after { box-sizing: border-box; }
11
+ body { background: #000; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; font-family: 'IBM Plex Mono', monospace; }
12
+
13
+ /* ── Animations ── */
14
+ .hb { animation: hblink 1.6s ease-in-out infinite; }
15
+ @keyframes hblink { 0%,100%{opacity:1} 50%{opacity:.3} }
16
+ @keyframes scan { 0%{top:0} 100%{top:100%} }
17
+ @keyframes spin { 100%{transform:rotate(360deg)} }
18
+ @keyframes critPulse { 0%,100%{box-shadow:0 0 0 0 rgba(232,68,68,0)} 50%{box-shadow:0 0 18px 6px rgba(232,68,68,.25)} }
19
+
20
+ .scan-line {
21
+ width:100%; height:2px; background:rgba(0,212,176,.9); position:absolute;
22
+ box-shadow:0 0 14px #00d4b0; animation:scan 1.4s linear infinite; z-index:5; display:none;
23
+ }
24
+ .rotor-spin { transform-origin:60px 45px; animation:spin .15s linear infinite; }
25
+ .threat-critical { animation:critPulse 1s ease-in-out infinite; }
26
+
27
+ /* ── Reusable classes ── */
28
+ .bc { position:absolute; width:15px; height:15px; }
29
+ .bc-tl { top:-1px; left:-1px; border-top:2px solid #00d4b0; border-left:2px solid #00d4b0; }
30
+ .bc-tr { top:-1px; right:-1px; border-top:2px solid #00d4b0; border-right:2px solid #00d4b0; }
31
+ .bc-bl { bottom:-1px; left:-1px; border-bottom:2px solid #00d4b0; border-left:2px solid #00d4b0; }
32
+ .bc-br { bottom:-1px; right:-1px; border-bottom:2px solid #00d4b0; border-right:2px solid #00d4b0; }
33
+
34
+ .panel {
35
+ border:1px solid rgba(0,212,176,.2); background:rgba(0,212,176,.025);
36
+ padding:11px; position:relative;
37
+ }
38
+ .panel-label {
39
+ font-size:9px; color:rgba(0,212,176,.4); letter-spacing:.2em;
40
+ margin-bottom:8px; text-transform:uppercase;
41
+ }
42
+ .data-row {
43
+ display:flex; justify-content:space-between; align-items:center;
44
+ border-bottom:1px solid rgba(0,212,176,.08); padding-bottom:4px; margin-bottom:4px;
45
+ font-size:9px;
46
+ }
47
+ .data-row:last-child { border-bottom:none; margin-bottom:0; }
48
+ .data-key { color:rgba(0,212,176,.45); letter-spacing:.05em; }
49
+ .data-val { color:#00d4b0; font-size:10px; font-weight:600; }
50
+
51
+ .tac-slider {
52
+ -webkit-appearance:none; width:100%; height:2px;
53
+ background:rgba(0,212,176,.2); outline:none; margin:8px 0; display:block;
54
+ }
55
+ .tac-slider::-webkit-slider-thumb {
56
+ -webkit-appearance:none; width:8px; height:14px; background:#00d4b0;
57
+ cursor:pointer; border-radius:1px; box-shadow:0 0 5px rgba(0,212,176,.8);
58
+ }
59
+ .tac-select {
60
+ background:rgba(0,212,176,.05); border:1px solid rgba(0,212,176,.3);
61
+ color:#00d4b0; font-family:'IBM Plex Mono',monospace; font-size:10px;
62
+ padding:4px; outline:none; width:100%; cursor:pointer;
63
+ }
64
+ .tac-select option { background:#06070b; color:#00d4b0; }
65
+
66
+ .tactical-feed {
67
+ width:100%; height:100%; object-fit:cover;
68
+ filter:grayscale(100%) sepia(100%) hue-rotate(130deg) saturate(300%) contrast(1.2);
69
+ opacity:.7; display:none; position:absolute; top:0; left:0; z-index:1;
70
+ }
71
+
72
+ /* Countermeasure toggle and Action Buttons */
73
+ .cm-btn {
74
+ cursor:pointer; border:1px solid rgba(0,212,176,.3);
75
+ background:rgba(0,212,176,.04); color:rgba(0,212,176,.6);
76
+ font-family:'IBM Plex Mono',monospace; font-size:9px; letter-spacing:.1em;
77
+ padding:8px 8px; width:100%; text-align:center; transition:all .2s;
78
+ }
79
+ .cm-btn:hover { background:rgba(0,212,176,.1); border-color:#00d4b0; color:#00d4b0; }
80
+ .cm-btn.deployed { border-color:#f0a030; background:rgba(240,160,48,.1); color:#f0a030; }
81
+ .cm-btn.deployed:hover { background:rgba(240,160,48,.2); }
82
+
83
+ .clear-btn {
84
+ margin-top:6px; border:1px solid rgba(0,212,176,.15); padding:4px 8px;
85
+ text-align:center; font-size:8px; color:rgba(0,212,176,.4); letter-spacing:.1em;
86
+ cursor:pointer; display:none; transition:all .2s;
87
+ }
88
+ .clear-btn:hover { border-color:rgba(0,212,176,.4); color:rgba(0,212,176,.7); }
89
+ </style>
90
+ </head>
91
+ <body>
92
+
93
+ <div style="background:#020203;padding:10px;border-radius:12px;box-shadow:inset 0 0 40px rgba(0,0,0,.8);width:880px;">
94
+ <div id="main-hud" style="position:relative;background:#06070b;border:1px solid rgba(0,212,176,.1);padding:20px;">
95
+
96
+ <div class="bc" style="top:5px;left:5px;border-top:3px solid #00d4b0;border-left:3px solid #00d4b0;"></div>
97
+ <div class="bc" style="top:5px;right:5px;border-top:3px solid #00d4b0;border-right:3px solid #00d4b0;"></div>
98
+ <div class="bc" style="bottom:5px;left:5px;border-bottom:3px solid #00d4b0;border-left:3px solid #00d4b0;"></div>
99
+ <div class="bc" style="bottom:5px;right:5px;border-bottom:3px solid #00d4b0;border-right:3px solid #00d4b0;"></div>
100
+
101
+ <div style="display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid rgba(0,212,176,.12);padding-bottom:12px;margin-bottom:14px;">
102
+ <div style="display:flex;align-items:baseline;gap:8px;">
103
+ <span style="font-size:18px;font-weight:700;letter-spacing:.3em;color:#00d4b0;">ATAS</span>
104
+ <span style="font-size:9px;color:rgba(0,212,176,.35);letter-spacing:.12em;">v2.5 // TACTICAL MFD PIPELINE</span>
105
+ </div>
106
+ <span id="header-alert" style="font-size:10px;color:rgba(0,212,176,.3);letter-spacing:.2em;font-weight:700;">SCANNING AIRSPACE</span>
107
+ <div style="font-size:9px;display:flex;gap:10px;">
108
+ <span style="color:#2dd4a0;">● UPLINK SECURE</span>
109
+ <span style="color:rgba(0,212,176,.4);">SYS.TIME: <span id="sys-time">00:00:00 IST</span></span>
110
+ </div>
111
+ </div>
112
+
113
+ <div style="display:grid;grid-template-columns:1fr 1.55fr 1fr;gap:14px;margin-bottom:12px;">
114
+
115
+ <div style="display:flex;flex-direction:column;gap:12px;">
116
+
117
+ <div class="panel">
118
+ <div class="bc bc-tl"></div><div class="bc bc-tr"></div>
119
+ <div class="bc bc-bl"></div><div class="bc bc-br"></div>
120
+ <div class="panel-label" style="display:flex;justify-content:space-between;">
121
+ <span>LIVE CAMERA FEED</span>
122
+ <span id="sensor-status" style="color:rgba(0,212,176,.3);">STANDBY</span>
123
+ </div>
124
+ <div id="sensor-feed"
125
+ style="border:1px dashed rgba(0,212,176,.28);background:rgba(0,0,0,.6);height:108px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;cursor:pointer;"
126
+ onclick="document.getElementById('file-upload').click()">
127
+ <input type="file" id="file-upload" accept="image/*" style="display:none;" onchange="handleImageUpload(event)">
128
+ <div class="scan-line" id="scan-line"></div>
129
+ <img id="sensor-img" class="tactical-feed" alt="sensor feed">
130
+ <div id="sensor-text" style="font-size:9px;color:rgba(0,212,176,.38);letter-spacing:.1em;text-align:center;z-index:2;position:relative;">
131
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-bottom:5px;opacity:.55;display:block;margin-left:auto;margin-right:auto;">
132
+ <rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>
133
+ </svg>
134
+ UPLOAD TARGET PHOTO
135
+ </div>
136
+ <div style="position:absolute;top:50%;left:50%;width:22px;height:1px;background:rgba(0,212,176,.25);transform:translate(-50%,-50%);z-index:3;"></div>
137
+ <div style="position:absolute;top:50%;left:50%;width:1px;height:22px;background:rgba(0,212,176,.25);transform:translate(-50%,-50%);z-index:3;"></div>
138
+ </div>
139
+ <div id="clear-btn" class="clear-btn" onclick="clearTarget()">↺ CLEAR TARGET LOCK</div>
140
+ </div>
141
+
142
+ <div class="panel" style="border-color:rgba(0,212,176,.28);background:rgba(0,212,176,.04);">
143
+ <div class="bc bc-tl"></div><div class="bc bc-tr"></div>
144
+ <div class="bc bc-bl"></div><div class="bc bc-br"></div>
145
+ <div style="font-size:9px;color:#00d4b0;letter-spacing:.15em;font-weight:700;margin-bottom:10px;">FRIENDLY PLATFORM</div>
146
+ <div style="display:flex;gap:10px;align-items:center;margin-bottom:10px;">
147
+ <svg width="36" height="36" viewBox="0 0 100 100" style="opacity:.8;flex-shrink:0;">
148
+ <polygon points="50,10 55,30 90,65 90,75 60,70 60,90 70,100 30,100 40,90 40,70 10,75 10,65 45,30" fill="none" stroke="#00d4b0" stroke-width="2"/>
149
+ <line x1="50" y1="10" x2="50" y2="90" stroke="rgba(0,212,176,.3)" stroke-width="1"/>
150
+ </svg>
151
+ <select id="own-platform-select" class="tac-select" style="flex:1;" onchange="updateOwnPlatform()"></select>
152
+ </div>
153
+ <div style="font-size:9px;color:rgba(0,212,176,.6);">
154
+ <div style="display:flex;justify-content:space-between;margin-bottom:2px;">
155
+ <span>AIRSPEED (MACH)</span>
156
+ <span>MAX <span id="max-spd-label">1.8</span> | <span id="val-spd" style="color:#00d4b0;font-weight:700;">1.20</span></span>
157
+ </div>
158
+ <input type="range" class="tac-slider" id="slide-spd" min="0.5" max="1.8" step="0.01" value="1.20" oninput="updateOwnSliders()">
159
+ <div style="display:flex;justify-content:space-between;margin-bottom:2px;">
160
+ <span>ALTITUDE (FT)</span>
161
+ <span>Max <span id="max-alt-label">60k</span> | <span id="val-alt" style="color:#00d4b0;font-weight:700;">32,000</span></span>
162
+ </div>
163
+ <input type="range" class="tac-slider" id="slide-alt" min="5000" max="60000" step="500" value="32000" oninput="updateOwnSliders()">
164
+ </div>
165
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;font-size:8px;margin-top:4px;">
166
+ <div style="background:rgba(0,0,0,.3);padding:4px;border-left:1px solid rgba(0,212,176,.35);">
167
+ <div style="color:rgba(0,212,176,.4);">GENERATION</div>
168
+ <div id="own-gen-label" style="color:#2dd4a0;">4.5</div>
169
+ </div>
170
+ <div style="background:rgba(0,0,0,.3);padding:4px;border-left:1px solid rgba(0,212,176,.35);">
171
+ <div style="color:rgba(0,212,176,.4);">MANEUVER</div>
172
+ <div id="own-man-label" style="color:#2dd4a0;">HIGH (2)</div>
173
+ </div>
174
+ </div>
175
+
176
+ <div id="start-btn" class="cm-btn" style="margin-top:12px; border-color:#00d4b0; color:#00d4b0; font-weight:bold; letter-spacing:0.15em;" onclick="startSimulation()">▶ START SIMULATION</div>
177
+ </div>
178
+ </div>
179
+
180
+ <div class="panel" style="display:flex;flex-direction:column;">
181
+ <div class="bc bc-tl"></div><div class="bc bc-tr"></div>
182
+ <div class="bc bc-bl"></div><div class="bc bc-br"></div>
183
+
184
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;">
185
+ <div style="font-size:10px;color:#00d4b0;letter-spacing:.2em;font-weight:700;">TARGET CLASSIFIER</div>
186
+ <span style="font-size:7px;color:rgba(0,212,176,.35);background:rgba(0,212,176,.05);padding:2px 6px;border:1px solid rgba(0,212,176,.18);letter-spacing:.07em;">EFFICIENTNETV2-L · 78.08%</span>
187
+ </div>
188
+
189
+ <div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;border:1px solid rgba(0,212,176,.05);background:rgba(0,0,0,.3);padding:10px;position:relative;overflow:hidden;">
190
+ <svg width="100%" height="100%" style="position:absolute;top:0;left:0;opacity:.07;pointer-events:none;">
191
+ <line x1="50%" y1="0" x2="50%" y2="100%" stroke="#00d4b0" stroke-width="1"/>
192
+ <line x1="0" y1="50%" x2="100%" y2="50%" stroke="#00d4b0" stroke-width="1"/>
193
+ <circle cx="50%" cy="50%" r="45" fill="none" stroke="#00d4b0" stroke-width="1"/>
194
+ <circle cx="50%" cy="50%" r="85" fill="none" stroke="#00d4b0" stroke-width="1" stroke-dasharray="2 4"/>
195
+ </svg>
196
+
197
+ <svg id="target-svg" width="158" height="158" viewBox="0 0 120 120"
198
+ style="margin-bottom:8px;z-index:1;position:relative;filter:drop-shadow(0 0 6px currentColor);flex-shrink:0;"></svg>
199
+ <div id="target-info" style="text-align:center;width:100%;z-index:1;position:relative;"></div>
200
+
201
+ <div id="true-specs-box" style="display:none;width:100%;margin-top:8px;border-top:1px dashed rgba(255,255,255,.1);padding-top:8px;z-index:1;position:relative;">
202
+ <div style="font-size:8px;color:rgba(255,255,255,.3);margin-bottom:5px;letter-spacing:.1em;">THREAT METADATA // LOOKUP TABLE</div>
203
+ <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:3px;font-size:9px;">
204
+ <div style="padding:4px;background:rgba(0,0,0,.45);border-left:1px solid #444;">
205
+ <span style="color:rgba(255,255,255,.28);font-size:7px;display:block;">MANEUVER</span>
206
+ <span id="e-man" style="color:#ccc;">---</span>
207
+ </div>
208
+ <div style="padding:4px;background:rgba(0,0,0,.45);border-left:1px solid #444;">
209
+ <span style="color:rgba(255,255,255,.28);font-size:7px;display:block;">ALTITUDE</span>
210
+ <span id="e-alt" style="color:#ccc;">---</span>
211
+ </div>
212
+ <div style="padding:4px;background:rgba(0,0,0,.45);border-left:1px solid #444;">
213
+ <span style="color:rgba(255,255,255,.28);font-size:7px;display:block;">GENERATION</span>
214
+ <span id="e-gen" style="color:#ccc;">---</span>
215
+ </div>
216
+ <div style="padding:4px;background:rgba(0,0,0,.45);border-left:1px solid #444;">
217
+ <span style="color:rgba(255,255,255,.28);font-size:7px;display:block;">MSL RANGE</span>
218
+ <span id="e-rng" style="color:#ccc;">---</span>
219
+ </div>
220
+ <div style="padding:4px;background:rgba(0,0,0,.45);border-left:1px solid #444;">
221
+ <span style="color:rgba(255,255,255,.28);font-size:7px;display:block;">MSL SPEED</span>
222
+ <span id="e-mspd" style="color:#ccc;">---</span>
223
+ </div>
224
+ <div style="padding:4px;background:rgba(0,0,0,.45);border-left:1px solid #444;">
225
+ <span style="color:rgba(255,255,255,.28);font-size:7px;display:block;">AA CAPABLE</span>
226
+ <span id="e-aa" style="color:#ccc;">---</span>
227
+ </div>
228
+ </div>
229
+ <div id="gen-adv-box" style="margin-top:4px;padding:4px 7px;border:1px solid rgba(0,212,176,.18);font-size:8px;display:flex;justify-content:space-between;align-items:center;">
230
+ <span style="color:rgba(0,212,176,.4);letter-spacing:.08em;">GENERATION ADVANTAGE</span>
231
+ <span id="gen-adv-val" style="font-weight:700;color:#00d4b0;">--</span>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ </div>
236
+
237
+ <div class="panel" style="display:flex;flex-direction:column;align-items:center;">
238
+ <div class="bc bc-tl"></div><div class="bc bc-tr"></div>
239
+ <div class="bc bc-bl"></div><div class="bc bc-br"></div>
240
+
241
+ <div class="panel-label" style="width:100%;text-align:center;margin-bottom:10px;">THREAT KINEMATICS</div>
242
+
243
+ <svg width="138" height="138" viewBox="0 0 132 132" style="flex-shrink:0;">
244
+ <circle cx="66" cy="66" r="58" fill="none" stroke="rgba(0,212,176,.05)" stroke-width="1" stroke-dasharray="2,4"/>
245
+ <circle cx="66" cy="66" r="52" fill="none" stroke="rgba(0,212,176,.1)" stroke-width="4"/>
246
+ <circle id="threat-arc" cx="66" cy="66" r="52" fill="none" stroke="#00d4b0" stroke-width="5"
247
+ stroke-dasharray="0 326" transform="rotate(-90 66 66)" style="transition:stroke-dasharray .5s ease-out,stroke .3s;"/>
248
+ <circle cx="66" cy="66" r="44" fill="none" stroke="rgba(0,212,176,.09)" stroke-width="9"/>
249
+ <circle cx="66" cy="66" r="35" fill="rgba(4,4,9,.92)" stroke="rgba(0,212,176,.14)" stroke-width=".8"/>
250
+ <line x1="66" y1="5" x2="66" y2="14" stroke="rgba(0,212,176,.4)" stroke-width="1"/>
251
+ <line x1="127" y1="66" x2="118" y2="66" stroke="rgba(0,212,176,.4)" stroke-width="1"/>
252
+ <line x1="66" y1="127" x2="66" y2="118" stroke="rgba(0,212,176,.4)" stroke-width="1"/>
253
+ <line x1="5" y1="66" x2="14" y2="66" stroke="rgba(0,212,176,.4)" stroke-width="1"/>
254
+ <text id="threat-val" x="66" y="59" text-anchor="middle" fill="#00d4b0"
255
+ font-family="'IBM Plex Mono',monospace" font-size="22" font-weight="700">0%</text>
256
+ <text id="threat-label" x="66" y="71" text-anchor="middle" fill="rgba(0,212,176,.5)"
257
+ font-family="'IBM Plex Mono',monospace" font-size="8" letter-spacing="2">SAFE</text>
258
+ <text x="66" y="81" text-anchor="middle" fill="rgba(0,212,176,.3)"
259
+ font-family="'IBM Plex Mono',monospace" font-size="5.5" letter-spacing="0">HIT PROBABILITY</text>
260
+ </svg>
261
+
262
+ <div style="width:100%;margin-top:10px;display:flex;flex-direction:column;gap:0;">
263
+ <div class="data-row">
264
+ <span class="data-key">EVASION ETA</span>
265
+ <span id="eta-val" class="data-val">N/A</span>
266
+ </div>
267
+ <div class="data-row">
268
+ <span class="data-key">CLOSURE RATE</span>
269
+ <span id="close-val" class="data-val">0 M/S</span>
270
+ </div>
271
+ <div class="data-row">
272
+ <span class="data-key">DIST TO TGT</span>
273
+ <span id="dist-val" class="data-val">---</span>
274
+ </div>
275
+ <div class="data-row">
276
+ <span class="data-key">ASPECT ANGLE</span>
277
+ <span id="aspect-val" class="data-val">---</span>
278
+ </div>
279
+ <div class="data-row" style="border-bottom:1px solid rgba(0,212,176,.08);padding-bottom:4px;margin-bottom:8px;">
280
+ <span class="data-key">MSL PHASE</span>
281
+ <span id="phase-val" class="data-val">---</span>
282
+ </div>
283
+ </div>
284
+
285
+ <div id="cm-toggle" class="cm-btn" onclick="toggleCM()">
286
+ CM: <span id="cm-status">NOT DEPLOYED</span>
287
+ </div>
288
+ </div>
289
+ </div>
290
+
291
+ <div style="display:flex;gap:10px;margin-bottom:8px;align-items:stretch;min-height:42px;">
292
+ <div id="tac-banner" style="flex:1;border:1px solid rgba(0,212,176,.2);background:rgba(0,212,176,.05);padding:10px 14px;display:flex;align-items:center;justify-content:space-between;transition:all .3s;">
293
+ <span style="font-size:9px;color:rgba(0,212,176,.45);letter-spacing:.15em;white-space:nowrap;flex-shrink:0;">SYS ACTION</span>
294
+ <span id="tac-text" style="font-size:12px;font-weight:700;letter-spacing:.15em;color:#00d4b0;text-align:center;flex:1;">CONTINUE SCANNING SECTOR</span>
295
+ <span id="conf-text" style="font-size:9px;color:rgba(0,212,176,.45);white-space:nowrap;flex-shrink:0;">SYS NORMAL</span>
296
+ </div>
297
+ </div>
298
+
299
+ <div style="display:flex;justify-content:space-between;font-size:8px;color:rgba(0,212,176,.22);letter-spacing:.05em;">
300
+ <span>CLASSIFIER: EFFICIENTNETV2-L · TOP-1 78.08% · TOP-5 92.02%</span>
301
+ <span>ETA: XGBOOST · R²=0.9939 · MAE=0.46s</span>
302
+ <span>HIT CLASSIFIER: XGBOOST · F1=0.9968</span>
303
+ <span>SYSTEM: ATAS PIPELINE ONLINE</span>
304
+ </div>
305
+
306
+ </div>
307
+ </div>
308
+
309
+ <script>
310
+ // ──────────────────────────────────────────────
311
+ // CLOCK (Indian Standard Time)
312
+ // ──────────────────────────────────────────────
313
+ setInterval(() => {
314
+ const now = new Date();
315
+ const options = { timeZone: 'Asia/Kolkata', hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' };
316
+ document.getElementById('sys-time').innerText = now.toLocaleTimeString('en-IN', options) + ' IST';
317
+ }, 1000);
318
+
319
+ // ──────────────────────────────────────────────
320
+ // ENEMY DATABASE (For Left Column UI population)
321
+ // ──────────────────────────────────────────────
322
+ const enemyDB = {
323
+ "A10": {aircraft_max_speed:222, enemy_generation:4.0, maneuverability:1},
324
+ "A400M": {aircraft_max_speed:255, enemy_generation:4.0, maneuverability:0},
325
+ "AG600": {aircraft_max_speed:155, enemy_generation:4.0, maneuverability:0},
326
+ "AH64": {aircraft_max_speed:101, enemy_generation:4.0, maneuverability:1},
327
+ "B21": {aircraft_max_speed:277, enemy_generation:5.0, maneuverability:0},
328
+ "F16": {aircraft_max_speed:590, enemy_generation:4.0, maneuverability:2},
329
+ "F22": {aircraft_max_speed:669, enemy_generation:5.0, maneuverability:2},
330
+ "Il76": {aircraft_max_speed:250, enemy_generation:3.5, maneuverability:0},
331
+ "J20": {aircraft_max_speed:590, enemy_generation:5.0, maneuverability:2},
332
+ "MQ9": {aircraft_max_speed:134, enemy_generation:4.0, maneuverability:1},
333
+ "Rafale": {aircraft_max_speed:531, enemy_generation:4.5, maneuverability:2},
334
+ "WZ9": {aircraft_max_speed:85, enemy_generation:4.0, maneuverability:1},
335
+ "Su57": {aircraft_max_speed:590, enemy_generation:5.0, maneuverability:2}
336
+ };
337
+
338
+ const fullList = [
339
+ 'A10','A400M','AG600','AH64','AKINCI','AV8B','An124','An22','An225','An72',
340
+ 'B1','B2','B21','B52','Be200','C1','C130','C17','C2','C390','C5','CH47','CH53',
341
+ 'CL415','E2','E7','EF2000','EMB314','F117','F14','F15','F16','F18','F2','F22',
342
+ 'F35','F4','FCK1','H6','Il76','J10','J20','J35','J36','J50','JAS39','JF17',
343
+ 'JH7','KAAN','KC135','KF21','KIZILELMA','KJ600','Ka27','Ka52','MQ20','MQ25',
344
+ 'MQ28','MQ9','Mi24','Mi26','Mi28','Mi8','Mig29','Mig31','Mirage2000','NH90',
345
+ 'P3','RQ4','Rafale','SR71','Su24','Su25','Su34','Su47','Su57','T50','TB001',
346
+ 'TB2','Tejas','Tornado','Tu160','Tu22M','Tu95','U2','UH60','US2','V22','V280',
347
+ 'Vulcan','WZ10','WZ7','WZ9','X29','X32','XB70','XQ58','Y20','YF23','Z10','Z19','Z21'
348
+ ];
349
+ fullList.forEach(ac => {
350
+ if (!enemyDB[ac]) enemyDB[ac] = {
351
+ aircraft_max_speed:400, enemy_generation:4.0, maneuverability:1
352
+ };
353
+ });
354
+
355
+ let currentOwn = {};
356
+ const ownSelect = document.getElementById('own-platform-select');
357
+ fullList.forEach(ac => {
358
+ const opt = document.createElement('option');
359
+ opt.value = ac;
360
+ opt.innerText = ac + ' (GEN ' + enemyDB[ac].enemy_generation.toFixed(1) + ')';
361
+ ownSelect.appendChild(opt);
362
+ });
363
+ ownSelect.value = 'Tejas';
364
+
365
+ // ──────────────────────────────────────────────
366
+ // STATE & TOGGLES
367
+ // ──────────────────────────────────────────────
368
+ let cmDeployed = false;
369
+ let isUI_Locked = false;
370
+ let uploadedFile = null;
371
+
372
+ function toggleCM() {
373
+ if (isUI_Locked) return;
374
+ cmDeployed = !cmDeployed;
375
+ const btn = document.getElementById('cm-toggle');
376
+ document.getElementById('cm-status').innerText = cmDeployed ? 'DEPLOYED' : 'NOT DEPLOYED';
377
+ cmDeployed ? btn.classList.add('deployed') : btn.classList.remove('deployed');
378
+ }
379
+
380
+ function updateOwnPlatform() {
381
+ if (isUI_Locked) return;
382
+ const key = document.getElementById('own-platform-select').value;
383
+ const data = enemyDB[key];
384
+ const cat = getCategory(key);
385
+ const machMax = parseFloat(Math.max(0.8, (data.aircraft_max_speed / 340)).toFixed(2));
386
+ const altMax = cat === 'fighter' ? 60000 : (cat === 'helo' ? 20000 : 42000);
387
+
388
+ currentOwn = {
389
+ max_spd: machMax, max_alt: altMax,
390
+ gen: data.enemy_generation, man: data.maneuverability,
391
+ name_man: data.maneuverability >= 2 ? 'HIGH (' + data.maneuverability + ')'
392
+ : (data.maneuverability === 1 ? 'MED (1)' : 'LOW (0)')
393
+ };
394
+
395
+ document.getElementById('max-spd-label').innerText = currentOwn.max_spd;
396
+ document.getElementById('max-alt-label').innerText = (currentOwn.max_alt / 1000) + 'k';
397
+ document.getElementById('own-gen-label').innerText = currentOwn.gen.toFixed(1);
398
+ document.getElementById('own-man-label').innerText = currentOwn.name_man;
399
+ document.getElementById('slide-spd').max = currentOwn.max_spd;
400
+ document.getElementById('slide-alt').max = currentOwn.max_alt;
401
+
402
+ if (parseFloat(document.getElementById('slide-spd').value) > currentOwn.max_spd)
403
+ document.getElementById('slide-spd').value = currentOwn.max_spd;
404
+ if (parseFloat(document.getElementById('slide-alt').value) > currentOwn.max_alt)
405
+ document.getElementById('slide-alt').value = currentOwn.max_alt;
406
+
407
+ updateOwnSliders();
408
+ }
409
+
410
+ function updateOwnSliders() {
411
+ if (isUI_Locked) return;
412
+ document.getElementById('val-spd').innerText = parseFloat(document.getElementById('slide-spd').value).toFixed(2);
413
+ document.getElementById('val-alt').innerText = parseInt(document.getElementById('slide-alt').value).toLocaleString();
414
+ }
415
+
416
+ function setLockState(locked) {
417
+ isUI_Locked = locked;
418
+ document.getElementById('slide-spd').disabled = locked;
419
+ document.getElementById('slide-alt').disabled = locked;
420
+ document.getElementById('own-platform-select').disabled = locked;
421
+
422
+ const opacityVal = locked ? '0.3' : '1';
423
+ const pointerVal = locked ? 'none' : 'auto';
424
+
425
+ document.getElementById('start-btn').style.opacity = opacityVal;
426
+ document.getElementById('start-btn').style.pointerEvents = pointerVal;
427
+
428
+ document.getElementById('cm-toggle').style.opacity = opacityVal;
429
+ document.getElementById('cm-toggle').style.pointerEvents = pointerVal;
430
+
431
+ document.getElementById('sensor-feed').style.pointerEvents = pointerVal;
432
+ }
433
+
434
+ // ────────��─────────────────────────────────────
435
+ // AIRCRAFT BLUEPRINTS (SVG) & CATEGORIES
436
+ // ──────────────────────────────────────────────
437
+ const premiumSVGs = {
438
+ none: `
439
+ <circle cx="60" cy="60" r="50" fill="none" stroke="rgba(0,212,176,0.08)" stroke-width="1"/>
440
+ <circle cx="60" cy="60" r="25" fill="none" stroke="rgba(0,212,176,0.05)" stroke-width="1" stroke-dasharray="3 5"/>
441
+ <line x1="60" y1="10" x2="60" y2="110" stroke="rgba(0,212,176,0.12)" stroke-width="1"/>
442
+ <line x1="10" y1="60" x2="110" y2="60" stroke="rgba(0,212,176,0.12)" stroke-width="1"/>
443
+ <circle cx="60" cy="60" r="3" fill="#00d4b0" opacity="0.5"/>`,
444
+
445
+ fighter: `
446
+ <g transform="translate(0,5)">
447
+ <polygon points="60,30 20,70 20,85 45,80 60,85" fill="rgba(232,68,68,0.1)" stroke="currentColor" stroke-width="1.2"/>
448
+ <polygon points="60,30 100,70 100,85 75,80 60,85" fill="rgba(232,68,68,0.1)" stroke="currentColor" stroke-width="1.2"/>
449
+ <path d="M60,10 L65,30 L70,70 L68,100 L52,100 L50,70 L55,30 Z" fill="rgba(232,68,68,0.15)" stroke="currentColor" stroke-width="1.5"/>
450
+ <path d="M58,25 L62,25 L63,40 L57,40 Z" fill="none" stroke="currentColor" stroke-width="0.8" opacity="0.8"/>
451
+ <line x1="60" y1="40" x2="60" y2="100" stroke="currentColor" stroke-width="1" opacity="0.3"/>
452
+ <rect x="53" y="95" width="4" height="10" fill="rgba(0,0,0,0.8)" stroke="currentColor" stroke-width="1"/>
453
+ <rect x="63" y="95" width="4" height="10" fill="rgba(0,0,0,0.8)" stroke="currentColor" stroke-width="1"/>
454
+ <polygon points="52,80 40,105 48,105 54,90" fill="rgba(232,68,68,0.2)" stroke="currentColor" stroke-width="1"/>
455
+ <polygon points="68,80 80,105 72,105 66,90" fill="rgba(232,68,68,0.2)" stroke="currentColor" stroke-width="1"/>
456
+ </g>`,
457
+
458
+ bomber: `
459
+ <g transform="translate(0,15)">
460
+ <path d="M60,10 L110,60 L90,75 L75,65 L60,75 L45,65 L30,75 L10,60 Z"
461
+ fill="rgba(232,68,68,0.15)" stroke="currentColor" stroke-width="1.5"/>
462
+ <path d="M55,25 L65,25 L62,30 L58,30 Z" fill="none" stroke="currentColor" stroke-width="0.8" opacity="0.8"/>
463
+ <line x1="50" y1="60" x2="50" y2="70" stroke="currentColor" stroke-width="1" opacity="0.5"/>
464
+ <line x1="70" y1="60" x2="70" y2="70" stroke="currentColor" stroke-width="1" opacity="0.5"/>
465
+ <path d="M45,65 Q60,55 75,65" fill="none" stroke="currentColor" stroke-width="1" stroke-dasharray="2,2" opacity="0.6"/>
466
+ </g>`,
467
+
468
+ helo: `
469
+ <g transform="translate(0,5)">
470
+ <g class="rotor-spin">
471
+ <circle cx="60" cy="45" r="45" fill="rgba(240,160,48,0.04)" stroke="currentColor" stroke-width="0.5" stroke-dasharray="2 6"/>
472
+ <line x1="60" y1="2" x2="60" y2="88" stroke="currentColor" stroke-width="1.5" opacity="0.7"/>
473
+ <line x1="17" y1="45" x2="103" y2="45" stroke="currentColor" stroke-width="1.5" opacity="0.7"/>
474
+ <circle cx="60" cy="45" r="3" fill="currentColor" opacity="0.9"/>
475
+ </g>
476
+ <path d="M60,15 C63,15 65,20 65,35 L62,65 L58,65 L55,35 C55,20 57,15 60,15 Z"
477
+ fill="rgba(240,160,48,0.2)" stroke="currentColor" stroke-width="1.5"/>
478
+ <rect x="42" y="42" width="13" height="4" fill="rgba(240,160,48,0.1)" stroke="currentColor" stroke-width="1"/>
479
+ <rect x="65" y="42" width="13" height="4" fill="rgba(240,160,48,0.1)" stroke="currentColor" stroke-width="1"/>
480
+ <polygon points="58,65 62,65 61,95 59,95" fill="rgba(240,160,48,0.15)" stroke="currentColor" stroke-width="1.2"/>
481
+ <polygon points="50,88 70,88 70,92 50,92" fill="rgba(240,160,48,0.1)" stroke="currentColor" stroke-width="1"/>
482
+ <g style="transform-origin:60px 98px;" class="rotor-spin">
483
+ <line x1="52" y1="98" x2="68" y2="98" stroke="currentColor" stroke-width="1"/>
484
+ <line x1="60" y1="90" x2="60" y2="106" stroke="currentColor" stroke-width="1"/>
485
+ </g>
486
+ </g>`,
487
+
488
+ cargo: `
489
+ <g transform="translate(0,5)">
490
+ <defs>
491
+ <filter id="eGlow" x="-50%" y="-50%" width="200%" height="200%">
492
+ <feGaussianBlur stdDeviation="1.5" result="b"/>
493
+ <feComposite in="SourceGraphic" in2="b" operator="over"/>
494
+ </filter>
495
+ </defs>
496
+ <path d="M54,40 L10,65 L8,75 L25,72 L54,55 Z" fill="rgba(0,212,176,0.1)" stroke="currentColor" stroke-width="1.2"/>
497
+ <path d="M66,40 L110,65 L112,75 L95,72 L66,55 Z" fill="rgba(0,212,176,0.1)" stroke="currentColor" stroke-width="1.2"/>
498
+ <rect x="28" y="52" width="5" height="12" rx="2" fill="rgba(0,0,0,.8)" stroke="currentColor" stroke-width="1"/>
499
+ <rect x="40" y="47" width="5" height="14" rx="2" fill="rgba(0,0,0,.8)" stroke="currentColor" stroke-width="1"/>
500
+ <rect x="87" y="52" width="5" height="12" rx="2" fill="rgba(0,0,0,.8)" stroke="currentColor" stroke-width="1"/>
501
+ <rect x="75" y="47" width="5" height="14" rx="2" fill="rgba(0,0,0,.8)" stroke="currentColor" stroke-width="1"/>
502
+ <path d="M29,64 L32,64 L30.5,72 Z" fill="#ffaa00" opacity="0.8"/>
503
+ <path d="M41,61 L44,61 L42.5,70 Z" fill="#ffaa00" opacity="0.8"/>
504
+ <path d="M88,64 L91,64 L89.5,72 Z" fill="#ffaa00" opacity="0.8"/>
505
+ <path d="M76,61 L79,61 L77.5,70 Z" fill="#ffaa00" opacity="0.8"/>
506
+ <path d="M60,10 C68,10 70,18 70,45 L66,95 L54,95 L50,45 C50,18 52,10 60,10 Z"
507
+ fill="rgba(0,212,176,0.15)" stroke="currentColor" stroke-width="1.5"/>
508
+ <polygon points="60,100 85,108 85,112 60,105 35,112 35,108"
509
+ fill="rgba(0,212,176,0.2)" stroke="currentColor" stroke-width="1.5"/>
510
+ </g>`
511
+ };
512
+
513
+ function getCategory(name) {
514
+ const bombers = ['B1','B2','B21','B52','H6','Tu160','Tu22M','Tu95','Vulcan','XB70'];
515
+ const cargo = ['A400M','An124','An22','An225','An72','C1','C130','C17','C2','C390',
516
+ 'C5','Il76','KC135','Y20','P3','E2','E7','KJ600','AG600','Be200','CL415','US2'];
517
+ const helo = ['AH64','CH47','CH53','Ka27','Ka52','Mi24','Mi26','Mi28','Mi8',
518
+ 'NH90','UH60','WZ10','WZ9','Z10','Z19','Z21','V22','V280'];
519
+ if (bombers.includes(name)) return 'bomber';
520
+ if (cargo.includes(name)) return 'cargo';
521
+ if (helo.includes(name)) return 'helo';
522
+ return 'fighter';
523
+ }
524
+
525
+ function aspectDescriptor(angle) {
526
+ if (angle < 30) return '(HEAD-ON)';
527
+ if (angle > 150) return '(TAIL)';
528
+ if (angle > 75 && angle < 105) return '(BEAM)';
529
+ return '(OBLIQUE)';
530
+ }
531
+
532
+ // ──────────────────────────────────────────────
533
+ // BACKEND API INTEGRATION (The Real Brain)
534
+ // ──────────────────────────────────────────────
535
+ function handleImageUpload(event) {
536
+ const file = event.target.files[0];
537
+ if (!file) return;
538
+ uploadedFile = file;
539
+
540
+ const img = document.getElementById('sensor-img');
541
+ img.src = URL.createObjectURL(file);
542
+ img.style.display = 'block';
543
+ document.getElementById('sensor-text').style.display = 'none';
544
+ document.getElementById('sensor-status').innerText = 'TARGET ACQUIRED';
545
+ document.getElementById('sensor-status').style.color = '#00d4b0';
546
+ }
547
+
548
+ async function startSimulation() {
549
+ if (isUI_Locked) return;
550
+ if (!uploadedFile) {
551
+ alert("Please upload a target photo to initiate pipeline.");
552
+ return;
553
+ }
554
+
555
+ // 1. Enter Loading State & Lock UI
556
+ setLockState(true);
557
+ document.getElementById('sensor-status').innerText = 'UPLINK ACTIVE...';
558
+ document.getElementById('sensor-status').style.color = '#f0a030';
559
+ document.getElementById('scan-line').style.display = 'block';
560
+ document.getElementById('clear-btn').style.display = 'none';
561
+
562
+ // 2. Gather UI values and generate plausible geometry for missing fields
563
+ const ownSpeedMS = parseFloat(document.getElementById('slide-spd').value) * 340;
564
+ const ownAltM = parseInt(document.getElementById('slide-alt').value) * 0.3048;
565
+ const enemyAltM = Math.floor(Math.random() * 9000 + 1000);
566
+ const launchDist = Math.floor(Math.random() * 25000 + 10000);
567
+ const remainDist = Math.floor(Math.random() * launchDist);
568
+ const azimuth = Math.floor(Math.random() * 180);
569
+ const elevation = Math.floor(Math.random() * 20);
570
+ const friendly = document.getElementById('own-platform-select').value || 'Tejas';
571
+
572
+ // 3. Construct Payload
573
+ const formData = new FormData();
574
+ formData.append('image', uploadedFile);
575
+ formData.append('your_speed', ownSpeedMS);
576
+ formData.append('your_altitude', ownAltM);
577
+ formData.append('enemy_altitude', enemyAltM);
578
+ formData.append('countermeasure_deployed', cmDeployed ? 1 : 0);
579
+ formData.append('launch_distance', launchDist);
580
+ formData.append('remaining_distance', remainDist);
581
+ formData.append('azimuth', azimuth);
582
+ formData.append('elevation', elevation);
583
+ formData.append('friendly_aircraft', friendly);
584
+
585
+ try {
586
+ // 4. Fire to FastAPI Backend
587
+ const response = await fetch('http://localhost:8000/analyze', {
588
+ method: 'POST',
589
+ body: formData
590
+ });
591
+
592
+ if (!response.ok) throw new Error("API Connection Failed");
593
+ const data = await response.json();
594
+
595
+ // 5. Success - Proceed to DOM update
596
+ document.getElementById('scan-line').style.display = 'none';
597
+ document.getElementById('sensor-status').innerText = 'LOCKED';
598
+ document.getElementById('sensor-status').style.color = '#e84444';
599
+
600
+ updateDOM(data, remainDist, azimuth, ownSpeedMS, enemyAltM);
601
+
602
+ } catch (error) {
603
+ console.error(error);
604
+ document.getElementById('scan-line').style.display = 'none';
605
+ document.getElementById('sensor-status').innerText = 'UPLINK FAILED';
606
+ document.getElementById('sensor-status').style.color = '#e84444';
607
+ } finally {
608
+ setLockState(false);
609
+ }
610
+ }
611
+
612
+ // ──────────────────────────────────────────────
613
+ // HUD UPDATER (Maps Real JSON to UI)
614
+ // ──────────────────────────────────────────────
615
+ function updateDOM(data, distMeters, azimuth, ownSpeedMS, enemyAltM) {
616
+ console.log(data);
617
+ const name = data.aircraft_name;
618
+ const cat = getCategory(name);
619
+ const isAA = data.no_aa_capability === 0;
620
+ const threatProb = isAA ? Math.floor(data.hit_probability * 100) : 0;
621
+
622
+ // Determine colour based on threat
623
+ let col = '#00d4b0'; // Safe
624
+ if (isAA) {
625
+ if (threatProb > 60 || data.eta_seconds < 10) col = '#e84444'; // Crit
626
+ else if (threatProb > 30) col = '#f0a030'; // Warn
627
+ }
628
+
629
+ // ── Header ──
630
+ const hdr = document.getElementById('header-alert');
631
+ hdr.innerText = 'TARGET LOCKED: ' + name.toUpperCase();
632
+ hdr.style.color = '#e84444';
633
+ hdr.classList.add('hb');
634
+
635
+ // ── SVG Canvas ──
636
+ const svgNode = document.getElementById('target-svg');
637
+ svgNode.style.color = col;
638
+ svgNode.innerHTML = premiumSVGs[cat];
639
+
640
+ document.getElementById('target-info').innerHTML = `
641
+ <div style="font-size:15px;font-weight:700;letter-spacing:.1em;color:${col};margin-top:5px;">${name.toUpperCase()}</div>
642
+ <div style="font-size:8px;color:rgba(255,255,255,.35);letter-spacing:.1em;margin-top:2px;">CLASS: ${cat.toUpperCase()}</div>
643
+ `;
644
+
645
+ // ── True Metadata ──
646
+ document.getElementById('true-specs-box').style.display = 'block';
647
+ document.getElementById('e-man').innerText = data.maneuverability >= 2 ? "HIGH (2)" : (data.maneuverability === 1 ? "MED (1)" : "LOW (0)");
648
+ document.getElementById('e-alt').textContent = Math.round(enemyAltM / 0.3048).toLocaleString() + ' FT';
649
+ document.getElementById('e-gen').innerText = 'GEN ' + data.enemy_generation.toFixed(1);
650
+ document.getElementById('e-rng').innerText = data.missile_range > 0 ? (data.missile_range / 1000).toFixed(0) + ' KM' : 'NONE';
651
+ document.getElementById('e-mspd').innerText = data.missile_speed > 0 ? data.missile_speed + ' M/S' : 'N/A';
652
+ document.getElementById('e-aa').innerText = isAA ? 'YES' : 'NO';
653
+
654
+ // ── Generation Advantage ──
655
+ if (isAA) {
656
+ const genDiff = currentOwn.gen - data.enemy_generation;
657
+ const advEl = document.getElementById('gen-adv-val');
658
+ const advBox = document.getElementById('gen-adv-box');
659
+
660
+ if (genDiff > 0) {
661
+ advEl.innerText = '+' + genDiff.toFixed(1) + ' FRIENDLY ADV';
662
+ advEl.style.color = '#2dd4a0';
663
+ advBox.style.borderColor = 'rgba(45,212,160,.28)';
664
+ } else if (genDiff < 0) {
665
+ advEl.innerText = genDiff.toFixed(1) + ' ENEMY ADV';
666
+ advEl.style.color = '#e84444';
667
+ advBox.style.borderColor = 'rgba(232,68,68,.28)';
668
+ } else {
669
+ advEl.innerText = 'PARITY';
670
+ advEl.style.color = '#f0a030';
671
+ advBox.style.borderColor = 'rgba(240,160,48,.28)';
672
+ }
673
+ } else {
674
+ document.getElementById('gen-adv-val').textContent = 'N/A';
675
+ document.getElementById('gen-adv-box').style.borderColor = 'rgba(0,212,176,.18)';
676
+ }
677
+
678
+ // ── Threat Gauge ──
679
+ const dash = (threatProb / 100) * 326;
680
+ document.getElementById('threat-arc').style.strokeDasharray = `${dash} 326`;
681
+ document.getElementById('threat-arc').style.stroke = col;
682
+ document.getElementById('threat-val').textContent = `${threatProb}%`;
683
+ document.getElementById('threat-val').setAttribute('fill', col);
684
+
685
+ let labelText = threatProb > 60 ? 'CRITICAL' : (threatProb > 30 ? 'ELEVATED' : 'LOW');
686
+ document.getElementById('threat-label').textContent = labelText;
687
+ document.getElementById('threat-label').setAttribute('fill', col);
688
+
689
+ // ── Kinematics ──
690
+ document.getElementById('eta-val').innerText = isAA ? data.eta_seconds.toFixed(1) + 's' : 'N/A';
691
+ document.getElementById('eta-val').style.color = col;
692
+
693
+ const approxClose = data.missile_speed > 0 ? Math.floor(data.missile_speed + ownSpeedMS) : 0;
694
+ document.getElementById('close-val').innerText = isAA ? approxClose.toLocaleString() + ' M/S' : '0 M/S';
695
+
696
+ document.getElementById('dist-val').innerText = (distMeters / 1000).toFixed(1) + ' KM';
697
+ document.getElementById('aspect-val').innerText = azimuth + '\u00b0 ' + aspectDescriptor(azimuth);
698
+ document.getElementById('phase-val').innerText = isAA ? 'TRACKING' : 'N/A';
699
+
700
+ // ── Banner ──
701
+ const banner = document.getElementById('tac-banner');
702
+ banner.style.borderColor = col;
703
+ banner.style.background = col === '#00d4b0' ? 'rgba(0,212,176,.05)'
704
+ : col === '#f0a030' ? 'rgba(240,160,48,.05)'
705
+ : 'rgba(232,68,68,.05)';
706
+ document.getElementById('tac-text').innerText = data.recommendation;
707
+ document.getElementById('tac-text').style.color = col;
708
+
709
+ document.getElementById('conf-text').innerText = threatProb > 70 ? 'HIGH CONF'
710
+ : threatProb > 35 ? 'MED CONF'
711
+ : 'SYS NORMAL';
712
+
713
+ // ── Critical Pulse ──
714
+ const hud = document.getElementById('main-hud');
715
+ threatProb > 60 ? hud.classList.add('threat-critical') : hud.classList.remove('threat-critical');
716
+
717
+ document.getElementById('clear-btn').style.display = 'block';
718
+ }
719
+
720
+ // ──────────────────────────────────────────────
721
+ // RESET HUD TO NEUTRAL
722
+ // ──────────────────────────────────────────────
723
+ function clearTarget() {
724
+ uploadedFile = null;
725
+ // SVG
726
+ const svgNode = document.getElementById('target-svg');
727
+ svgNode.style.color = '#00d4b0';
728
+ svgNode.innerHTML = premiumSVGs.none;
729
+ document.getElementById('target-info').innerHTML =
730
+ `<div style="font-size:13px;font-weight:700;color:#00d4b0;margin-top:12px;letter-spacing:.1em;">SCANNING AIRSPACE...</div>`;
731
+ document.getElementById('true-specs-box').style.display = 'none';
732
+
733
+ // Threat gauge
734
+ document.getElementById('threat-arc').style.strokeDasharray = '0 326';
735
+ document.getElementById('threat-arc').style.stroke = '#00d4b0';
736
+ document.getElementById('threat-val').textContent = '0%';
737
+ document.getElementById('threat-val').setAttribute('fill', '#00d4b0');
738
+ document.getElementById('threat-label').textContent = 'SAFE';
739
+ document.getElementById('threat-label').setAttribute('fill', 'rgba(0,212,176,.5)');
740
+
741
+ // Kinematics
742
+ document.getElementById('eta-val').innerText = 'N/A';
743
+ document.getElementById('eta-val').style.color = '#00d4b0';
744
+ document.getElementById('close-val').innerText = '0 M/S';
745
+ document.getElementById('dist-val').innerText = '---';
746
+ document.getElementById('aspect-val').innerText = '---';
747
+ document.getElementById('phase-val').innerText = '---';
748
+
749
+ // Banner
750
+ const banner = document.getElementById('tac-banner');
751
+ banner.style.borderColor = 'rgba(0,212,176,.2)';
752
+ banner.style.background = 'rgba(0,212,176,.05)';
753
+ document.getElementById('tac-text').innerText = 'CONTINUE SCANNING SECTOR';
754
+ document.getElementById('tac-text').style.color = '#00d4b0';
755
+ document.getElementById('conf-text').innerText = 'SYS NORMAL';
756
+
757
+ // Header & Feed Reset
758
+ const hdr = document.getElementById('header-alert');
759
+ hdr.innerText = 'SCANNING AIRSPACE';
760
+ hdr.style.color = 'rgba(0,212,176,.3)';
761
+ hdr.classList.remove('hb');
762
+
763
+ document.getElementById('main-hud').classList.remove('threat-critical');
764
+ document.getElementById('clear-btn').style.display = 'none';
765
+ document.getElementById('sensor-status').innerText = 'STANDBY';
766
+ document.getElementById('sensor-status').style.color = 'rgba(0,212,176,.3)';
767
+ document.getElementById('sensor-img').style.display = 'none';
768
+ document.getElementById('sensor-text').style.display = 'block';
769
+ document.getElementById('file-upload').value = '';
770
+ }
771
+
772
+ // ── INIT ──
773
+ updateOwnPlatform();
774
+ </script>
775
+ </body>
776
+ </html>
release_models/aircraft_classifier/atas_final_fine_tuned_aircraft_classifier_model.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f35c73c33c0318b07ec261f6d1cf37e5ce15543a32867a398841793c9f1fca2d
3
+ size 941789413
release_models/eta/atas_final_eta_regressor_model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:599498f9d68a49295abe2d221e8e5aaf20ba071b2c524b640e176b52bc5a19d7
3
+ size 42999244
release_models/hit/atas_final_hit_classifier_model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d4a4900f2e5e1a4027d6652ff605e363f5c79a97abb640aba09708d6792a78c1
3
+ size 425666
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.136.3
2
+ uvicorn==0.49.0
3
+ python-multipart==0.0.32
4
+ pydantic==2.13.4
5
+ tensorflow==2.16.1
6
+ tf-keras==2.16.0
7
+ tensorflow-hub==0.16.1
8
+ numpy==1.26.4
9
+ pandas
10
+ pillow
11
+ scikit-learn
12
+ xgboost
13
+ joblib
src/__pycache__/classifier.cpython-311.pyc ADDED
Binary file (6.89 kB). View file
 
src/__pycache__/classifier.cpython-313.pyc ADDED
Binary file (6.39 kB). View file
 
src/__pycache__/decision.cpython-311.pyc ADDED
Binary file (3.52 kB). View file
 
src/__pycache__/metadata.cpython-311.pyc ADDED
Binary file (1.61 kB). View file
 
src/__pycache__/models.cpython-311.pyc ADDED
Binary file (3.15 kB). View file
 
src/__pycache__/physics_generator.cpython-311.pyc ADDED
Binary file (15 kB). View file
 
src/__pycache__/schemas.cpython-311.pyc ADDED
Binary file (1.86 kB). View file
 
src/__pycache__/schemas.cpython-313.pyc ADDED
Binary file (1.83 kB). View file
 
src/analysis_for_physics_generator.ipynb ADDED
@@ -0,0 +1,2770 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "f8aa68cc",
6
+ "metadata": {},
7
+ "source": [
8
+ "# **Physics Generator - Design Philosophy**\n",
9
+ "\n",
10
+ "The physics generator creates synthetic missile engagement scenarios for ATAS.\n",
11
+ "\n",
12
+ "Real engagement data is difficult and unrealistic to obtain at scale, so synthetic data is used to generate controlled training scenarios with known ground-truth labels.\n",
13
+ "\n",
14
+ "The generator uses metadata-derived aircraft capability ranges from `aircraft_metadata.csv` instead of fully hardcoded values.\n"
15
+ ]
16
+ },
17
+ {
18
+ "cell_type": "markdown",
19
+ "id": "57887b61",
20
+ "metadata": {},
21
+ "source": [
22
+ "### **aircraft_metadata.csv**\n",
23
+ "\n",
24
+ "This CSV is the threat database for ATAS.\n",
25
+ "\n",
26
+ "The classifier only predicts:\n",
27
+ "```python\n",
28
+ "\"F22\"\n",
29
+ "````\n",
30
+ "\n",
31
+ "The metadata adds tactical information:\n",
32
+ "\n",
33
+ "* missile speed\n",
34
+ "* missile range\n",
35
+ "* aircraft generation\n",
36
+ "* maneuverability\n",
37
+ "* combat capability\n",
38
+ "\n",
39
+ "This allows the system to simulate engagement scenarios.\n",
40
+ "\n",
41
+ "---\n",
42
+ "\n",
43
+ "### Columns\n",
44
+ "\n",
45
+ "#### `aircraft`\n",
46
+ "\n",
47
+ "Aircraft class name.\n",
48
+ "\n",
49
+ "Used to match classifier output with metadata.\n",
50
+ "\n",
51
+ "---\n",
52
+ "\n",
53
+ "#### `missile_speed`\n",
54
+ "\n",
55
+ "Approx missile speed (m/s).\n",
56
+ "\n",
57
+ "Used for:\n",
58
+ "\n",
59
+ "* closure rate\n",
60
+ "* evasion time\n",
61
+ "* threat level\n",
62
+ "\n",
63
+ "Higher speed = less reaction time.\n",
64
+ "\n",
65
+ "---\n",
66
+ "\n",
67
+ "#### `missile_range`\n",
68
+ "\n",
69
+ "Approx max missile range (m).\n",
70
+ "\n",
71
+ "Used for:\n",
72
+ "\n",
73
+ "* launch distance generation\n",
74
+ "* engagement realism\n",
75
+ "\n",
76
+ "Higher range = longer reach.\n",
77
+ "\n",
78
+ "---\n",
79
+ "\n",
80
+ "#### `enemy_generation`\n",
81
+ "\n",
82
+ "Aircraft technology level.\n",
83
+ "\n",
84
+ "Values:\n",
85
+ "\n",
86
+ "* 3.5\n",
87
+ "* 4\n",
88
+ "* 4.5\n",
89
+ "* 5\n",
90
+ "\n",
91
+ "Used for:\n",
92
+ "\n",
93
+ "* threat weighting\n",
94
+ "* hit probability modifiers\n",
95
+ "\n",
96
+ "Higher generation = more dangerous.\n",
97
+ "\n",
98
+ "---\n",
99
+ "\n",
100
+ "#### `maneuverability`\n",
101
+ "\n",
102
+ "Aircraft agility.\n",
103
+ "\n",
104
+ "Values:\n",
105
+ "\n",
106
+ "* 0 = low\n",
107
+ "* 1 = medium\n",
108
+ "* 2 = high\n",
109
+ "\n",
110
+ "Used for:\n",
111
+ "\n",
112
+ "* evasion logic\n",
113
+ "* survival probability\n",
114
+ "\n",
115
+ "Higher maneuverability = harder to hit.\n",
116
+ "\n",
117
+ "---\n",
118
+ "\n",
119
+ "#### `no_aa_capability`\n",
120
+ "\n",
121
+ "Whether aircraft lacks air-to-air combat capability.\n",
122
+ "\n",
123
+ "Values:\n",
124
+ "\n",
125
+ "* 0 = combat capable\n",
126
+ "* 1 = not combat capable\n",
127
+ "\n",
128
+ "Used to:\n",
129
+ "\n",
130
+ "* lower threat score\n",
131
+ "* skip missile logic for support aircraft\n",
132
+ "\n",
133
+ "---\n",
134
+ "\n",
135
+ "## Why This Exists\n",
136
+ "\n",
137
+ "The metadata converts:\n",
138
+ "\n",
139
+ "```python\n",
140
+ "\"What aircraft is this?\"\n",
141
+ "```\n",
142
+ "\n",
143
+ "into:\n",
144
+ "\n",
145
+ "```python\n",
146
+ "\"How dangerous is this aircraft?\"\n",
147
+ "```"
148
+ ]
149
+ },
150
+ {
151
+ "cell_type": "code",
152
+ "execution_count": 2,
153
+ "id": "ce0f85d7",
154
+ "metadata": {},
155
+ "outputs": [],
156
+ "source": [
157
+ "# Importing the libraries\n",
158
+ "\n",
159
+ "import pandas as pd\n",
160
+ "import numpy as np"
161
+ ]
162
+ },
163
+ {
164
+ "cell_type": "code",
165
+ "execution_count": 3,
166
+ "id": "6831bd71",
167
+ "metadata": {},
168
+ "outputs": [
169
+ {
170
+ "data": {
171
+ "text/html": [
172
+ "<div>\n",
173
+ "<style scoped>\n",
174
+ " .dataframe tbody tr th:only-of-type {\n",
175
+ " vertical-align: middle;\n",
176
+ " }\n",
177
+ "\n",
178
+ " .dataframe tbody tr th {\n",
179
+ " vertical-align: top;\n",
180
+ " }\n",
181
+ "\n",
182
+ " .dataframe thead th {\n",
183
+ " text-align: right;\n",
184
+ " }\n",
185
+ "</style>\n",
186
+ "<table border=\"1\" class=\"dataframe\">\n",
187
+ " <thead>\n",
188
+ " <tr style=\"text-align: right;\">\n",
189
+ " <th></th>\n",
190
+ " <th>aircraft</th>\n",
191
+ " <th>aircraft_max_speed</th>\n",
192
+ " <th>missile_speed</th>\n",
193
+ " <th>missile_range</th>\n",
194
+ " <th>enemy_generation</th>\n",
195
+ " <th>maneuverability</th>\n",
196
+ " <th>no_aa_capability</th>\n",
197
+ " </tr>\n",
198
+ " </thead>\n",
199
+ " <tbody>\n",
200
+ " <tr>\n",
201
+ " <th>0</th>\n",
202
+ " <td>A10</td>\n",
203
+ " <td>222</td>\n",
204
+ " <td>857</td>\n",
205
+ " <td>35000</td>\n",
206
+ " <td>4.0</td>\n",
207
+ " <td>1</td>\n",
208
+ " <td>0</td>\n",
209
+ " </tr>\n",
210
+ " <tr>\n",
211
+ " <th>1</th>\n",
212
+ " <td>A400M</td>\n",
213
+ " <td>255</td>\n",
214
+ " <td>-1</td>\n",
215
+ " <td>-1</td>\n",
216
+ " <td>4.0</td>\n",
217
+ " <td>0</td>\n",
218
+ " <td>1</td>\n",
219
+ " </tr>\n",
220
+ " <tr>\n",
221
+ " <th>2</th>\n",
222
+ " <td>AG600</td>\n",
223
+ " <td>155</td>\n",
224
+ " <td>-1</td>\n",
225
+ " <td>-1</td>\n",
226
+ " <td>4.0</td>\n",
227
+ " <td>0</td>\n",
228
+ " <td>1</td>\n",
229
+ " </tr>\n",
230
+ " <tr>\n",
231
+ " <th>3</th>\n",
232
+ " <td>AH64</td>\n",
233
+ " <td>101</td>\n",
234
+ " <td>750</td>\n",
235
+ " <td>8000</td>\n",
236
+ " <td>4.0</td>\n",
237
+ " <td>1</td>\n",
238
+ " <td>0</td>\n",
239
+ " </tr>\n",
240
+ " <tr>\n",
241
+ " <th>4</th>\n",
242
+ " <td>AKINCI</td>\n",
243
+ " <td>100</td>\n",
244
+ " <td>1372</td>\n",
245
+ " <td>65000</td>\n",
246
+ " <td>4.0</td>\n",
247
+ " <td>1</td>\n",
248
+ " <td>0</td>\n",
249
+ " </tr>\n",
250
+ " <tr>\n",
251
+ " <th>...</th>\n",
252
+ " <td>...</td>\n",
253
+ " <td>...</td>\n",
254
+ " <td>...</td>\n",
255
+ " <td>...</td>\n",
256
+ " <td>...</td>\n",
257
+ " <td>...</td>\n",
258
+ " <td>...</td>\n",
259
+ " </tr>\n",
260
+ " <tr>\n",
261
+ " <th>97</th>\n",
262
+ " <td>Y20</td>\n",
263
+ " <td>255</td>\n",
264
+ " <td>-1</td>\n",
265
+ " <td>-1</td>\n",
266
+ " <td>4.0</td>\n",
267
+ " <td>0</td>\n",
268
+ " <td>1</td>\n",
269
+ " </tr>\n",
270
+ " <tr>\n",
271
+ " <th>98</th>\n",
272
+ " <td>YF23</td>\n",
273
+ " <td>648</td>\n",
274
+ " <td>1372</td>\n",
275
+ " <td>160000</td>\n",
276
+ " <td>5.0</td>\n",
277
+ " <td>2</td>\n",
278
+ " <td>0</td>\n",
279
+ " </tr>\n",
280
+ " <tr>\n",
281
+ " <th>99</th>\n",
282
+ " <td>Z10</td>\n",
283
+ " <td>83</td>\n",
284
+ " <td>686</td>\n",
285
+ " <td>8000</td>\n",
286
+ " <td>4.0</td>\n",
287
+ " <td>1</td>\n",
288
+ " <td>0</td>\n",
289
+ " </tr>\n",
290
+ " <tr>\n",
291
+ " <th>100</th>\n",
292
+ " <td>Z19</td>\n",
293
+ " <td>78</td>\n",
294
+ " <td>686</td>\n",
295
+ " <td>8000</td>\n",
296
+ " <td>4.0</td>\n",
297
+ " <td>1</td>\n",
298
+ " <td>0</td>\n",
299
+ " </tr>\n",
300
+ " <tr>\n",
301
+ " <th>101</th>\n",
302
+ " <td>Z21</td>\n",
303
+ " <td>83</td>\n",
304
+ " <td>686</td>\n",
305
+ " <td>8000</td>\n",
306
+ " <td>4.0</td>\n",
307
+ " <td>1</td>\n",
308
+ " <td>0</td>\n",
309
+ " </tr>\n",
310
+ " </tbody>\n",
311
+ "</table>\n",
312
+ "<p>102 rows × 7 columns</p>\n",
313
+ "</div>"
314
+ ],
315
+ "text/plain": [
316
+ " aircraft aircraft_max_speed missile_speed missile_range \\\n",
317
+ "0 A10 222 857 35000 \n",
318
+ "1 A400M 255 -1 -1 \n",
319
+ "2 AG600 155 -1 -1 \n",
320
+ "3 AH64 101 750 8000 \n",
321
+ "4 AKINCI 100 1372 65000 \n",
322
+ ".. ... ... ... ... \n",
323
+ "97 Y20 255 -1 -1 \n",
324
+ "98 YF23 648 1372 160000 \n",
325
+ "99 Z10 83 686 8000 \n",
326
+ "100 Z19 78 686 8000 \n",
327
+ "101 Z21 83 686 8000 \n",
328
+ "\n",
329
+ " enemy_generation maneuverability no_aa_capability \n",
330
+ "0 4.0 1 0 \n",
331
+ "1 4.0 0 1 \n",
332
+ "2 4.0 0 1 \n",
333
+ "3 4.0 1 0 \n",
334
+ "4 4.0 1 0 \n",
335
+ ".. ... ... ... \n",
336
+ "97 4.0 0 1 \n",
337
+ "98 5.0 2 0 \n",
338
+ "99 4.0 1 0 \n",
339
+ "100 4.0 1 0 \n",
340
+ "101 4.0 1 0 \n",
341
+ "\n",
342
+ "[102 rows x 7 columns]"
343
+ ]
344
+ },
345
+ "execution_count": 3,
346
+ "metadata": {},
347
+ "output_type": "execute_result"
348
+ }
349
+ ],
350
+ "source": [
351
+ "# Importing the CSV to work with\n",
352
+ "\n",
353
+ "df = pd.read_csv(\"../data/aircraft_metadata.csv\")\n",
354
+ "df"
355
+ ]
356
+ },
357
+ {
358
+ "cell_type": "code",
359
+ "execution_count": 4,
360
+ "id": "3ac9444c",
361
+ "metadata": {},
362
+ "outputs": [
363
+ {
364
+ "name": "stdout",
365
+ "output_type": "stream",
366
+ "text": [
367
+ "<class 'pandas.DataFrame'>\n",
368
+ "RangeIndex: 102 entries, 0 to 101\n",
369
+ "Data columns (total 7 columns):\n",
370
+ " # Column Non-Null Count Dtype \n",
371
+ "--- ------ -------------- ----- \n",
372
+ " 0 aircraft 102 non-null str \n",
373
+ " 1 aircraft_max_speed 102 non-null int64 \n",
374
+ " 2 missile_speed 102 non-null int64 \n",
375
+ " 3 missile_range 102 non-null int64 \n",
376
+ " 4 enemy_generation 102 non-null float64\n",
377
+ " 5 maneuverability 102 non-null int64 \n",
378
+ " 6 no_aa_capability 102 non-null int64 \n",
379
+ "dtypes: float64(1), int64(5), str(1)\n",
380
+ "memory usage: 5.7 KB\n"
381
+ ]
382
+ }
383
+ ],
384
+ "source": [
385
+ "df.info()"
386
+ ]
387
+ },
388
+ {
389
+ "cell_type": "code",
390
+ "execution_count": 5,
391
+ "id": "c29a7656",
392
+ "metadata": {},
393
+ "outputs": [
394
+ {
395
+ "data": {
396
+ "text/html": [
397
+ "<div>\n",
398
+ "<style scoped>\n",
399
+ " .dataframe tbody tr th:only-of-type {\n",
400
+ " vertical-align: middle;\n",
401
+ " }\n",
402
+ "\n",
403
+ " .dataframe tbody tr th {\n",
404
+ " vertical-align: top;\n",
405
+ " }\n",
406
+ "\n",
407
+ " .dataframe thead th {\n",
408
+ " text-align: right;\n",
409
+ " }\n",
410
+ "</style>\n",
411
+ "<table border=\"1\" class=\"dataframe\">\n",
412
+ " <thead>\n",
413
+ " <tr style=\"text-align: right;\">\n",
414
+ " <th></th>\n",
415
+ " <th>aircraft</th>\n",
416
+ " <th>aircraft_max_speed</th>\n",
417
+ " <th>missile_speed</th>\n",
418
+ " <th>missile_range</th>\n",
419
+ " <th>enemy_generation</th>\n",
420
+ " <th>maneuverability</th>\n",
421
+ " <th>no_aa_capability</th>\n",
422
+ " </tr>\n",
423
+ " </thead>\n",
424
+ " <tbody>\n",
425
+ " <tr>\n",
426
+ " <th>count</th>\n",
427
+ " <td>102</td>\n",
428
+ " <td>102.000000</td>\n",
429
+ " <td>102.000000</td>\n",
430
+ " <td>102.000000</td>\n",
431
+ " <td>102.000000</td>\n",
432
+ " <td>102.000000</td>\n",
433
+ " <td>102.000000</td>\n",
434
+ " </tr>\n",
435
+ " <tr>\n",
436
+ " <th>unique</th>\n",
437
+ " <td>102</td>\n",
438
+ " <td>NaN</td>\n",
439
+ " <td>NaN</td>\n",
440
+ " <td>NaN</td>\n",
441
+ " <td>NaN</td>\n",
442
+ " <td>NaN</td>\n",
443
+ " <td>NaN</td>\n",
444
+ " </tr>\n",
445
+ " <tr>\n",
446
+ " <th>top</th>\n",
447
+ " <td>A10</td>\n",
448
+ " <td>NaN</td>\n",
449
+ " <td>NaN</td>\n",
450
+ " <td>NaN</td>\n",
451
+ " <td>NaN</td>\n",
452
+ " <td>NaN</td>\n",
453
+ " <td>NaN</td>\n",
454
+ " </tr>\n",
455
+ " <tr>\n",
456
+ " <th>freq</th>\n",
457
+ " <td>1</td>\n",
458
+ " <td>NaN</td>\n",
459
+ " <td>NaN</td>\n",
460
+ " <td>NaN</td>\n",
461
+ " <td>NaN</td>\n",
462
+ " <td>NaN</td>\n",
463
+ " <td>NaN</td>\n",
464
+ " </tr>\n",
465
+ " <tr>\n",
466
+ " <th>mean</th>\n",
467
+ " <td>NaN</td>\n",
468
+ " <td>345.764706</td>\n",
469
+ " <td>636.460784</td>\n",
470
+ " <td>64724.990196</td>\n",
471
+ " <td>4.117647</td>\n",
472
+ " <td>0.852941</td>\n",
473
+ " <td>0.500000</td>\n",
474
+ " </tr>\n",
475
+ " <tr>\n",
476
+ " <th>std</th>\n",
477
+ " <td>NaN</td>\n",
478
+ " <td>226.619242</td>\n",
479
+ " <td>692.537831</td>\n",
480
+ " <td>102267.602554</td>\n",
481
+ " <td>0.478118</td>\n",
482
+ " <td>0.825306</td>\n",
483
+ " <td>0.502469</td>\n",
484
+ " </tr>\n",
485
+ " <tr>\n",
486
+ " <th>min</th>\n",
487
+ " <td>NaN</td>\n",
488
+ " <td>61.000000</td>\n",
489
+ " <td>-1.000000</td>\n",
490
+ " <td>-1.000000</td>\n",
491
+ " <td>3.500000</td>\n",
492
+ " <td>0.000000</td>\n",
493
+ " <td>0.000000</td>\n",
494
+ " </tr>\n",
495
+ " <tr>\n",
496
+ " <th>25%</th>\n",
497
+ " <td>NaN</td>\n",
498
+ " <td>164.500000</td>\n",
499
+ " <td>-1.000000</td>\n",
500
+ " <td>-1.000000</td>\n",
501
+ " <td>4.000000</td>\n",
502
+ " <td>0.000000</td>\n",
503
+ " <td>0.000000</td>\n",
504
+ " </tr>\n",
505
+ " <tr>\n",
506
+ " <th>50%</th>\n",
507
+ " <td>NaN</td>\n",
508
+ " <td>268.000000</td>\n",
509
+ " <td>342.500000</td>\n",
510
+ " <td>3999.500000</td>\n",
511
+ " <td>4.000000</td>\n",
512
+ " <td>1.000000</td>\n",
513
+ " <td>0.500000</td>\n",
514
+ " </tr>\n",
515
+ " <tr>\n",
516
+ " <th>75%</th>\n",
517
+ " <td>NaN</td>\n",
518
+ " <td>544.000000</td>\n",
519
+ " <td>1372.000000</td>\n",
520
+ " <td>108750.000000</td>\n",
521
+ " <td>4.500000</td>\n",
522
+ " <td>2.000000</td>\n",
523
+ " <td>1.000000</td>\n",
524
+ " </tr>\n",
525
+ " <tr>\n",
526
+ " <th>max</th>\n",
527
+ " <td>NaN</td>\n",
528
+ " <td>983.000000</td>\n",
529
+ " <td>2058.000000</td>\n",
530
+ " <td>400000.000000</td>\n",
531
+ " <td>5.000000</td>\n",
532
+ " <td>2.000000</td>\n",
533
+ " <td>1.000000</td>\n",
534
+ " </tr>\n",
535
+ " </tbody>\n",
536
+ "</table>\n",
537
+ "</div>"
538
+ ],
539
+ "text/plain": [
540
+ " aircraft aircraft_max_speed missile_speed missile_range \\\n",
541
+ "count 102 102.000000 102.000000 102.000000 \n",
542
+ "unique 102 NaN NaN NaN \n",
543
+ "top A10 NaN NaN NaN \n",
544
+ "freq 1 NaN NaN NaN \n",
545
+ "mean NaN 345.764706 636.460784 64724.990196 \n",
546
+ "std NaN 226.619242 692.537831 102267.602554 \n",
547
+ "min NaN 61.000000 -1.000000 -1.000000 \n",
548
+ "25% NaN 164.500000 -1.000000 -1.000000 \n",
549
+ "50% NaN 268.000000 342.500000 3999.500000 \n",
550
+ "75% NaN 544.000000 1372.000000 108750.000000 \n",
551
+ "max NaN 983.000000 2058.000000 400000.000000 \n",
552
+ "\n",
553
+ " enemy_generation maneuverability no_aa_capability \n",
554
+ "count 102.000000 102.000000 102.000000 \n",
555
+ "unique NaN NaN NaN \n",
556
+ "top NaN NaN NaN \n",
557
+ "freq NaN NaN NaN \n",
558
+ "mean 4.117647 0.852941 0.500000 \n",
559
+ "std 0.478118 0.825306 0.502469 \n",
560
+ "min 3.500000 0.000000 0.000000 \n",
561
+ "25% 4.000000 0.000000 0.000000 \n",
562
+ "50% 4.000000 1.000000 0.500000 \n",
563
+ "75% 4.500000 2.000000 1.000000 \n",
564
+ "max 5.000000 2.000000 1.000000 "
565
+ ]
566
+ },
567
+ "execution_count": 5,
568
+ "metadata": {},
569
+ "output_type": "execute_result"
570
+ }
571
+ ],
572
+ "source": [
573
+ "df.describe(include=\"all\")"
574
+ ]
575
+ },
576
+ {
577
+ "cell_type": "code",
578
+ "execution_count": 6,
579
+ "id": "ce49133d",
580
+ "metadata": {},
581
+ "outputs": [
582
+ {
583
+ "name": "stdout",
584
+ "output_type": "stream",
585
+ "text": [
586
+ "[4. 3.5 4.5 5. ]\n",
587
+ "[1 0 2]\n",
588
+ "[0 1]\n"
589
+ ]
590
+ }
591
+ ],
592
+ "source": [
593
+ "print(df[\"enemy_generation\"].unique())\n",
594
+ "print(df[\"maneuverability\"].unique())\n",
595
+ "print(df[\"no_aa_capability\"].unique())"
596
+ ]
597
+ },
598
+ {
599
+ "cell_type": "code",
600
+ "execution_count": 7,
601
+ "id": "0297a837",
602
+ "metadata": {},
603
+ "outputs": [
604
+ {
605
+ "data": {
606
+ "text/html": [
607
+ "<div>\n",
608
+ "<style scoped>\n",
609
+ " .dataframe tbody tr th:only-of-type {\n",
610
+ " vertical-align: middle;\n",
611
+ " }\n",
612
+ "\n",
613
+ " .dataframe tbody tr th {\n",
614
+ " vertical-align: top;\n",
615
+ " }\n",
616
+ "\n",
617
+ " .dataframe thead th {\n",
618
+ " text-align: right;\n",
619
+ " }\n",
620
+ "</style>\n",
621
+ "<table border=\"1\" class=\"dataframe\">\n",
622
+ " <thead>\n",
623
+ " <tr style=\"text-align: right;\">\n",
624
+ " <th></th>\n",
625
+ " <th>aircraft</th>\n",
626
+ " <th>aircraft_max_speed</th>\n",
627
+ " <th>missile_speed</th>\n",
628
+ " <th>missile_range</th>\n",
629
+ " <th>enemy_generation</th>\n",
630
+ " <th>maneuverability</th>\n",
631
+ " <th>no_aa_capability</th>\n",
632
+ " </tr>\n",
633
+ " </thead>\n",
634
+ " <tbody>\n",
635
+ " <tr>\n",
636
+ " <th>0</th>\n",
637
+ " <td>A10</td>\n",
638
+ " <td>222</td>\n",
639
+ " <td>857</td>\n",
640
+ " <td>35000</td>\n",
641
+ " <td>4.0</td>\n",
642
+ " <td>1</td>\n",
643
+ " <td>0</td>\n",
644
+ " </tr>\n",
645
+ " <tr>\n",
646
+ " <th>1</th>\n",
647
+ " <td>AH64</td>\n",
648
+ " <td>101</td>\n",
649
+ " <td>750</td>\n",
650
+ " <td>8000</td>\n",
651
+ " <td>4.0</td>\n",
652
+ " <td>1</td>\n",
653
+ " <td>0</td>\n",
654
+ " </tr>\n",
655
+ " <tr>\n",
656
+ " <th>2</th>\n",
657
+ " <td>AKINCI</td>\n",
658
+ " <td>100</td>\n",
659
+ " <td>1372</td>\n",
660
+ " <td>65000</td>\n",
661
+ " <td>4.0</td>\n",
662
+ " <td>1</td>\n",
663
+ " <td>0</td>\n",
664
+ " </tr>\n",
665
+ " <tr>\n",
666
+ " <th>3</th>\n",
667
+ " <td>AV8B</td>\n",
668
+ " <td>300</td>\n",
669
+ " <td>1372</td>\n",
670
+ " <td>160000</td>\n",
671
+ " <td>4.0</td>\n",
672
+ " <td>1</td>\n",
673
+ " <td>0</td>\n",
674
+ " </tr>\n",
675
+ " <tr>\n",
676
+ " <th>4</th>\n",
677
+ " <td>EF2000</td>\n",
678
+ " <td>590</td>\n",
679
+ " <td>1372</td>\n",
680
+ " <td>200000</td>\n",
681
+ " <td>4.5</td>\n",
682
+ " <td>2</td>\n",
683
+ " <td>0</td>\n",
684
+ " </tr>\n",
685
+ " </tbody>\n",
686
+ "</table>\n",
687
+ "</div>"
688
+ ],
689
+ "text/plain": [
690
+ " aircraft aircraft_max_speed missile_speed missile_range \\\n",
691
+ "0 A10 222 857 35000 \n",
692
+ "1 AH64 101 750 8000 \n",
693
+ "2 AKINCI 100 1372 65000 \n",
694
+ "3 AV8B 300 1372 160000 \n",
695
+ "4 EF2000 590 1372 200000 \n",
696
+ "\n",
697
+ " enemy_generation maneuverability no_aa_capability \n",
698
+ "0 4.0 1 0 \n",
699
+ "1 4.0 1 0 \n",
700
+ "2 4.0 1 0 \n",
701
+ "3 4.0 1 0 \n",
702
+ "4 4.5 2 0 "
703
+ ]
704
+ },
705
+ "execution_count": 7,
706
+ "metadata": {},
707
+ "output_type": "execute_result"
708
+ }
709
+ ],
710
+ "source": [
711
+ "# Getting data of aircarfts that has air-to-air combact ability\n",
712
+ "combat_df = df[df[\"no_aa_capability\"]==0].reset_index(drop=True)\n",
713
+ "combat_df.head()"
714
+ ]
715
+ },
716
+ {
717
+ "cell_type": "code",
718
+ "execution_count": 8,
719
+ "id": "c578c9c1",
720
+ "metadata": {},
721
+ "outputs": [
722
+ {
723
+ "name": "stdout",
724
+ "output_type": "stream",
725
+ "text": [
726
+ "<class 'pandas.DataFrame'>\n",
727
+ "RangeIndex: 51 entries, 0 to 50\n",
728
+ "Data columns (total 7 columns):\n",
729
+ " # Column Non-Null Count Dtype \n",
730
+ "--- ------ -------------- ----- \n",
731
+ " 0 aircraft 51 non-null str \n",
732
+ " 1 aircraft_max_speed 51 non-null int64 \n",
733
+ " 2 missile_speed 51 non-null int64 \n",
734
+ " 3 missile_range 51 non-null int64 \n",
735
+ " 4 enemy_generation 51 non-null float64\n",
736
+ " 5 maneuverability 51 non-null int64 \n",
737
+ " 6 no_aa_capability 51 non-null int64 \n",
738
+ "dtypes: float64(1), int64(5), str(1)\n",
739
+ "memory usage: 2.9 KB\n"
740
+ ]
741
+ }
742
+ ],
743
+ "source": [
744
+ "combat_df.info()"
745
+ ]
746
+ },
747
+ {
748
+ "cell_type": "code",
749
+ "execution_count": 9,
750
+ "id": "2dc32a13",
751
+ "metadata": {},
752
+ "outputs": [
753
+ {
754
+ "data": {
755
+ "text/html": [
756
+ "<div>\n",
757
+ "<style scoped>\n",
758
+ " .dataframe tbody tr th:only-of-type {\n",
759
+ " vertical-align: middle;\n",
760
+ " }\n",
761
+ "\n",
762
+ " .dataframe tbody tr th {\n",
763
+ " vertical-align: top;\n",
764
+ " }\n",
765
+ "\n",
766
+ " .dataframe thead th {\n",
767
+ " text-align: right;\n",
768
+ " }\n",
769
+ "</style>\n",
770
+ "<table border=\"1\" class=\"dataframe\">\n",
771
+ " <thead>\n",
772
+ " <tr style=\"text-align: right;\">\n",
773
+ " <th></th>\n",
774
+ " <th>aircraft</th>\n",
775
+ " <th>aircraft_max_speed</th>\n",
776
+ " <th>missile_speed</th>\n",
777
+ " <th>missile_range</th>\n",
778
+ " <th>enemy_generation</th>\n",
779
+ " <th>maneuverability</th>\n",
780
+ " <th>no_aa_capability</th>\n",
781
+ " </tr>\n",
782
+ " </thead>\n",
783
+ " <tbody>\n",
784
+ " <tr>\n",
785
+ " <th>count</th>\n",
786
+ " <td>51</td>\n",
787
+ " <td>51.000000</td>\n",
788
+ " <td>51.000000</td>\n",
789
+ " <td>51.000000</td>\n",
790
+ " <td>51.000000</td>\n",
791
+ " <td>51.000000</td>\n",
792
+ " <td>51.0</td>\n",
793
+ " </tr>\n",
794
+ " <tr>\n",
795
+ " <th>unique</th>\n",
796
+ " <td>51</td>\n",
797
+ " <td>NaN</td>\n",
798
+ " <td>NaN</td>\n",
799
+ " <td>NaN</td>\n",
800
+ " <td>NaN</td>\n",
801
+ " <td>NaN</td>\n",
802
+ " <td>NaN</td>\n",
803
+ " </tr>\n",
804
+ " <tr>\n",
805
+ " <th>top</th>\n",
806
+ " <td>A10</td>\n",
807
+ " <td>NaN</td>\n",
808
+ " <td>NaN</td>\n",
809
+ " <td>NaN</td>\n",
810
+ " <td>NaN</td>\n",
811
+ " <td>NaN</td>\n",
812
+ " <td>NaN</td>\n",
813
+ " </tr>\n",
814
+ " <tr>\n",
815
+ " <th>freq</th>\n",
816
+ " <td>1</td>\n",
817
+ " <td>NaN</td>\n",
818
+ " <td>NaN</td>\n",
819
+ " <td>NaN</td>\n",
820
+ " <td>NaN</td>\n",
821
+ " <td>NaN</td>\n",
822
+ " <td>NaN</td>\n",
823
+ " </tr>\n",
824
+ " <tr>\n",
825
+ " <th>mean</th>\n",
826
+ " <td>NaN</td>\n",
827
+ " <td>440.235294</td>\n",
828
+ " <td>1273.921569</td>\n",
829
+ " <td>129450.980392</td>\n",
830
+ " <td>4.284314</td>\n",
831
+ " <td>1.529412</td>\n",
832
+ " <td>0.0</td>\n",
833
+ " </tr>\n",
834
+ " <tr>\n",
835
+ " <th>std</th>\n",
836
+ " <td>NaN</td>\n",
837
+ " <td>225.509609</td>\n",
838
+ " <td>373.955497</td>\n",
839
+ " <td>112160.655085</td>\n",
840
+ " <td>0.461030</td>\n",
841
+ " <td>0.504101</td>\n",
842
+ " <td>0.0</td>\n",
843
+ " </tr>\n",
844
+ " <tr>\n",
845
+ " <th>min</th>\n",
846
+ " <td>NaN</td>\n",
847
+ " <td>78.000000</td>\n",
848
+ " <td>686.000000</td>\n",
849
+ " <td>8000.000000</td>\n",
850
+ " <td>3.500000</td>\n",
851
+ " <td>1.000000</td>\n",
852
+ " <td>0.0</td>\n",
853
+ " </tr>\n",
854
+ " <tr>\n",
855
+ " <th>25%</th>\n",
856
+ " <td>NaN</td>\n",
857
+ " <td>246.500000</td>\n",
858
+ " <td>857.000000</td>\n",
859
+ " <td>37500.000000</td>\n",
860
+ " <td>4.000000</td>\n",
861
+ " <td>1.000000</td>\n",
862
+ " <td>0.0</td>\n",
863
+ " </tr>\n",
864
+ " <tr>\n",
865
+ " <th>50%</th>\n",
866
+ " <td>NaN</td>\n",
867
+ " <td>532.000000</td>\n",
868
+ " <td>1372.000000</td>\n",
869
+ " <td>110000.000000</td>\n",
870
+ " <td>4.000000</td>\n",
871
+ " <td>2.000000</td>\n",
872
+ " <td>0.0</td>\n",
873
+ " </tr>\n",
874
+ " <tr>\n",
875
+ " <th>75%</th>\n",
876
+ " <td>NaN</td>\n",
877
+ " <td>590.000000</td>\n",
878
+ " <td>1372.000000</td>\n",
879
+ " <td>160000.000000</td>\n",
880
+ " <td>4.500000</td>\n",
881
+ " <td>2.000000</td>\n",
882
+ " <td>0.0</td>\n",
883
+ " </tr>\n",
884
+ " <tr>\n",
885
+ " <th>max</th>\n",
886
+ " <td>NaN</td>\n",
887
+ " <td>833.000000</td>\n",
888
+ " <td>2058.000000</td>\n",
889
+ " <td>400000.000000</td>\n",
890
+ " <td>5.000000</td>\n",
891
+ " <td>2.000000</td>\n",
892
+ " <td>0.0</td>\n",
893
+ " </tr>\n",
894
+ " </tbody>\n",
895
+ "</table>\n",
896
+ "</div>"
897
+ ],
898
+ "text/plain": [
899
+ " aircraft aircraft_max_speed missile_speed missile_range \\\n",
900
+ "count 51 51.000000 51.000000 51.000000 \n",
901
+ "unique 51 NaN NaN NaN \n",
902
+ "top A10 NaN NaN NaN \n",
903
+ "freq 1 NaN NaN NaN \n",
904
+ "mean NaN 440.235294 1273.921569 129450.980392 \n",
905
+ "std NaN 225.509609 373.955497 112160.655085 \n",
906
+ "min NaN 78.000000 686.000000 8000.000000 \n",
907
+ "25% NaN 246.500000 857.000000 37500.000000 \n",
908
+ "50% NaN 532.000000 1372.000000 110000.000000 \n",
909
+ "75% NaN 590.000000 1372.000000 160000.000000 \n",
910
+ "max NaN 833.000000 2058.000000 400000.000000 \n",
911
+ "\n",
912
+ " enemy_generation maneuverability no_aa_capability \n",
913
+ "count 51.000000 51.000000 51.0 \n",
914
+ "unique NaN NaN NaN \n",
915
+ "top NaN NaN NaN \n",
916
+ "freq NaN NaN NaN \n",
917
+ "mean 4.284314 1.529412 0.0 \n",
918
+ "std 0.461030 0.504101 0.0 \n",
919
+ "min 3.500000 1.000000 0.0 \n",
920
+ "25% 4.000000 1.000000 0.0 \n",
921
+ "50% 4.000000 2.000000 0.0 \n",
922
+ "75% 4.500000 2.000000 0.0 \n",
923
+ "max 5.000000 2.000000 0.0 "
924
+ ]
925
+ },
926
+ "execution_count": 9,
927
+ "metadata": {},
928
+ "output_type": "execute_result"
929
+ }
930
+ ],
931
+ "source": [
932
+ "combat_df.describe(include=\"all\")"
933
+ ]
934
+ },
935
+ {
936
+ "cell_type": "code",
937
+ "execution_count": 10,
938
+ "id": "b51a060f",
939
+ "metadata": {},
940
+ "outputs": [
941
+ {
942
+ "name": "stdout",
943
+ "output_type": "stream",
944
+ "text": [
945
+ "Missile Speed ranges: \n",
946
+ "\n",
947
+ "Max → 2058 \n",
948
+ "Min → 686\n"
949
+ ]
950
+ }
951
+ ],
952
+ "source": [
953
+ "# Getting missile speed ranges\n",
954
+ "print(\"Missile Speed ranges: \\n\")\n",
955
+ "print(f\"Max → {combat_df['missile_speed'].max()} \\nMin → {combat_df['missile_speed'].min()}\")"
956
+ ]
957
+ },
958
+ {
959
+ "cell_type": "code",
960
+ "execution_count": 11,
961
+ "id": "d76aabf2",
962
+ "metadata": {},
963
+ "outputs": [
964
+ {
965
+ "name": "stdout",
966
+ "output_type": "stream",
967
+ "text": [
968
+ "Missile range: \n",
969
+ "\n",
970
+ "Max → 400000 \n",
971
+ "Min → 8000\n"
972
+ ]
973
+ }
974
+ ],
975
+ "source": [
976
+ "# Getting missile speed ranges\n",
977
+ "print(\"Missile range: \\n\")\n",
978
+ "print(f\"Max → {combat_df['missile_range'].max()} \\nMin → {combat_df['missile_range'].min()}\")"
979
+ ]
980
+ },
981
+ {
982
+ "cell_type": "code",
983
+ "execution_count": 12,
984
+ "id": "1e7207e0",
985
+ "metadata": {},
986
+ "outputs": [
987
+ {
988
+ "name": "stdout",
989
+ "output_type": "stream",
990
+ "text": [
991
+ "Aircraft speed range: \n",
992
+ "\n",
993
+ "Max → 983 \n",
994
+ "Min → 61\n"
995
+ ]
996
+ }
997
+ ],
998
+ "source": [
999
+ "# Getting aircraft speed ranges\n",
1000
+ "print(\"Aircraft speed range: \\n\")\n",
1001
+ "print(f\"Max → {df['aircraft_max_speed'].max()} \\nMin → {df['aircraft_max_speed'].min()}\")"
1002
+ ]
1003
+ },
1004
+ {
1005
+ "cell_type": "code",
1006
+ "execution_count": 13,
1007
+ "id": "21a1d9d3",
1008
+ "metadata": {},
1009
+ "outputs": [
1010
+ {
1011
+ "data": {
1012
+ "text/plain": [
1013
+ "enemy_generation\n",
1014
+ "4.0 25\n",
1015
+ "4.5 11\n",
1016
+ "5.0 11\n",
1017
+ "3.5 4\n",
1018
+ "Name: count, dtype: int64"
1019
+ ]
1020
+ },
1021
+ "execution_count": 13,
1022
+ "metadata": {},
1023
+ "output_type": "execute_result"
1024
+ }
1025
+ ],
1026
+ "source": [
1027
+ "# Getting the number of unique generation\n",
1028
+ "combat_df[\"enemy_generation\"].value_counts()"
1029
+ ]
1030
+ },
1031
+ {
1032
+ "cell_type": "code",
1033
+ "execution_count": 14,
1034
+ "id": "d47323a4",
1035
+ "metadata": {},
1036
+ "outputs": [
1037
+ {
1038
+ "data": {
1039
+ "text/plain": [
1040
+ "maneuverability\n",
1041
+ "2 27\n",
1042
+ "1 24\n",
1043
+ "Name: count, dtype: int64"
1044
+ ]
1045
+ },
1046
+ "execution_count": 14,
1047
+ "metadata": {},
1048
+ "output_type": "execute_result"
1049
+ }
1050
+ ],
1051
+ "source": [
1052
+ "# Getting the maneverability level\n",
1053
+ "combat_df[\"maneuverability\"].value_counts()"
1054
+ ]
1055
+ },
1056
+ {
1057
+ "cell_type": "code",
1058
+ "execution_count": 15,
1059
+ "id": "7471829e",
1060
+ "metadata": {},
1061
+ "outputs": [
1062
+ {
1063
+ "data": {
1064
+ "text/html": [
1065
+ "<div>\n",
1066
+ "<style scoped>\n",
1067
+ " .dataframe tbody tr th:only-of-type {\n",
1068
+ " vertical-align: middle;\n",
1069
+ " }\n",
1070
+ "\n",
1071
+ " .dataframe tbody tr th {\n",
1072
+ " vertical-align: top;\n",
1073
+ " }\n",
1074
+ "\n",
1075
+ " .dataframe thead th {\n",
1076
+ " text-align: right;\n",
1077
+ " }\n",
1078
+ "</style>\n",
1079
+ "<table border=\"1\" class=\"dataframe\">\n",
1080
+ " <thead>\n",
1081
+ " <tr style=\"text-align: right;\">\n",
1082
+ " <th></th>\n",
1083
+ " <th>aircraft</th>\n",
1084
+ " <th>aircraft_max_speed</th>\n",
1085
+ " <th>missile_speed</th>\n",
1086
+ " <th>missile_range</th>\n",
1087
+ " <th>enemy_generation</th>\n",
1088
+ " <th>maneuverability</th>\n",
1089
+ " <th>no_aa_capability</th>\n",
1090
+ " </tr>\n",
1091
+ " </thead>\n",
1092
+ " <tbody>\n",
1093
+ " <tr>\n",
1094
+ " <th>14</th>\n",
1095
+ " <td>FCK1</td>\n",
1096
+ " <td>532</td>\n",
1097
+ " <td>2058</td>\n",
1098
+ " <td>100000</td>\n",
1099
+ " <td>4.0</td>\n",
1100
+ " <td>2</td>\n",
1101
+ " <td>0</td>\n",
1102
+ " </tr>\n",
1103
+ " <tr>\n",
1104
+ " <th>39</th>\n",
1105
+ " <td>Su57</td>\n",
1106
+ " <td>590</td>\n",
1107
+ " <td>2058</td>\n",
1108
+ " <td>400000</td>\n",
1109
+ " <td>5.0</td>\n",
1110
+ " <td>2</td>\n",
1111
+ " <td>0</td>\n",
1112
+ " </tr>\n",
1113
+ " <tr>\n",
1114
+ " <th>32</th>\n",
1115
+ " <td>Mig31</td>\n",
1116
+ " <td>833</td>\n",
1117
+ " <td>2058</td>\n",
1118
+ " <td>400000</td>\n",
1119
+ " <td>4.0</td>\n",
1120
+ " <td>1</td>\n",
1121
+ " <td>0</td>\n",
1122
+ " </tr>\n",
1123
+ " <tr>\n",
1124
+ " <th>18</th>\n",
1125
+ " <td>J36</td>\n",
1126
+ " <td>590</td>\n",
1127
+ " <td>1715</td>\n",
1128
+ " <td>400000</td>\n",
1129
+ " <td>5.0</td>\n",
1130
+ " <td>2</td>\n",
1131
+ " <td>0</td>\n",
1132
+ " </tr>\n",
1133
+ " <tr>\n",
1134
+ " <th>19</th>\n",
1135
+ " <td>J50</td>\n",
1136
+ " <td>590</td>\n",
1137
+ " <td>1715</td>\n",
1138
+ " <td>400000</td>\n",
1139
+ " <td>5.0</td>\n",
1140
+ " <td>2</td>\n",
1141
+ " <td>0</td>\n",
1142
+ " </tr>\n",
1143
+ " <tr>\n",
1144
+ " <th>17</th>\n",
1145
+ " <td>J35</td>\n",
1146
+ " <td>590</td>\n",
1147
+ " <td>1715</td>\n",
1148
+ " <td>300000</td>\n",
1149
+ " <td>5.0</td>\n",
1150
+ " <td>2</td>\n",
1151
+ " <td>0</td>\n",
1152
+ " </tr>\n",
1153
+ " <tr>\n",
1154
+ " <th>16</th>\n",
1155
+ " <td>J20</td>\n",
1156
+ " <td>590</td>\n",
1157
+ " <td>1715</td>\n",
1158
+ " <td>300000</td>\n",
1159
+ " <td>5.0</td>\n",
1160
+ " <td>2</td>\n",
1161
+ " <td>0</td>\n",
1162
+ " </tr>\n",
1163
+ " <tr>\n",
1164
+ " <th>15</th>\n",
1165
+ " <td>J10</td>\n",
1166
+ " <td>650</td>\n",
1167
+ " <td>1715</td>\n",
1168
+ " <td>300000</td>\n",
1169
+ " <td>4.5</td>\n",
1170
+ " <td>2</td>\n",
1171
+ " <td>0</td>\n",
1172
+ " </tr>\n",
1173
+ " <tr>\n",
1174
+ " <th>10</th>\n",
1175
+ " <td>F2</td>\n",
1176
+ " <td>590</td>\n",
1177
+ " <td>1544</td>\n",
1178
+ " <td>105000</td>\n",
1179
+ " <td>4.5</td>\n",
1180
+ " <td>2</td>\n",
1181
+ " <td>0</td>\n",
1182
+ " </tr>\n",
1183
+ " <tr>\n",
1184
+ " <th>41</th>\n",
1185
+ " <td>Tejas</td>\n",
1186
+ " <td>550</td>\n",
1187
+ " <td>1544</td>\n",
1188
+ " <td>110000</td>\n",
1189
+ " <td>4.5</td>\n",
1190
+ " <td>2</td>\n",
1191
+ " <td>0</td>\n",
1192
+ " </tr>\n",
1193
+ " </tbody>\n",
1194
+ "</table>\n",
1195
+ "</div>"
1196
+ ],
1197
+ "text/plain": [
1198
+ " aircraft aircraft_max_speed missile_speed missile_range \\\n",
1199
+ "14 FCK1 532 2058 100000 \n",
1200
+ "39 Su57 590 2058 400000 \n",
1201
+ "32 Mig31 833 2058 400000 \n",
1202
+ "18 J36 590 1715 400000 \n",
1203
+ "19 J50 590 1715 400000 \n",
1204
+ "17 J35 590 1715 300000 \n",
1205
+ "16 J20 590 1715 300000 \n",
1206
+ "15 J10 650 1715 300000 \n",
1207
+ "10 F2 590 1544 105000 \n",
1208
+ "41 Tejas 550 1544 110000 \n",
1209
+ "\n",
1210
+ " enemy_generation maneuverability no_aa_capability \n",
1211
+ "14 4.0 2 0 \n",
1212
+ "39 5.0 2 0 \n",
1213
+ "32 4.0 1 0 \n",
1214
+ "18 5.0 2 0 \n",
1215
+ "19 5.0 2 0 \n",
1216
+ "17 5.0 2 0 \n",
1217
+ "16 5.0 2 0 \n",
1218
+ "15 4.5 2 0 \n",
1219
+ "10 4.5 2 0 \n",
1220
+ "41 4.5 2 0 "
1221
+ ]
1222
+ },
1223
+ "execution_count": 15,
1224
+ "metadata": {},
1225
+ "output_type": "execute_result"
1226
+ }
1227
+ ],
1228
+ "source": [
1229
+ "# get values of ranges of missile speed\n",
1230
+ "combat_df.sort_values(\"missile_speed\", ascending=False).head(10)"
1231
+ ]
1232
+ },
1233
+ {
1234
+ "cell_type": "code",
1235
+ "execution_count": 16,
1236
+ "id": "f14cb2cf",
1237
+ "metadata": {},
1238
+ "outputs": [
1239
+ {
1240
+ "data": {
1241
+ "text/html": [
1242
+ "<div>\n",
1243
+ "<style scoped>\n",
1244
+ " .dataframe tbody tr th:only-of-type {\n",
1245
+ " vertical-align: middle;\n",
1246
+ " }\n",
1247
+ "\n",
1248
+ " .dataframe tbody tr th {\n",
1249
+ " vertical-align: top;\n",
1250
+ " }\n",
1251
+ "\n",
1252
+ " .dataframe thead th {\n",
1253
+ " text-align: right;\n",
1254
+ " }\n",
1255
+ "</style>\n",
1256
+ "<table border=\"1\" class=\"dataframe\">\n",
1257
+ " <thead>\n",
1258
+ " <tr style=\"text-align: right;\">\n",
1259
+ " <th></th>\n",
1260
+ " <th>aircraft</th>\n",
1261
+ " <th>aircraft_max_speed</th>\n",
1262
+ " <th>missile_speed</th>\n",
1263
+ " <th>missile_range</th>\n",
1264
+ " <th>enemy_generation</th>\n",
1265
+ " <th>maneuverability</th>\n",
1266
+ " <th>no_aa_capability</th>\n",
1267
+ " </tr>\n",
1268
+ " </thead>\n",
1269
+ " <tbody>\n",
1270
+ " <tr>\n",
1271
+ " <th>19</th>\n",
1272
+ " <td>J50</td>\n",
1273
+ " <td>590</td>\n",
1274
+ " <td>1715</td>\n",
1275
+ " <td>400000</td>\n",
1276
+ " <td>5.0</td>\n",
1277
+ " <td>2</td>\n",
1278
+ " <td>0</td>\n",
1279
+ " </tr>\n",
1280
+ " <tr>\n",
1281
+ " <th>39</th>\n",
1282
+ " <td>Su57</td>\n",
1283
+ " <td>590</td>\n",
1284
+ " <td>2058</td>\n",
1285
+ " <td>400000</td>\n",
1286
+ " <td>5.0</td>\n",
1287
+ " <td>2</td>\n",
1288
+ " <td>0</td>\n",
1289
+ " </tr>\n",
1290
+ " <tr>\n",
1291
+ " <th>18</th>\n",
1292
+ " <td>J36</td>\n",
1293
+ " <td>590</td>\n",
1294
+ " <td>1715</td>\n",
1295
+ " <td>400000</td>\n",
1296
+ " <td>5.0</td>\n",
1297
+ " <td>2</td>\n",
1298
+ " <td>0</td>\n",
1299
+ " </tr>\n",
1300
+ " <tr>\n",
1301
+ " <th>32</th>\n",
1302
+ " <td>Mig31</td>\n",
1303
+ " <td>833</td>\n",
1304
+ " <td>2058</td>\n",
1305
+ " <td>400000</td>\n",
1306
+ " <td>4.0</td>\n",
1307
+ " <td>1</td>\n",
1308
+ " <td>0</td>\n",
1309
+ " </tr>\n",
1310
+ " <tr>\n",
1311
+ " <th>17</th>\n",
1312
+ " <td>J35</td>\n",
1313
+ " <td>590</td>\n",
1314
+ " <td>1715</td>\n",
1315
+ " <td>300000</td>\n",
1316
+ " <td>5.0</td>\n",
1317
+ " <td>2</td>\n",
1318
+ " <td>0</td>\n",
1319
+ " </tr>\n",
1320
+ " <tr>\n",
1321
+ " <th>16</th>\n",
1322
+ " <td>J20</td>\n",
1323
+ " <td>590</td>\n",
1324
+ " <td>1715</td>\n",
1325
+ " <td>300000</td>\n",
1326
+ " <td>5.0</td>\n",
1327
+ " <td>2</td>\n",
1328
+ " <td>0</td>\n",
1329
+ " </tr>\n",
1330
+ " <tr>\n",
1331
+ " <th>15</th>\n",
1332
+ " <td>J10</td>\n",
1333
+ " <td>650</td>\n",
1334
+ " <td>1715</td>\n",
1335
+ " <td>300000</td>\n",
1336
+ " <td>4.5</td>\n",
1337
+ " <td>2</td>\n",
1338
+ " <td>0</td>\n",
1339
+ " </tr>\n",
1340
+ " <tr>\n",
1341
+ " <th>34</th>\n",
1342
+ " <td>Rafale</td>\n",
1343
+ " <td>531</td>\n",
1344
+ " <td>1372</td>\n",
1345
+ " <td>200000</td>\n",
1346
+ " <td>4.5</td>\n",
1347
+ " <td>2</td>\n",
1348
+ " <td>0</td>\n",
1349
+ " </tr>\n",
1350
+ " <tr>\n",
1351
+ " <th>20</th>\n",
1352
+ " <td>JAS39</td>\n",
1353
+ " <td>590</td>\n",
1354
+ " <td>1372</td>\n",
1355
+ " <td>200000</td>\n",
1356
+ " <td>4.5</td>\n",
1357
+ " <td>2</td>\n",
1358
+ " <td>0</td>\n",
1359
+ " </tr>\n",
1360
+ " <tr>\n",
1361
+ " <th>24</th>\n",
1362
+ " <td>KF21</td>\n",
1363
+ " <td>532</td>\n",
1364
+ " <td>1372</td>\n",
1365
+ " <td>200000</td>\n",
1366
+ " <td>4.5</td>\n",
1367
+ " <td>2</td>\n",
1368
+ " <td>0</td>\n",
1369
+ " </tr>\n",
1370
+ " </tbody>\n",
1371
+ "</table>\n",
1372
+ "</div>"
1373
+ ],
1374
+ "text/plain": [
1375
+ " aircraft aircraft_max_speed missile_speed missile_range \\\n",
1376
+ "19 J50 590 1715 400000 \n",
1377
+ "39 Su57 590 2058 400000 \n",
1378
+ "18 J36 590 1715 400000 \n",
1379
+ "32 Mig31 833 2058 400000 \n",
1380
+ "17 J35 590 1715 300000 \n",
1381
+ "16 J20 590 1715 300000 \n",
1382
+ "15 J10 650 1715 300000 \n",
1383
+ "34 Rafale 531 1372 200000 \n",
1384
+ "20 JAS39 590 1372 200000 \n",
1385
+ "24 KF21 532 1372 200000 \n",
1386
+ "\n",
1387
+ " enemy_generation maneuverability no_aa_capability \n",
1388
+ "19 5.0 2 0 \n",
1389
+ "39 5.0 2 0 \n",
1390
+ "18 5.0 2 0 \n",
1391
+ "32 4.0 1 0 \n",
1392
+ "17 5.0 2 0 \n",
1393
+ "16 5.0 2 0 \n",
1394
+ "15 4.5 2 0 \n",
1395
+ "34 4.5 2 0 \n",
1396
+ "20 4.5 2 0 \n",
1397
+ "24 4.5 2 0 "
1398
+ ]
1399
+ },
1400
+ "execution_count": 16,
1401
+ "metadata": {},
1402
+ "output_type": "execute_result"
1403
+ }
1404
+ ],
1405
+ "source": [
1406
+ "# Get sorted decending missile ranges\n",
1407
+ "combat_df.sort_values(\"missile_range\", ascending=False)[:10]"
1408
+ ]
1409
+ },
1410
+ {
1411
+ "cell_type": "code",
1412
+ "execution_count": 17,
1413
+ "id": "5e723e87",
1414
+ "metadata": {},
1415
+ "outputs": [
1416
+ {
1417
+ "data": {
1418
+ "text/html": [
1419
+ "<div>\n",
1420
+ "<style scoped>\n",
1421
+ " .dataframe tbody tr th:only-of-type {\n",
1422
+ " vertical-align: middle;\n",
1423
+ " }\n",
1424
+ "\n",
1425
+ " .dataframe tbody tr th {\n",
1426
+ " vertical-align: top;\n",
1427
+ " }\n",
1428
+ "\n",
1429
+ " .dataframe thead th {\n",
1430
+ " text-align: right;\n",
1431
+ " }\n",
1432
+ "</style>\n",
1433
+ "<table border=\"1\" class=\"dataframe\">\n",
1434
+ " <thead>\n",
1435
+ " <tr style=\"text-align: right;\">\n",
1436
+ " <th></th>\n",
1437
+ " <th>aircraft</th>\n",
1438
+ " <th>aircraft_max_speed</th>\n",
1439
+ " <th>missile_speed</th>\n",
1440
+ " <th>missile_range</th>\n",
1441
+ " <th>enemy_generation</th>\n",
1442
+ " <th>maneuverability</th>\n",
1443
+ " <th>no_aa_capability</th>\n",
1444
+ " </tr>\n",
1445
+ " </thead>\n",
1446
+ " <tbody>\n",
1447
+ " <tr>\n",
1448
+ " <th>11</th>\n",
1449
+ " <td>F22</td>\n",
1450
+ " <td>669</td>\n",
1451
+ " <td>1372</td>\n",
1452
+ " <td>160000</td>\n",
1453
+ " <td>5.0</td>\n",
1454
+ " <td>2</td>\n",
1455
+ " <td>0</td>\n",
1456
+ " </tr>\n",
1457
+ " </tbody>\n",
1458
+ "</table>\n",
1459
+ "</div>"
1460
+ ],
1461
+ "text/plain": [
1462
+ " aircraft aircraft_max_speed missile_speed missile_range \\\n",
1463
+ "11 F22 669 1372 160000 \n",
1464
+ "\n",
1465
+ " enemy_generation maneuverability no_aa_capability \n",
1466
+ "11 5.0 2 0 "
1467
+ ]
1468
+ },
1469
+ "execution_count": 17,
1470
+ "metadata": {},
1471
+ "output_type": "execute_result"
1472
+ }
1473
+ ],
1474
+ "source": [
1475
+ "combat_df[combat_df[\"aircraft\"]==\"F22\"]"
1476
+ ]
1477
+ },
1478
+ {
1479
+ "cell_type": "code",
1480
+ "execution_count": 18,
1481
+ "id": "41f0712c",
1482
+ "metadata": {},
1483
+ "outputs": [
1484
+ {
1485
+ "data": {
1486
+ "text/html": [
1487
+ "<div>\n",
1488
+ "<style scoped>\n",
1489
+ " .dataframe tbody tr th:only-of-type {\n",
1490
+ " vertical-align: middle;\n",
1491
+ " }\n",
1492
+ "\n",
1493
+ " .dataframe tbody tr th {\n",
1494
+ " vertical-align: top;\n",
1495
+ " }\n",
1496
+ "\n",
1497
+ " .dataframe thead th {\n",
1498
+ " text-align: right;\n",
1499
+ " }\n",
1500
+ "</style>\n",
1501
+ "<table border=\"1\" class=\"dataframe\">\n",
1502
+ " <thead>\n",
1503
+ " <tr style=\"text-align: right;\">\n",
1504
+ " <th></th>\n",
1505
+ " <th>aircraft_max_speed</th>\n",
1506
+ " <th>missile_speed</th>\n",
1507
+ " <th>missile_range</th>\n",
1508
+ " </tr>\n",
1509
+ " </thead>\n",
1510
+ " <tbody>\n",
1511
+ " <tr>\n",
1512
+ " <th>aircraft_max_speed</th>\n",
1513
+ " <td>1.000000</td>\n",
1514
+ " <td>0.753865</td>\n",
1515
+ " <td>0.633663</td>\n",
1516
+ " </tr>\n",
1517
+ " <tr>\n",
1518
+ " <th>missile_speed</th>\n",
1519
+ " <td>0.753865</td>\n",
1520
+ " <td>1.000000</td>\n",
1521
+ " <td>0.820434</td>\n",
1522
+ " </tr>\n",
1523
+ " <tr>\n",
1524
+ " <th>missile_range</th>\n",
1525
+ " <td>0.633663</td>\n",
1526
+ " <td>0.820434</td>\n",
1527
+ " <td>1.000000</td>\n",
1528
+ " </tr>\n",
1529
+ " </tbody>\n",
1530
+ "</table>\n",
1531
+ "</div>"
1532
+ ],
1533
+ "text/plain": [
1534
+ " aircraft_max_speed missile_speed missile_range\n",
1535
+ "aircraft_max_speed 1.000000 0.753865 0.633663\n",
1536
+ "missile_speed 0.753865 1.000000 0.820434\n",
1537
+ "missile_range 0.633663 0.820434 1.000000"
1538
+ ]
1539
+ },
1540
+ "execution_count": 18,
1541
+ "metadata": {},
1542
+ "output_type": "execute_result"
1543
+ }
1544
+ ],
1545
+ "source": [
1546
+ "# correlation between aircraft speed and missile \n",
1547
+ "combat_df[[\"aircraft_max_speed\", \"missile_speed\", \"missile_range\"]].corr()"
1548
+ ]
1549
+ },
1550
+ {
1551
+ "cell_type": "code",
1552
+ "execution_count": 19,
1553
+ "id": "468e508e",
1554
+ "metadata": {},
1555
+ "outputs": [
1556
+ {
1557
+ "data": {
1558
+ "text/plain": [
1559
+ "enemy_generation\n",
1560
+ "3.5 26500.000000\n",
1561
+ "4.0 83240.000000\n",
1562
+ "4.5 159090.909091\n",
1563
+ "5.0 242272.727273\n",
1564
+ "Name: missile_range, dtype: float64"
1565
+ ]
1566
+ },
1567
+ "execution_count": 19,
1568
+ "metadata": {},
1569
+ "output_type": "execute_result"
1570
+ }
1571
+ ],
1572
+ "source": [
1573
+ "combat_df.groupby(\"enemy_generation\")[\"missile_range\"].mean()"
1574
+ ]
1575
+ },
1576
+ {
1577
+ "cell_type": "markdown",
1578
+ "id": "f7850284",
1579
+ "metadata": {},
1580
+ "source": [
1581
+ "## **Extracted Metadata Insights**\n",
1582
+ "\n",
1583
+ "### **Combat-Capable Aircraft**\n",
1584
+ "- Total aircraft: 102\n",
1585
+ "- Combat-capable aircraft: 56\n",
1586
+ "- Non combat-capable aircraft: 46\n",
1587
+ "\n",
1588
+ "---\n",
1589
+ "\n",
1590
+ "### Aircraft Speed Range\n",
1591
+ "\n",
1592
+ "```python\n",
1593
+ "61 m/s → 983 m/s\n",
1594
+ "```\n",
1595
+ "\n",
1596
+ "---\n",
1597
+ "\n",
1598
+ "### Missile Speed Range\n",
1599
+ "\n",
1600
+ "```python \n",
1601
+ "686 m/s → 2058 m/s\n",
1602
+ "````\n",
1603
+ "\n",
1604
+ "---\n",
1605
+ "\n",
1606
+ "### Missile Range\n",
1607
+ "\n",
1608
+ "```python id=\"e2h15d\"\n",
1609
+ "8,000 m → 400,000 m\n",
1610
+ "```\n",
1611
+ "\n",
1612
+ "---\n",
1613
+ "\n",
1614
+ "### Enemy Generation Distribution\n",
1615
+ "\n",
1616
+ "```python id=\"txut2q\"\n",
1617
+ "4.0 → 23 aircraft\n",
1618
+ "5.0 → 13 aircraft\n",
1619
+ "4.5 → 10 aircraft\n",
1620
+ "3.5 → 5 aircraft\n",
1621
+ "```\n",
1622
+ "\n",
1623
+ "---\n",
1624
+ "\n",
1625
+ "### Maneuverability Distribution\n",
1626
+ "\n",
1627
+ "```python id=\"nt11ao\"\n",
1628
+ "2 (high) → 27 aircraft\n",
1629
+ "1 (medium) → 24 aircraft\n",
1630
+ "```\n",
1631
+ "\n",
1632
+ "---\n",
1633
+ "\n",
1634
+ "### Key Observations\n",
1635
+ "\n",
1636
+ "* Most combat aircraft belong to Gen 4 and Gen 5.\n",
1637
+ "* Most combat aircraft have medium or high maneuverability.\n",
1638
+ "* Advanced aircraft generally have longer missile ranges.\n",
1639
+ "* High-end aircraft like Su57, Mig31, J35, J36, and J50 dominate the upper missile range limits.\n",
1640
+ "\n",
1641
+ "The goal of the generator is not perfect aerospace simulation, but generation of believable and internally consistent combat scenarios that allow ML models to learn meaningful relationships.\n",
1642
+ "\n",
1643
+ "---"
1644
+ ]
1645
+ },
1646
+ {
1647
+ "cell_type": "markdown",
1648
+ "id": "8832d356",
1649
+ "metadata": {},
1650
+ "source": [
1651
+ "## **Plan to build physics generator**\n",
1652
+ "\n",
1653
+ "### **How it should be called**\n",
1654
+ "\n",
1655
+ "```python\n",
1656
+ "from src.physics_generator import generate_dataset\n",
1657
+ "generate_dataset()\n",
1658
+ "```\n",
1659
+ "\n",
1660
+ "### **How it will be build**\n",
1661
+ "\n",
1662
+ "```markdown\n",
1663
+ "generate_dataset() ← the one function the notebook calls\n",
1664
+ " └── load_metadata() ← reads aircraft_metadata.csv\n",
1665
+ " └── generate_row() ← builds one scenario\n",
1666
+ " └── derive_missile_phase()\n",
1667
+ " └── derive_closure_rate()\n",
1668
+ " └── derive_evasion_time()\n",
1669
+ " └── derive_hit_label()\n",
1670
+ " └── save_dataset() ← saves to CSV\n",
1671
+ "```\n",
1672
+ "---\n",
1673
+ "\n",
1674
+ "### **The sequence of `physics_generator.py` script**\n",
1675
+ "\n",
1676
+ "```python\n",
1677
+ "_generate_row()\n",
1678
+ "│\n",
1679
+ "├── 1. Pick a random enemy aircraft from metadata\n",
1680
+ "│ (combat-capable only — no_aa_capability = 0)\n",
1681
+ "│\n",
1682
+ "├── 2. Sample raw feature values\n",
1683
+ "│ your_speed, your_altitude, azimuth, elevation,\n",
1684
+ "│ maneuverability, countermeasure_deployed\n",
1685
+ "│ launch_distance (within that aircraft's missile_range)\n",
1686
+ "│ remaining_distance (between 0 and launch_distance)\n",
1687
+ "│\n",
1688
+ "├── 3. _derive_missile_phase()\n",
1689
+ "│ \"How far has the missile already traveled?\"\n",
1690
+ "│ → returns 0, 1, or 2\n",
1691
+ "│\n",
1692
+ "├── 4. _derive_closure_rate()\n",
1693
+ "│ \"How fast is the gap closing in 3D space?\"\n",
1694
+ "│ → returns a speed in m/s\n",
1695
+ "│\n",
1696
+ "├── 5. _derive_evasion_time()\n",
1697
+ "│ \"How many seconds before it reaches you?\"\n",
1698
+ "│ → returns a float in seconds\n",
1699
+ "│\n",
1700
+ "├── 6. _derive_hit_label()\n",
1701
+ "│ \"After your evasion attempt, does it hit?\"\n",
1702
+ "│ → returns 0 or 1\n",
1703
+ "│\n",
1704
+ "└── 7. Package everything into a dict → one row\n",
1705
+ "```\n",
1706
+ "\n",
1707
+ "---"
1708
+ ]
1709
+ },
1710
+ {
1711
+ "cell_type": "markdown",
1712
+ "id": "c3d32245",
1713
+ "metadata": {},
1714
+ "source": [
1715
+ "## **Sanity Check - Physics Generator**\n",
1716
+ "\n",
1717
+ "Quick validation that `generate_dataset()` produces clean, physically sensible output.\n",
1718
+ "Run with `N_ROWS = 1_000` before the full 1M generation."
1719
+ ]
1720
+ },
1721
+ {
1722
+ "cell_type": "code",
1723
+ "execution_count": 26,
1724
+ "id": "37b78e79",
1725
+ "metadata": {},
1726
+ "outputs": [],
1727
+ "source": [
1728
+ "import sys\n",
1729
+ "from pathlib import Path\n",
1730
+ "\n",
1731
+ "# Add src/ to path so we can import physics_generator\n",
1732
+ "sys.path.append(str(Path.cwd().resolve().parent.parent / \"src\"))\n",
1733
+ "\n",
1734
+ "from physics_generator import generate_dataset"
1735
+ ]
1736
+ },
1737
+ {
1738
+ "cell_type": "code",
1739
+ "execution_count": null,
1740
+ "id": "4181ca14",
1741
+ "metadata": {},
1742
+ "outputs": [
1743
+ {
1744
+ "name": "stdout",
1745
+ "output_type": "stream",
1746
+ "text": [
1747
+ "Done. 1,000 rows saved to /mnt/d/Eakem/Learning ML and DS/Project_ATAS/ATAS_Project/data/synthetic_engagements.csv\n"
1748
+ ]
1749
+ }
1750
+ ],
1751
+ "source": [
1752
+ "# # Running the generator for N_ROWS = 1_000\n",
1753
+ "# df = generate_dataset()"
1754
+ ]
1755
+ },
1756
+ {
1757
+ "cell_type": "code",
1758
+ "execution_count": null,
1759
+ "id": "54d3fc2c",
1760
+ "metadata": {},
1761
+ "outputs": [
1762
+ {
1763
+ "name": "stdout",
1764
+ "output_type": "stream",
1765
+ "text": [
1766
+ "Rows: 1000\n",
1767
+ "Columns: 16\n",
1768
+ "Shape: (1000, 16)\n"
1769
+ ]
1770
+ }
1771
+ ],
1772
+ "source": [
1773
+ "# import pandas as pd\n",
1774
+ "# df = pd.read_csv(\"../data/synthetic_engagements_1000.csv\")\n",
1775
+ "# print(f\"Rows: {len(df)}\")\n",
1776
+ "# print(f\"Columns: {len(df.columns)}\")\n",
1777
+ "# print(f\"Shape: {df.shape}\")"
1778
+ ]
1779
+ },
1780
+ {
1781
+ "cell_type": "code",
1782
+ "execution_count": null,
1783
+ "id": "14d1f643",
1784
+ "metadata": {},
1785
+ "outputs": [
1786
+ {
1787
+ "data": {
1788
+ "text/html": [
1789
+ "<div>\n",
1790
+ "<style scoped>\n",
1791
+ " .dataframe tbody tr th:only-of-type {\n",
1792
+ " vertical-align: middle;\n",
1793
+ " }\n",
1794
+ "\n",
1795
+ " .dataframe tbody tr th {\n",
1796
+ " vertical-align: top;\n",
1797
+ " }\n",
1798
+ "\n",
1799
+ " .dataframe thead th {\n",
1800
+ " text-align: right;\n",
1801
+ " }\n",
1802
+ "</style>\n",
1803
+ "<table border=\"1\" class=\"dataframe\">\n",
1804
+ " <thead>\n",
1805
+ " <tr style=\"text-align: right;\">\n",
1806
+ " <th></th>\n",
1807
+ " <th>launch_distance</th>\n",
1808
+ " <th>remaining_distance</th>\n",
1809
+ " <th>closure_rate</th>\n",
1810
+ " <th>azimuth</th>\n",
1811
+ " <th>elevation</th>\n",
1812
+ " <th>missile_phase</th>\n",
1813
+ " <th>your_speed</th>\n",
1814
+ " <th>your_altitude</th>\n",
1815
+ " <th>your_maneuverability</th>\n",
1816
+ " <th>enemy_altitude</th>\n",
1817
+ " <th>missile_speed</th>\n",
1818
+ " <th>missile_range</th>\n",
1819
+ " <th>enemy_generation</th>\n",
1820
+ " <th>countermeasure_deployed</th>\n",
1821
+ " <th>evasion_time</th>\n",
1822
+ " <th>hit</th>\n",
1823
+ " </tr>\n",
1824
+ " </thead>\n",
1825
+ " <tbody>\n",
1826
+ " <tr>\n",
1827
+ " <th>0</th>\n",
1828
+ " <td>22570.893367</td>\n",
1829
+ " <td>2961.980067</td>\n",
1830
+ " <td>1185.763992</td>\n",
1831
+ " <td>61.005549</td>\n",
1832
+ " <td>-34.707563</td>\n",
1833
+ " <td>2</td>\n",
1834
+ " <td>825.050818</td>\n",
1835
+ " <td>6512.661972</td>\n",
1836
+ " <td>1</td>\n",
1837
+ " <td>3655.798699</td>\n",
1838
+ " <td>857</td>\n",
1839
+ " <td>35000</td>\n",
1840
+ " <td>4.0</td>\n",
1841
+ " <td>1</td>\n",
1842
+ " <td>2.229421</td>\n",
1843
+ " <td>0</td>\n",
1844
+ " </tr>\n",
1845
+ " <tr>\n",
1846
+ " <th>1</th>\n",
1847
+ " <td>2367.007196</td>\n",
1848
+ " <td>1652.662358</td>\n",
1849
+ " <td>625.878279</td>\n",
1850
+ " <td>212.466382</td>\n",
1851
+ " <td>68.630275</td>\n",
1852
+ " <td>0</td>\n",
1853
+ " <td>751.777144</td>\n",
1854
+ " <td>18782.156933</td>\n",
1855
+ " <td>1</td>\n",
1856
+ " <td>19508.648175</td>\n",
1857
+ " <td>857</td>\n",
1858
+ " <td>35000</td>\n",
1859
+ " <td>4.0</td>\n",
1860
+ " <td>0</td>\n",
1861
+ " <td>2.772577</td>\n",
1862
+ " <td>1</td>\n",
1863
+ " </tr>\n",
1864
+ " <tr>\n",
1865
+ " <th>2</th>\n",
1866
+ " <td>6602.346475</td>\n",
1867
+ " <td>2558.752556</td>\n",
1868
+ " <td>871.179381</td>\n",
1869
+ " <td>281.112640</td>\n",
1870
+ " <td>45.266055</td>\n",
1871
+ " <td>1</td>\n",
1872
+ " <td>104.527357</td>\n",
1873
+ " <td>3625.183143</td>\n",
1874
+ " <td>2</td>\n",
1875
+ " <td>29894.396018</td>\n",
1876
+ " <td>857</td>\n",
1877
+ " <td>35000</td>\n",
1878
+ " <td>4.0</td>\n",
1879
+ " <td>0</td>\n",
1880
+ " <td>3.230825</td>\n",
1881
+ " <td>0</td>\n",
1882
+ " </tr>\n",
1883
+ " <tr>\n",
1884
+ " <th>3</th>\n",
1885
+ " <td>18281.792405</td>\n",
1886
+ " <td>18049.709650</td>\n",
1887
+ " <td>648.636953</td>\n",
1888
+ " <td>115.135435</td>\n",
1889
+ " <td>-37.372887</td>\n",
1890
+ " <td>0</td>\n",
1891
+ " <td>617.267514</td>\n",
1892
+ " <td>5759.552561</td>\n",
1893
+ " <td>1</td>\n",
1894
+ " <td>15002.849014</td>\n",
1895
+ " <td>857</td>\n",
1896
+ " <td>35000</td>\n",
1897
+ " <td>4.0</td>\n",
1898
+ " <td>1</td>\n",
1899
+ " <td>32.140344</td>\n",
1900
+ " <td>0</td>\n",
1901
+ " </tr>\n",
1902
+ " <tr>\n",
1903
+ " <th>4</th>\n",
1904
+ " <td>9834.379665</td>\n",
1905
+ " <td>1522.983460</td>\n",
1906
+ " <td>1088.038782</td>\n",
1907
+ " <td>316.104680</td>\n",
1908
+ " <td>-45.805628</td>\n",
1909
+ " <td>2</td>\n",
1910
+ " <td>459.932597</td>\n",
1911
+ " <td>28821.640152</td>\n",
1912
+ " <td>2</td>\n",
1913
+ " <td>4643.721244</td>\n",
1914
+ " <td>857</td>\n",
1915
+ " <td>35000</td>\n",
1916
+ " <td>4.0</td>\n",
1917
+ " <td>1</td>\n",
1918
+ " <td>1.308767</td>\n",
1919
+ " <td>0</td>\n",
1920
+ " </tr>\n",
1921
+ " </tbody>\n",
1922
+ "</table>\n",
1923
+ "</div>"
1924
+ ],
1925
+ "text/plain": [
1926
+ " launch_distance remaining_distance closure_rate azimuth elevation \\\n",
1927
+ "0 22570.893367 2961.980067 1185.763992 61.005549 -34.707563 \n",
1928
+ "1 2367.007196 1652.662358 625.878279 212.466382 68.630275 \n",
1929
+ "2 6602.346475 2558.752556 871.179381 281.112640 45.266055 \n",
1930
+ "3 18281.792405 18049.709650 648.636953 115.135435 -37.372887 \n",
1931
+ "4 9834.379665 1522.983460 1088.038782 316.104680 -45.805628 \n",
1932
+ "\n",
1933
+ " missile_phase your_speed your_altitude your_maneuverability \\\n",
1934
+ "0 2 825.050818 6512.661972 1 \n",
1935
+ "1 0 751.777144 18782.156933 1 \n",
1936
+ "2 1 104.527357 3625.183143 2 \n",
1937
+ "3 0 617.267514 5759.552561 1 \n",
1938
+ "4 2 459.932597 28821.640152 2 \n",
1939
+ "\n",
1940
+ " enemy_altitude missile_speed missile_range enemy_generation \\\n",
1941
+ "0 3655.798699 857 35000 4.0 \n",
1942
+ "1 19508.648175 857 35000 4.0 \n",
1943
+ "2 29894.396018 857 35000 4.0 \n",
1944
+ "3 15002.849014 857 35000 4.0 \n",
1945
+ "4 4643.721244 857 35000 4.0 \n",
1946
+ "\n",
1947
+ " countermeasure_deployed evasion_time hit \n",
1948
+ "0 1 2.229421 0 \n",
1949
+ "1 0 2.772577 1 \n",
1950
+ "2 0 3.230825 0 \n",
1951
+ "3 1 32.140344 0 \n",
1952
+ "4 1 1.308767 0 "
1953
+ ]
1954
+ },
1955
+ "execution_count": 29,
1956
+ "metadata": {},
1957
+ "output_type": "execute_result"
1958
+ }
1959
+ ],
1960
+ "source": [
1961
+ "# df.head()"
1962
+ ]
1963
+ },
1964
+ {
1965
+ "cell_type": "code",
1966
+ "execution_count": null,
1967
+ "id": "19f98851",
1968
+ "metadata": {},
1969
+ "outputs": [
1970
+ {
1971
+ "name": "stdout",
1972
+ "output_type": "stream",
1973
+ "text": [
1974
+ "Negative closure_rate: 0\n",
1975
+ "Negative evasion_rate: 0\n"
1976
+ ]
1977
+ }
1978
+ ],
1979
+ "source": [
1980
+ "# # CHecking for negatives values\n",
1981
+ "# print(f\"Negative closure_rate: {(df['closure_rate'] < 0).sum()}\")\n",
1982
+ "# print(f\"Negative evasion_rate: {(df['evasion_time'] < 0).sum()}\")"
1983
+ ]
1984
+ },
1985
+ {
1986
+ "cell_type": "code",
1987
+ "execution_count": null,
1988
+ "id": "3b3d531f",
1989
+ "metadata": {},
1990
+ "outputs": [
1991
+ {
1992
+ "data": {
1993
+ "text/plain": [
1994
+ "missile_speed\n",
1995
+ "1372 473\n",
1996
+ "857 154\n",
1997
+ "686 100\n",
1998
+ "1715 97\n",
1999
+ "2058 58\n",
2000
+ "847 40\n",
2001
+ "1544 39\n",
2002
+ "750 20\n",
2003
+ "1475 19\n",
2004
+ "Name: count, dtype: int64"
2005
+ ]
2006
+ },
2007
+ "execution_count": 32,
2008
+ "metadata": {},
2009
+ "output_type": "execute_result"
2010
+ }
2011
+ ],
2012
+ "source": [
2013
+ "# df.missile_speed.value_counts() # From metadata csv"
2014
+ ]
2015
+ },
2016
+ {
2017
+ "cell_type": "code",
2018
+ "execution_count": null,
2019
+ "id": "e7bd832a",
2020
+ "metadata": {},
2021
+ "outputs": [
2022
+ {
2023
+ "data": {
2024
+ "text/html": [
2025
+ "<div>\n",
2026
+ "<style scoped>\n",
2027
+ " .dataframe tbody tr th:only-of-type {\n",
2028
+ " vertical-align: middle;\n",
2029
+ " }\n",
2030
+ "\n",
2031
+ " .dataframe tbody tr th {\n",
2032
+ " vertical-align: top;\n",
2033
+ " }\n",
2034
+ "\n",
2035
+ " .dataframe thead th {\n",
2036
+ " text-align: right;\n",
2037
+ " }\n",
2038
+ "</style>\n",
2039
+ "<table border=\"1\" class=\"dataframe\">\n",
2040
+ " <thead>\n",
2041
+ " <tr style=\"text-align: right;\">\n",
2042
+ " <th></th>\n",
2043
+ " <th>0</th>\n",
2044
+ " <th>1</th>\n",
2045
+ " <th>2</th>\n",
2046
+ " <th>3</th>\n",
2047
+ " <th>4</th>\n",
2048
+ " <th>5</th>\n",
2049
+ " <th>6</th>\n",
2050
+ " <th>7</th>\n",
2051
+ " <th>8</th>\n",
2052
+ " <th>9</th>\n",
2053
+ " <th>...</th>\n",
2054
+ " <th>990</th>\n",
2055
+ " <th>991</th>\n",
2056
+ " <th>992</th>\n",
2057
+ " <th>993</th>\n",
2058
+ " <th>994</th>\n",
2059
+ " <th>995</th>\n",
2060
+ " <th>996</th>\n",
2061
+ " <th>997</th>\n",
2062
+ " <th>998</th>\n",
2063
+ " <th>999</th>\n",
2064
+ " </tr>\n",
2065
+ " </thead>\n",
2066
+ " <tbody>\n",
2067
+ " <tr>\n",
2068
+ " <th>launch_distance</th>\n",
2069
+ " <td>22570.893367</td>\n",
2070
+ " <td>2367.007196</td>\n",
2071
+ " <td>6602.346475</td>\n",
2072
+ " <td>18281.792405</td>\n",
2073
+ " <td>9834.379665</td>\n",
2074
+ " <td>12661.488033</td>\n",
2075
+ " <td>25901.724724</td>\n",
2076
+ " <td>8890.584630</td>\n",
2077
+ " <td>983.734396</td>\n",
2078
+ " <td>13859.562006</td>\n",
2079
+ " <td>...</td>\n",
2080
+ " <td>4981.478801</td>\n",
2081
+ " <td>32056.695360</td>\n",
2082
+ " <td>53419.689040</td>\n",
2083
+ " <td>19021.028127</td>\n",
2084
+ " <td>128594.277243</td>\n",
2085
+ " <td>64845.033514</td>\n",
2086
+ " <td>83532.658041</td>\n",
2087
+ " <td>146555.411805</td>\n",
2088
+ " <td>77971.657118</td>\n",
2089
+ " <td>2253.861997</td>\n",
2090
+ " </tr>\n",
2091
+ " <tr>\n",
2092
+ " <th>remaining_distance</th>\n",
2093
+ " <td>2961.980067</td>\n",
2094
+ " <td>1652.662358</td>\n",
2095
+ " <td>2558.752556</td>\n",
2096
+ " <td>18049.709650</td>\n",
2097
+ " <td>1522.983460</td>\n",
2098
+ " <td>3400.938832</td>\n",
2099
+ " <td>17955.494364</td>\n",
2100
+ " <td>7886.781225</td>\n",
2101
+ " <td>483.793860</td>\n",
2102
+ " <td>13391.098584</td>\n",
2103
+ " <td>...</td>\n",
2104
+ " <td>1605.088087</td>\n",
2105
+ " <td>4117.674334</td>\n",
2106
+ " <td>26058.452754</td>\n",
2107
+ " <td>3767.541060</td>\n",
2108
+ " <td>40310.301403</td>\n",
2109
+ " <td>60646.537540</td>\n",
2110
+ " <td>13843.586940</td>\n",
2111
+ " <td>94701.450863</td>\n",
2112
+ " <td>17925.445277</td>\n",
2113
+ " <td>1103.757518</td>\n",
2114
+ " </tr>\n",
2115
+ " <tr>\n",
2116
+ " <th>closure_rate</th>\n",
2117
+ " <td>1185.763992</td>\n",
2118
+ " <td>625.878279</td>\n",
2119
+ " <td>871.179381</td>\n",
2120
+ " <td>648.636953</td>\n",
2121
+ " <td>1088.038782</td>\n",
2122
+ " <td>725.990269</td>\n",
2123
+ " <td>844.960185</td>\n",
2124
+ " <td>1020.875119</td>\n",
2125
+ " <td>1066.347972</td>\n",
2126
+ " <td>386.272373</td>\n",
2127
+ " <td>...</td>\n",
2128
+ " <td>590.826598</td>\n",
2129
+ " <td>1312.773778</td>\n",
2130
+ " <td>1663.712597</td>\n",
2131
+ " <td>438.758129</td>\n",
2132
+ " <td>1394.591033</td>\n",
2133
+ " <td>1186.846963</td>\n",
2134
+ " <td>1211.445463</td>\n",
2135
+ " <td>1803.707956</td>\n",
2136
+ " <td>1636.533985</td>\n",
2137
+ " <td>405.627102</td>\n",
2138
+ " </tr>\n",
2139
+ " <tr>\n",
2140
+ " <th>azimuth</th>\n",
2141
+ " <td>61.005549</td>\n",
2142
+ " <td>212.466382</td>\n",
2143
+ " <td>281.112640</td>\n",
2144
+ " <td>115.135435</td>\n",
2145
+ " <td>316.104680</td>\n",
2146
+ " <td>259.107852</td>\n",
2147
+ " <td>211.731931</td>\n",
2148
+ " <td>283.740414</td>\n",
2149
+ " <td>349.132944</td>\n",
2150
+ " <td>176.945221</td>\n",
2151
+ " <td>...</td>\n",
2152
+ " <td>240.738983</td>\n",
2153
+ " <td>191.345225</td>\n",
2154
+ " <td>72.020487</td>\n",
2155
+ " <td>206.765055</td>\n",
2156
+ " <td>69.911001</td>\n",
2157
+ " <td>251.992973</td>\n",
2158
+ " <td>243.510094</td>\n",
2159
+ " <td>41.013277</td>\n",
2160
+ " <td>305.568549</td>\n",
2161
+ " <td>117.061738</td>\n",
2162
+ " </tr>\n",
2163
+ " <tr>\n",
2164
+ " <th>elevation</th>\n",
2165
+ " <td>-34.707563</td>\n",
2166
+ " <td>68.630275</td>\n",
2167
+ " <td>45.266055</td>\n",
2168
+ " <td>-37.372887</td>\n",
2169
+ " <td>-45.805628</td>\n",
2170
+ " <td>-2.007360</td>\n",
2171
+ " <td>-83.509892</td>\n",
2172
+ " <td>-13.091010</td>\n",
2173
+ " <td>-43.024865</td>\n",
2174
+ " <td>-55.574971</td>\n",
2175
+ " <td>...</td>\n",
2176
+ " <td>73.492841</td>\n",
2177
+ " <td>-85.265763</td>\n",
2178
+ " <td>-30.731328</td>\n",
2179
+ " <td>27.430184</td>\n",
2180
+ " <td>-76.994103</td>\n",
2181
+ " <td>-14.818047</td>\n",
2182
+ " <td>-43.173902</td>\n",
2183
+ " <td>-54.366817</td>\n",
2184
+ " <td>-32.225887</td>\n",
2185
+ " <td>-20.136454</td>\n",
2186
+ " </tr>\n",
2187
+ " <tr>\n",
2188
+ " <th>missile_phase</th>\n",
2189
+ " <td>2.000000</td>\n",
2190
+ " <td>0.000000</td>\n",
2191
+ " <td>1.000000</td>\n",
2192
+ " <td>0.000000</td>\n",
2193
+ " <td>2.000000</td>\n",
2194
+ " <td>2.000000</td>\n",
2195
+ " <td>0.000000</td>\n",
2196
+ " <td>0.000000</td>\n",
2197
+ " <td>1.000000</td>\n",
2198
+ " <td>0.000000</td>\n",
2199
+ " <td>...</td>\n",
2200
+ " <td>2.000000</td>\n",
2201
+ " <td>2.000000</td>\n",
2202
+ " <td>1.000000</td>\n",
2203
+ " <td>2.000000</td>\n",
2204
+ " <td>2.000000</td>\n",
2205
+ " <td>0.000000</td>\n",
2206
+ " <td>2.000000</td>\n",
2207
+ " <td>1.000000</td>\n",
2208
+ " <td>2.000000</td>\n",
2209
+ " <td>1.000000</td>\n",
2210
+ " </tr>\n",
2211
+ " <tr>\n",
2212
+ " <th>your_speed</th>\n",
2213
+ " <td>825.050818</td>\n",
2214
+ " <td>751.777144</td>\n",
2215
+ " <td>104.527357</td>\n",
2216
+ " <td>617.267514</td>\n",
2217
+ " <td>459.932597</td>\n",
2218
+ " <td>693.742453</td>\n",
2219
+ " <td>125.237980</td>\n",
2220
+ " <td>708.341294</td>\n",
2221
+ " <td>291.592189</td>\n",
2222
+ " <td>833.848608</td>\n",
2223
+ " <td>...</td>\n",
2224
+ " <td>685.281835</td>\n",
2225
+ " <td>731.899168</td>\n",
2226
+ " <td>451.183218</td>\n",
2227
+ " <td>527.764085</td>\n",
2228
+ " <td>292.249519</td>\n",
2229
+ " <td>619.546047</td>\n",
2230
+ " <td>493.576101</td>\n",
2231
+ " <td>982.046031</td>\n",
2232
+ " <td>537.593520</td>\n",
2233
+ " <td>656.393231</td>\n",
2234
+ " </tr>\n",
2235
+ " <tr>\n",
2236
+ " <th>your_altitude</th>\n",
2237
+ " <td>6512.661972</td>\n",
2238
+ " <td>18782.156933</td>\n",
2239
+ " <td>3625.183143</td>\n",
2240
+ " <td>5759.552561</td>\n",
2241
+ " <td>28821.640152</td>\n",
2242
+ " <td>9790.692112</td>\n",
2243
+ " <td>1510.779084</td>\n",
2244
+ " <td>14553.522284</td>\n",
2245
+ " <td>4020.288191</td>\n",
2246
+ " <td>15714.406817</td>\n",
2247
+ " <td>...</td>\n",
2248
+ " <td>4796.945631</td>\n",
2249
+ " <td>4000.690960</td>\n",
2250
+ " <td>7211.059032</td>\n",
2251
+ " <td>2657.273049</td>\n",
2252
+ " <td>16145.505669</td>\n",
2253
+ " <td>23094.841893</td>\n",
2254
+ " <td>24159.528537</td>\n",
2255
+ " <td>26605.676886</td>\n",
2256
+ " <td>22146.398479</td>\n",
2257
+ " <td>1624.780195</td>\n",
2258
+ " </tr>\n",
2259
+ " <tr>\n",
2260
+ " <th>your_maneuverability</th>\n",
2261
+ " <td>1.000000</td>\n",
2262
+ " <td>1.000000</td>\n",
2263
+ " <td>2.000000</td>\n",
2264
+ " <td>1.000000</td>\n",
2265
+ " <td>2.000000</td>\n",
2266
+ " <td>1.000000</td>\n",
2267
+ " <td>1.000000</td>\n",
2268
+ " <td>1.000000</td>\n",
2269
+ " <td>1.000000</td>\n",
2270
+ " <td>0.000000</td>\n",
2271
+ " <td>...</td>\n",
2272
+ " <td>0.000000</td>\n",
2273
+ " <td>2.000000</td>\n",
2274
+ " <td>1.000000</td>\n",
2275
+ " <td>0.000000</td>\n",
2276
+ " <td>0.000000</td>\n",
2277
+ " <td>1.000000</td>\n",
2278
+ " <td>2.000000</td>\n",
2279
+ " <td>1.000000</td>\n",
2280
+ " <td>1.000000</td>\n",
2281
+ " <td>0.000000</td>\n",
2282
+ " </tr>\n",
2283
+ " <tr>\n",
2284
+ " <th>enemy_altitude</th>\n",
2285
+ " <td>3655.798699</td>\n",
2286
+ " <td>19508.648175</td>\n",
2287
+ " <td>29894.396018</td>\n",
2288
+ " <td>15002.849014</td>\n",
2289
+ " <td>4643.721244</td>\n",
2290
+ " <td>26557.889641</td>\n",
2291
+ " <td>11515.140368</td>\n",
2292
+ " <td>4119.563278</td>\n",
2293
+ " <td>2974.936095</td>\n",
2294
+ " <td>28617.057195</td>\n",
2295
+ " <td>...</td>\n",
2296
+ " <td>13657.745440</td>\n",
2297
+ " <td>24585.421109</td>\n",
2298
+ " <td>28243.550517</td>\n",
2299
+ " <td>16566.700223</td>\n",
2300
+ " <td>15476.262795</td>\n",
2301
+ " <td>26693.600073</td>\n",
2302
+ " <td>17534.961728</td>\n",
2303
+ " <td>7711.296412</td>\n",
2304
+ " <td>4263.977523</td>\n",
2305
+ " <td>17091.003655</td>\n",
2306
+ " </tr>\n",
2307
+ " <tr>\n",
2308
+ " <th>missile_speed</th>\n",
2309
+ " <td>857.000000</td>\n",
2310
+ " <td>857.000000</td>\n",
2311
+ " <td>857.000000</td>\n",
2312
+ " <td>857.000000</td>\n",
2313
+ " <td>857.000000</td>\n",
2314
+ " <td>857.000000</td>\n",
2315
+ " <td>857.000000</td>\n",
2316
+ " <td>857.000000</td>\n",
2317
+ " <td>857.000000</td>\n",
2318
+ " <td>857.000000</td>\n",
2319
+ " <td>...</td>\n",
2320
+ " <td>686.000000</td>\n",
2321
+ " <td>1372.000000</td>\n",
2322
+ " <td>1544.000000</td>\n",
2323
+ " <td>857.000000</td>\n",
2324
+ " <td>1372.000000</td>\n",
2325
+ " <td>1372.000000</td>\n",
2326
+ " <td>1372.000000</td>\n",
2327
+ " <td>1372.000000</td>\n",
2328
+ " <td>1372.000000</td>\n",
2329
+ " <td>686.000000</td>\n",
2330
+ " </tr>\n",
2331
+ " <tr>\n",
2332
+ " <th>missile_range</th>\n",
2333
+ " <td>35000.000000</td>\n",
2334
+ " <td>35000.000000</td>\n",
2335
+ " <td>35000.000000</td>\n",
2336
+ " <td>35000.000000</td>\n",
2337
+ " <td>35000.000000</td>\n",
2338
+ " <td>35000.000000</td>\n",
2339
+ " <td>35000.000000</td>\n",
2340
+ " <td>35000.000000</td>\n",
2341
+ " <td>35000.000000</td>\n",
2342
+ " <td>35000.000000</td>\n",
2343
+ " <td>...</td>\n",
2344
+ " <td>8000.000000</td>\n",
2345
+ " <td>160000.000000</td>\n",
2346
+ " <td>105000.000000</td>\n",
2347
+ " <td>40000.000000</td>\n",
2348
+ " <td>160000.000000</td>\n",
2349
+ " <td>65000.000000</td>\n",
2350
+ " <td>160000.000000</td>\n",
2351
+ " <td>200000.000000</td>\n",
2352
+ " <td>110000.000000</td>\n",
2353
+ " <td>8000.000000</td>\n",
2354
+ " </tr>\n",
2355
+ " <tr>\n",
2356
+ " <th>enemy_generation</th>\n",
2357
+ " <td>4.000000</td>\n",
2358
+ " <td>4.000000</td>\n",
2359
+ " <td>4.000000</td>\n",
2360
+ " <td>4.000000</td>\n",
2361
+ " <td>4.000000</td>\n",
2362
+ " <td>4.000000</td>\n",
2363
+ " <td>4.000000</td>\n",
2364
+ " <td>4.000000</td>\n",
2365
+ " <td>4.000000</td>\n",
2366
+ " <td>4.000000</td>\n",
2367
+ " <td>...</td>\n",
2368
+ " <td>4.000000</td>\n",
2369
+ " <td>5.000000</td>\n",
2370
+ " <td>4.500000</td>\n",
2371
+ " <td>4.000000</td>\n",
2372
+ " <td>4.000000</td>\n",
2373
+ " <td>4.500000</td>\n",
2374
+ " <td>4.500000</td>\n",
2375
+ " <td>4.500000</td>\n",
2376
+ " <td>4.000000</td>\n",
2377
+ " <td>4.000000</td>\n",
2378
+ " </tr>\n",
2379
+ " <tr>\n",
2380
+ " <th>countermeasure_deployed</th>\n",
2381
+ " <td>1.000000</td>\n",
2382
+ " <td>0.000000</td>\n",
2383
+ " <td>0.000000</td>\n",
2384
+ " <td>1.000000</td>\n",
2385
+ " <td>1.000000</td>\n",
2386
+ " <td>0.000000</td>\n",
2387
+ " <td>0.000000</td>\n",
2388
+ " <td>1.000000</td>\n",
2389
+ " <td>0.000000</td>\n",
2390
+ " <td>1.000000</td>\n",
2391
+ " <td>...</td>\n",
2392
+ " <td>1.000000</td>\n",
2393
+ " <td>0.000000</td>\n",
2394
+ " <td>0.000000</td>\n",
2395
+ " <td>1.000000</td>\n",
2396
+ " <td>0.000000</td>\n",
2397
+ " <td>1.000000</td>\n",
2398
+ " <td>0.000000</td>\n",
2399
+ " <td>0.000000</td>\n",
2400
+ " <td>0.000000</td>\n",
2401
+ " <td>1.000000</td>\n",
2402
+ " </tr>\n",
2403
+ " <tr>\n",
2404
+ " <th>evasion_time</th>\n",
2405
+ " <td>2.229421</td>\n",
2406
+ " <td>2.772577</td>\n",
2407
+ " <td>3.230825</td>\n",
2408
+ " <td>32.140344</td>\n",
2409
+ " <td>1.308767</td>\n",
2410
+ " <td>4.599058</td>\n",
2411
+ " <td>23.375118</td>\n",
2412
+ " <td>8.922964</td>\n",
2413
+ " <td>0.453692</td>\n",
2414
+ " <td>40.040966</td>\n",
2415
+ " <td>...</td>\n",
2416
+ " <td>2.667103</td>\n",
2417
+ " <td>2.771440</td>\n",
2418
+ " <td>17.229116</td>\n",
2419
+ " <td>8.430119</td>\n",
2420
+ " <td>24.569035</td>\n",
2421
+ " <td>53.653813</td>\n",
2422
+ " <td>10.684553</td>\n",
2423
+ " <td>60.641844</td>\n",
2424
+ " <td>10.753401</td>\n",
2425
+ " <td>3.142886</td>\n",
2426
+ " </tr>\n",
2427
+ " <tr>\n",
2428
+ " <th>hit</th>\n",
2429
+ " <td>0.000000</td>\n",
2430
+ " <td>1.000000</td>\n",
2431
+ " <td>0.000000</td>\n",
2432
+ " <td>0.000000</td>\n",
2433
+ " <td>0.000000</td>\n",
2434
+ " <td>1.000000</td>\n",
2435
+ " <td>1.000000</td>\n",
2436
+ " <td>0.000000</td>\n",
2437
+ " <td>1.000000</td>\n",
2438
+ " <td>0.000000</td>\n",
2439
+ " <td>...</td>\n",
2440
+ " <td>0.000000</td>\n",
2441
+ " <td>1.000000</td>\n",
2442
+ " <td>0.000000</td>\n",
2443
+ " <td>0.000000</td>\n",
2444
+ " <td>1.000000</td>\n",
2445
+ " <td>0.000000</td>\n",
2446
+ " <td>0.000000</td>\n",
2447
+ " <td>1.000000</td>\n",
2448
+ " <td>1.000000</td>\n",
2449
+ " <td>0.000000</td>\n",
2450
+ " </tr>\n",
2451
+ " </tbody>\n",
2452
+ "</table>\n",
2453
+ "<p>16 rows × 1000 columns</p>\n",
2454
+ "</div>"
2455
+ ],
2456
+ "text/plain": [
2457
+ " 0 1 2 \\\n",
2458
+ "launch_distance 22570.893367 2367.007196 6602.346475 \n",
2459
+ "remaining_distance 2961.980067 1652.662358 2558.752556 \n",
2460
+ "closure_rate 1185.763992 625.878279 871.179381 \n",
2461
+ "azimuth 61.005549 212.466382 281.112640 \n",
2462
+ "elevation -34.707563 68.630275 45.266055 \n",
2463
+ "missile_phase 2.000000 0.000000 1.000000 \n",
2464
+ "your_speed 825.050818 751.777144 104.527357 \n",
2465
+ "your_altitude 6512.661972 18782.156933 3625.183143 \n",
2466
+ "your_maneuverability 1.000000 1.000000 2.000000 \n",
2467
+ "enemy_altitude 3655.798699 19508.648175 29894.396018 \n",
2468
+ "missile_speed 857.000000 857.000000 857.000000 \n",
2469
+ "missile_range 35000.000000 35000.000000 35000.000000 \n",
2470
+ "enemy_generation 4.000000 4.000000 4.000000 \n",
2471
+ "countermeasure_deployed 1.000000 0.000000 0.000000 \n",
2472
+ "evasion_time 2.229421 2.772577 3.230825 \n",
2473
+ "hit 0.000000 1.000000 0.000000 \n",
2474
+ "\n",
2475
+ " 3 4 5 \\\n",
2476
+ "launch_distance 18281.792405 9834.379665 12661.488033 \n",
2477
+ "remaining_distance 18049.709650 1522.983460 3400.938832 \n",
2478
+ "closure_rate 648.636953 1088.038782 725.990269 \n",
2479
+ "azimuth 115.135435 316.104680 259.107852 \n",
2480
+ "elevation -37.372887 -45.805628 -2.007360 \n",
2481
+ "missile_phase 0.000000 2.000000 2.000000 \n",
2482
+ "your_speed 617.267514 459.932597 693.742453 \n",
2483
+ "your_altitude 5759.552561 28821.640152 9790.692112 \n",
2484
+ "your_maneuverability 1.000000 2.000000 1.000000 \n",
2485
+ "enemy_altitude 15002.849014 4643.721244 26557.889641 \n",
2486
+ "missile_speed 857.000000 857.000000 857.000000 \n",
2487
+ "missile_range 35000.000000 35000.000000 35000.000000 \n",
2488
+ "enemy_generation 4.000000 4.000000 4.000000 \n",
2489
+ "countermeasure_deployed 1.000000 1.000000 0.000000 \n",
2490
+ "evasion_time 32.140344 1.308767 4.599058 \n",
2491
+ "hit 0.000000 0.000000 1.000000 \n",
2492
+ "\n",
2493
+ " 6 7 8 \\\n",
2494
+ "launch_distance 25901.724724 8890.584630 983.734396 \n",
2495
+ "remaining_distance 17955.494364 7886.781225 483.793860 \n",
2496
+ "closure_rate 844.960185 1020.875119 1066.347972 \n",
2497
+ "azimuth 211.731931 283.740414 349.132944 \n",
2498
+ "elevation -83.509892 -13.091010 -43.024865 \n",
2499
+ "missile_phase 0.000000 0.000000 1.000000 \n",
2500
+ "your_speed 125.237980 708.341294 291.592189 \n",
2501
+ "your_altitude 1510.779084 14553.522284 4020.288191 \n",
2502
+ "your_maneuverability 1.000000 1.000000 1.000000 \n",
2503
+ "enemy_altitude 11515.140368 4119.563278 2974.936095 \n",
2504
+ "missile_speed 857.000000 857.000000 857.000000 \n",
2505
+ "missile_range 35000.000000 35000.000000 35000.000000 \n",
2506
+ "enemy_generation 4.000000 4.000000 4.000000 \n",
2507
+ "countermeasure_deployed 0.000000 1.000000 0.000000 \n",
2508
+ "evasion_time 23.375118 8.922964 0.453692 \n",
2509
+ "hit 1.000000 0.000000 1.000000 \n",
2510
+ "\n",
2511
+ " 9 ... 990 991 \\\n",
2512
+ "launch_distance 13859.562006 ... 4981.478801 32056.695360 \n",
2513
+ "remaining_distance 13391.098584 ... 1605.088087 4117.674334 \n",
2514
+ "closure_rate 386.272373 ... 590.826598 1312.773778 \n",
2515
+ "azimuth 176.945221 ... 240.738983 191.345225 \n",
2516
+ "elevation -55.574971 ... 73.492841 -85.265763 \n",
2517
+ "missile_phase 0.000000 ... 2.000000 2.000000 \n",
2518
+ "your_speed 833.848608 ... 685.281835 731.899168 \n",
2519
+ "your_altitude 15714.406817 ... 4796.945631 4000.690960 \n",
2520
+ "your_maneuverability 0.000000 ... 0.000000 2.000000 \n",
2521
+ "enemy_altitude 28617.057195 ... 13657.745440 24585.421109 \n",
2522
+ "missile_speed 857.000000 ... 686.000000 1372.000000 \n",
2523
+ "missile_range 35000.000000 ... 8000.000000 160000.000000 \n",
2524
+ "enemy_generation 4.000000 ... 4.000000 5.000000 \n",
2525
+ "countermeasure_deployed 1.000000 ... 1.000000 0.000000 \n",
2526
+ "evasion_time 40.040966 ... 2.667103 2.771440 \n",
2527
+ "hit 0.000000 ... 0.000000 1.000000 \n",
2528
+ "\n",
2529
+ " 992 993 994 \\\n",
2530
+ "launch_distance 53419.689040 19021.028127 128594.277243 \n",
2531
+ "remaining_distance 26058.452754 3767.541060 40310.301403 \n",
2532
+ "closure_rate 1663.712597 438.758129 1394.591033 \n",
2533
+ "azimuth 72.020487 206.765055 69.911001 \n",
2534
+ "elevation -30.731328 27.430184 -76.994103 \n",
2535
+ "missile_phase 1.000000 2.000000 2.000000 \n",
2536
+ "your_speed 451.183218 527.764085 292.249519 \n",
2537
+ "your_altitude 7211.059032 2657.273049 16145.505669 \n",
2538
+ "your_maneuverability 1.000000 0.000000 0.000000 \n",
2539
+ "enemy_altitude 28243.550517 16566.700223 15476.262795 \n",
2540
+ "missile_speed 1544.000000 857.000000 1372.000000 \n",
2541
+ "missile_range 105000.000000 40000.000000 160000.000000 \n",
2542
+ "enemy_generation 4.500000 4.000000 4.000000 \n",
2543
+ "countermeasure_deployed 0.000000 1.000000 0.000000 \n",
2544
+ "evasion_time 17.229116 8.430119 24.569035 \n",
2545
+ "hit 0.000000 0.000000 1.000000 \n",
2546
+ "\n",
2547
+ " 995 996 997 \\\n",
2548
+ "launch_distance 64845.033514 83532.658041 146555.411805 \n",
2549
+ "remaining_distance 60646.537540 13843.586940 94701.450863 \n",
2550
+ "closure_rate 1186.846963 1211.445463 1803.707956 \n",
2551
+ "azimuth 251.992973 243.510094 41.013277 \n",
2552
+ "elevation -14.818047 -43.173902 -54.366817 \n",
2553
+ "missile_phase 0.000000 2.000000 1.000000 \n",
2554
+ "your_speed 619.546047 493.576101 982.046031 \n",
2555
+ "your_altitude 23094.841893 24159.528537 26605.676886 \n",
2556
+ "your_maneuverability 1.000000 2.000000 1.000000 \n",
2557
+ "enemy_altitude 26693.600073 17534.961728 7711.296412 \n",
2558
+ "missile_speed 1372.000000 1372.000000 1372.000000 \n",
2559
+ "missile_range 65000.000000 160000.000000 200000.000000 \n",
2560
+ "enemy_generation 4.500000 4.500000 4.500000 \n",
2561
+ "countermeasure_deployed 1.000000 0.000000 0.000000 \n",
2562
+ "evasion_time 53.653813 10.684553 60.641844 \n",
2563
+ "hit 0.000000 0.000000 1.000000 \n",
2564
+ "\n",
2565
+ " 998 999 \n",
2566
+ "launch_distance 77971.657118 2253.861997 \n",
2567
+ "remaining_distance 17925.445277 1103.757518 \n",
2568
+ "closure_rate 1636.533985 405.627102 \n",
2569
+ "azimuth 305.568549 117.061738 \n",
2570
+ "elevation -32.225887 -20.136454 \n",
2571
+ "missile_phase 2.000000 1.000000 \n",
2572
+ "your_speed 537.593520 656.393231 \n",
2573
+ "your_altitude 22146.398479 1624.780195 \n",
2574
+ "your_maneuverability 1.000000 0.000000 \n",
2575
+ "enemy_altitude 4263.977523 17091.003655 \n",
2576
+ "missile_speed 1372.000000 686.000000 \n",
2577
+ "missile_range 110000.000000 8000.000000 \n",
2578
+ "enemy_generation 4.000000 4.000000 \n",
2579
+ "countermeasure_deployed 0.000000 1.000000 \n",
2580
+ "evasion_time 10.753401 3.142886 \n",
2581
+ "hit 1.000000 0.000000 \n",
2582
+ "\n",
2583
+ "[16 rows x 1000 columns]"
2584
+ ]
2585
+ },
2586
+ "execution_count": 33,
2587
+ "metadata": {},
2588
+ "output_type": "execute_result"
2589
+ }
2590
+ ],
2591
+ "source": [
2592
+ "# df.T"
2593
+ ]
2594
+ },
2595
+ {
2596
+ "cell_type": "code",
2597
+ "execution_count": null,
2598
+ "id": "afbdd5d1",
2599
+ "metadata": {},
2600
+ "outputs": [
2601
+ {
2602
+ "name": "stdout",
2603
+ "output_type": "stream",
2604
+ "text": [
2605
+ " launch_distance remaining_distance closure_rate evasion_time \\\n",
2606
+ "count 1000.000000 1000.000000 1000.000000 1000.000000 \n",
2607
+ "mean 64915.002129 31821.659117 1266.905104 24.195931 \n",
2608
+ "std 74767.756596 45271.208405 482.164719 31.584890 \n",
2609
+ "min 669.396283 2.403273 4.027710 0.006151 \n",
2610
+ "25% 10515.861139 3803.500738 872.679155 4.102251 \n",
2611
+ "50% 39195.914463 14983.450620 1327.613182 11.872938 \n",
2612
+ "75% 93328.236924 40924.330595 1573.072050 31.334417 \n",
2613
+ "max 395478.660029 389130.451998 2858.542394 284.080232 \n",
2614
+ "\n",
2615
+ " your_speed your_altitude \n",
2616
+ "count 1000.000000 1000.000000 \n",
2617
+ "mean 521.111816 14957.021066 \n",
2618
+ "std 263.082784 8549.393126 \n",
2619
+ "min 62.358418 72.422424 \n",
2620
+ "25% 297.653412 7338.047586 \n",
2621
+ "50% 518.151709 15324.311391 \n",
2622
+ "75% 751.001456 22210.948597 \n",
2623
+ "max 982.046031 29963.768605 \n"
2624
+ ]
2625
+ }
2626
+ ],
2627
+ "source": [
2628
+ "# # Check feature ranges are within expected bounds\n",
2629
+ "# print(df[['launch_distance', 'remaining_distance', 'closure_rate', \n",
2630
+ "# 'evasion_time', 'your_speed', 'your_altitude']].describe())"
2631
+ ]
2632
+ },
2633
+ {
2634
+ "cell_type": "code",
2635
+ "execution_count": null,
2636
+ "id": "ea4cf1bf",
2637
+ "metadata": {},
2638
+ "outputs": [
2639
+ {
2640
+ "name": "stdout",
2641
+ "output_type": "stream",
2642
+ "text": [
2643
+ "remaining > launch: 0\n"
2644
+ ]
2645
+ }
2646
+ ],
2647
+ "source": [
2648
+ "# # Check remaining_distance never exceeds launch_distance\n",
2649
+ "# print(f\"remaining > launch: {(df['remaining_distance'] > df['launch_distance']).sum()}\")"
2650
+ ]
2651
+ },
2652
+ {
2653
+ "cell_type": "code",
2654
+ "execution_count": null,
2655
+ "id": "d16d1952",
2656
+ "metadata": {},
2657
+ "outputs": [
2658
+ {
2659
+ "name": "stdout",
2660
+ "output_type": "stream",
2661
+ "text": [
2662
+ "hit\n",
2663
+ "0 602\n",
2664
+ "1 398\n",
2665
+ "Name: count, dtype: int64\n",
2666
+ "hit\n",
2667
+ "0 0.602\n",
2668
+ "1 0.398\n",
2669
+ "Name: proportion, dtype: float64\n"
2670
+ ]
2671
+ }
2672
+ ],
2673
+ "source": [
2674
+ "# # Check hit label distribution\n",
2675
+ "# print(df['hit'].value_counts())\n",
2676
+ "# print(df['hit'].value_counts(normalize=True)) # It shows the distribution as percentages instead of counts."
2677
+ ]
2678
+ },
2679
+ {
2680
+ "cell_type": "markdown",
2681
+ "id": "6955deb6",
2682
+ "metadata": {},
2683
+ "source": [
2684
+ "- `remaining > launch: 0` - physical constraint holds ✅\n",
2685
+ "- All ranges look sensible - speeds, altitudes, distances all within expected bounds ✅\n",
2686
+ "- evasion_time min is 0.006s (very close missile, terminal phase) - realistic ✅\n",
2687
+ "- **Hit distribution:** 60% miss, 40% hit - reasonable balance, not skewed ✅\n",
2688
+ "\n",
2689
+ "---"
2690
+ ]
2691
+ },
2692
+ {
2693
+ "cell_type": "markdown",
2694
+ "id": "a56f6f02",
2695
+ "metadata": {},
2696
+ "source": [
2697
+ "## **Full Dataset Generation**\n",
2698
+ "\n",
2699
+ "Sanity check passed on **1,000** rows, no negatives, no constraint violations.\n",
2700
+ "\n",
2701
+ "Changing `N_ROWS` to `1_000_000` and running full generation."
2702
+ ]
2703
+ },
2704
+ {
2705
+ "cell_type": "code",
2706
+ "execution_count": 40,
2707
+ "id": "c8722b7c",
2708
+ "metadata": {},
2709
+ "outputs": [],
2710
+ "source": [
2711
+ "import importlib\n",
2712
+ "import physics_generator\n",
2713
+ "\n",
2714
+ "# Reload the module to pick up any changes made to the script\n",
2715
+ "importlib.reload(physics_generator)\n",
2716
+ "from physics_generator import generate_dataset"
2717
+ ]
2718
+ },
2719
+ {
2720
+ "cell_type": "code",
2721
+ "execution_count": 41,
2722
+ "id": "0ef8538e",
2723
+ "metadata": {},
2724
+ "outputs": [
2725
+ {
2726
+ "name": "stdout",
2727
+ "output_type": "stream",
2728
+ "text": [
2729
+ "Done. 1,000,000 rows saved to /mnt/d/Eakem/Learning ML and DS/Project_ATAS/ATAS_Project/data/synthetic_engagements.csv\n"
2730
+ ]
2731
+ }
2732
+ ],
2733
+ "source": [
2734
+ "# Generate the data with ~1M rows\n",
2735
+ "df_full = generate_dataset()"
2736
+ ]
2737
+ },
2738
+ {
2739
+ "cell_type": "markdown",
2740
+ "id": "881b8593",
2741
+ "metadata": {},
2742
+ "source": [
2743
+ "**Now from here moving to Hit and ETA classifier notebooks**\n",
2744
+ "\n",
2745
+ "---"
2746
+ ]
2747
+ }
2748
+ ],
2749
+ "metadata": {
2750
+ "kernelspec": {
2751
+ "display_name": "env",
2752
+ "language": "python",
2753
+ "name": "python3"
2754
+ },
2755
+ "language_info": {
2756
+ "codemirror_mode": {
2757
+ "name": "ipython",
2758
+ "version": 3
2759
+ },
2760
+ "file_extension": ".py",
2761
+ "mimetype": "text/x-python",
2762
+ "name": "python",
2763
+ "nbconvert_exporter": "python",
2764
+ "pygments_lexer": "ipython3",
2765
+ "version": "3.11.15"
2766
+ }
2767
+ },
2768
+ "nbformat": 4,
2769
+ "nbformat_minor": 5
2770
+ }
src/classifier.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ classifier.py
3
+ -------------
4
+ Loads the trained EfficientNetV2-L aircraft classifier and runs inference
5
+ with Test Time Augmentation (TTA).
6
+
7
+ Responsibilities:
8
+ - Set TF_USE_LEGACY_KERAS before any TensorFlow import
9
+ - Load the saved .keras model from disk once at module import time
10
+ - Accept an image file path and return a predicted aircraft class name
11
+ - Apply TTA (N=15) using horizontal flip, vertical flip,
12
+ brightness and contrast jitter
13
+
14
+ Used by: main.py (FastAPI endpoint)
15
+ """
16
+
17
+ # Must be set before any TensorFlow or TF Hub import.
18
+ # The model was trained with tf_keras (Keras 2) and saved in that format.
19
+ # Without this flag, TF Hub's KerasLayer will fail to deserialize correctly
20
+ # on TF 2.13+ which defaults to Keras 3.
21
+ import os
22
+ os.environ["TF_USE_LEGACY_KERAS"] = "1"
23
+
24
+
25
+ import tensorflow as tf
26
+ import tensorflow_hub as hub
27
+ from src.schemas import MODEL_PATHS
28
+ import numpy as np
29
+
30
+ # Prevent TF from allocating all VRAM at startup — allocate as needed instead.
31
+ # Skipped automatically on CPU-only environments (e.g. Hugging Face Spaces).
32
+ gpus = tf.config.list_physical_devices('GPU')
33
+ if gpus:
34
+ tf.config.experimental.set_memory_growth(gpus[0], True)
35
+
36
+ # Load the Keras model
37
+
38
+ try:
39
+ classifier_model = tf.keras.models.load_model(
40
+ MODEL_PATHS['classifier'],
41
+ custom_objects={"KerasLayer": hub.KerasLayer}
42
+ )
43
+ print("Classifier model loaded successfully.")
44
+ except FileNotFoundError:
45
+ print(f"Error: model file not found at {MODEL_PATHS['classifier']}")
46
+ except Exception as e:
47
+ print(f"Error loading classifier model: {e}")
48
+
49
+
50
+ CLASS_NAMES = [
51
+ 'A10', 'A400M', 'AG600', 'AH64', 'AKINCI', 'AV8B', 'An124', 'An22',
52
+ 'An225', 'An72', 'B1', 'B2', 'B21', 'B52', 'Be200', 'C1', 'C130',
53
+ 'C17', 'C2', 'C390', 'C5', 'CH47', 'CH53', 'CL415', 'E2', 'E7',
54
+ 'EF2000', 'EMB314', 'F117', 'F14', 'F15', 'F16', 'F18', 'F2',
55
+ 'F22', 'F35', 'F4', 'FCK1', 'H6', 'Il76', 'J10', 'J20', 'J35',
56
+ 'J36', 'J50', 'JAS39', 'JF17', 'JH7', 'KAAN', 'KC135', 'KF21',
57
+ 'KIZILELMA', 'KJ600', 'Ka27', 'Ka52', 'MQ20', 'MQ25', 'MQ28',
58
+ 'MQ9', 'Mi24', 'Mi26', 'Mi28', 'Mi8', 'Mig29', 'Mig31',
59
+ 'Mirage2000', 'NH90', 'P3', 'RQ4', 'Rafale', 'SR71', 'Su24',
60
+ 'Su25', 'Su34', 'Su47', 'Su57', 'T50', 'TB001', 'TB2', 'Tejas',
61
+ 'Tornado', 'Tu160', 'Tu22M', 'Tu95', 'U2', 'UH60', 'US2', 'V22',
62
+ 'V280', 'Vulcan', 'WZ10', 'WZ7', 'WZ9', 'X29', 'X32', 'XB70',
63
+ 'XQ58', 'Y20', 'YF23', 'Z10', 'Z19'
64
+ ]
65
+
66
+
67
+ # Create a function to preprocess the image
68
+ def _process_image(image_path:str, image_size=(480, 480)):
69
+ """
70
+ Load and preprocess a single image from disk.
71
+ - Reads raw bytes from the filepath
72
+ - Decodes into an RGB tensor
73
+ - Resizes to the target image size
74
+ - Normalizes pixel values to [0, 1]
75
+
76
+ Args:
77
+ image_path: Path to the image file (string)
78
+ image_size: Target image size
79
+
80
+ Returns:
81
+ Preprocessed image tensor
82
+ """
83
+
84
+ # Read the image as raw bytes from the filepath
85
+ image = tf.io.read_file(image_path)
86
+
87
+ # Decode into an RGB tensor
88
+ image = tf.image.decode_jpeg(image, channels=3)
89
+
90
+ # Convert pixel values from 0-255 to 0-1
91
+ image = tf.image.convert_image_dtype(image, tf.float32)
92
+
93
+ # Resize the image to the desired shape
94
+ image = tf.image.resize(image, image_size)
95
+
96
+ return image
97
+
98
+
99
+
100
+ # Function to apply augmentation to Validation or Test images
101
+ def _tta_augment(image):
102
+ """
103
+ Applies random augmentations to a single image at inference time.
104
+ Matches training augmentation exactly: flips, brightness, contrast.
105
+ Used during TTA to generate N augmented versions of the same image.
106
+
107
+ Args:
108
+ image: preprocessed image tensor, shape (Hight, Width, 3 channels)
109
+
110
+ Returns:
111
+ Augmented image tensor, same shape as input.
112
+ """
113
+
114
+ image = tf.image.random_flip_left_right(image)
115
+ image = tf.image.random_flip_up_down(image)
116
+ image = tf.image.random_brightness(image, max_delta=0.1)
117
+ image = tf.image.random_contrast(image, lower=0.9, upper=1.1)
118
+
119
+ return image
120
+
121
+
122
+ # Function to do prediction and apply augmentaion during predictions
123
+
124
+ def _tta_predict(model, image_path, n_augments=15):
125
+ """
126
+ Performs Test-Time Augmentation (TTA) prediction on a single image.
127
+
128
+ This function loads an image, generates multiple augmented versions of it
129
+ (creating an on-the-fly batch), and passes the entire batch through the
130
+ model in a single forward pass. The final output is the average of the
131
+ softmax probabilities across all versions, which improves prediction robustness.
132
+
133
+ Args:
134
+ model (tf.keras.Model): The loaded aircraft classifier model.
135
+ image_path (str or Path): The file path to the input image.
136
+ n_augments (int, optional): The total number of image variations to
137
+ evaluate (1 original + N-1 augmentations). Defaults to 7.
138
+
139
+ Returns:
140
+ numpy.ndarray: A 1D array containing the averaged softmax probability
141
+ vector for the target image.
142
+ """
143
+ # 1. Load and preprocess the image
144
+ image = _process_image(image_path=image_path)
145
+
146
+ # 2. Build batch of N versions
147
+ versions = [image]
148
+ for _ in range(n_augments - 1):
149
+ versions.append(_tta_augment(image))
150
+
151
+ # 3. Stack into one batch (N, H, W, 3) - default batch size 7
152
+ batch = tf.stack(versions, axis=0)
153
+
154
+ # 4. One predict call
155
+ # FAST INFERENCE: Call the model directly instead of .predict()
156
+ # training=False ensures layers like Dropout and BatchNorm behave correctly for inference (Using a trained model to make predictions on new data)
157
+ predictions = model(batch, training=False).numpy()
158
+
159
+ # predictions = model.predict(batch, verbose=2)
160
+
161
+ # 5. Average the N Softmax vectors
162
+ avg_pred = np.mean(predictions, axis=0)
163
+
164
+ return avg_pred
165
+
166
+
167
+
168
+
169
+ # Function to predict the class name of the aircraft
170
+ def predict_aircraft(image_path:str):
171
+ """
172
+ Run TTA inference on a single image and return the predicted aircraft class name.
173
+
174
+ Args:
175
+ image_path (str): Path to the input image file.
176
+
177
+ Returns:
178
+ str: Predicted aircraft class name (e.g. 'F22', 'Rafale').
179
+ """
180
+
181
+ # Make the predictions
182
+ average_tta_predictions = _tta_predict(model=classifier_model,
183
+ image_path=image_path,
184
+ n_augments=15)
185
+
186
+ # Get the maximum probaility class index
187
+ class_label_idx = np.argmax(average_tta_predictions)
188
+ class_label = CLASS_NAMES[class_label_idx]
189
+
190
+ return class_label
src/decision.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ decision.py — Tactical Decision Layer
3
+ ---------------------------------------
4
+ Receives ETA and hit probability from models.py and converts them into
5
+ a human-readable threat level and tactical recommendation.
6
+
7
+ This module knows nothing about models, physics, or metadata.
8
+ Its only job is: predictions in, decision out.
9
+ """
10
+
11
+ from src.schemas import DECISION_THRESHOLDS
12
+
13
+ def get_recommendation(eta_seconds, hit_probability, no_aa_capability, enemy_generation, friendly_generation):
14
+ """
15
+ Converts model predictions and platform data into a tactical recommendation.
16
+
17
+ Args:
18
+ eta_seconds (float): Predicted evasion time in seconds, clipped at 0.
19
+ hit_probability (float): Probability of hit after evasion maneuvers (0.0 - 1.0).
20
+ no_aa_capability (int): 1 if enemy has no air-to-air capability, 0 otherwise.
21
+ enemy_generation (float): Enemy aircraft generation (e.g. 4.0, 4.5, 5.0).
22
+ friendly_generation (float): Friendly aircraft generation (e.g. 4.0, 4.5, 5.0).
23
+
24
+ Returns:
25
+ str: Tactical recommendation string for the pilot.
26
+ """
27
+
28
+ # Check first air-to-air capability
29
+ if no_aa_capability == 1:
30
+ return "NON-THREAT PLATFORM - MONITOR"
31
+
32
+ # Calculate the generational gap
33
+ gen_gap = friendly_generation - enemy_generation
34
+
35
+ # Define combat posture
36
+ if gen_gap >= DECISION_THRESHOLDS['gen_advantage_threshold']:
37
+ combat_posture = "Friendly advantage"
38
+ elif gen_gap <= -DECISION_THRESHOLDS['gen_disadvantage_threshold']:
39
+ combat_posture = "Enemy advantage"
40
+ else:
41
+ combat_posture = "Parity"
42
+
43
+ # Get the ETA status
44
+ if eta_seconds <= DECISION_THRESHOLDS['eta_critical']:
45
+ eta_status = "Low ETA"
46
+ else:
47
+ eta_status = "High ETA"
48
+
49
+
50
+ # Get the Hit probability status
51
+ if hit_probability >= DECISION_THRESHOLDS['hit_prob_high']:
52
+ hit_status = "High hit probability"
53
+ elif DECISION_THRESHOLDS['hit_prob_medium'] <= hit_probability <= DECISION_THRESHOLDS['hit_prob_high']:
54
+ hit_status = "Medium hit probability"
55
+ else:
56
+ hit_status = "Low hit probability"
57
+
58
+
59
+ # Get the recommendation based on decisions
60
+ if eta_status == "Low ETA":
61
+ if hit_status == "High hit probability":
62
+ # Critical threat - missile close, high chance of hit
63
+ if combat_posture == "Friendly advantage":
64
+ return "BREAK HARD + DEPLOY CM + COUNTER-ENGAGE"
65
+ elif combat_posture == "Parity":
66
+ return "BREAK HARD + DEPLOY CM"
67
+ else:
68
+ # Outmatched - survive first, disengage
69
+ return "BREAK HARD + DEPLOY CM + DISENGAGE IMMEDIATELY"
70
+
71
+ elif hit_status == "Medium hit probability":
72
+ # Elevated threat - missile close, uncertain outcome
73
+ if combat_posture == "Friendly advantage":
74
+ return "DEPLOY CM + DEFENSIVE MANEUVERS + ENGAGE"
75
+ elif combat_posture == "Parity":
76
+ return "DEPLOY CM + DEFENSIVE MANEUVERS"
77
+ else:
78
+ # Disadvantaged - don't risk engagement
79
+ return "DEPLOY CM + DEFENSIVE MANEUVERS + DISENGAGE"
80
+
81
+ else:
82
+ # Missile close but likely evadable - redirect and reassess
83
+ if combat_posture == "Friendly advantage":
84
+ return "DEPLOY CM + CHANGE FLIGHT PATH + ENGAGE"
85
+ elif combat_posture == "Parity":
86
+ return "DEPLOY CM + CHANGE FLIGHT PATH"
87
+ else:
88
+ return "DEPLOY CM + CHANGE FLIGHT PATH + DISENGAGE"
89
+
90
+ else:
91
+ if hit_status == "High hit probability":
92
+ # Time available but threat is serious - prepare now
93
+ if combat_posture == "Friendly advantage":
94
+ return "DEFENSIVE MANEUVERS + PREPARE CM + COUNTER-ENGAGE"
95
+ elif combat_posture == "Parity":
96
+ return "DEFENSIVE MANEUVERS + PREPARE CM"
97
+ else:
98
+ # Outmatched - use the time to create distance
99
+ return "DEFENSIVE MANEUVERS + PREPARE CM + MAINTAIN DISTANCE"
100
+
101
+ elif hit_status == "Medium hit probability":
102
+ # Uncertain threat with time to monitor - stay ready
103
+ if combat_posture == "Friendly advantage":
104
+ return "PREPARE CM + MONITOR + ENGAGE WHEN READY"
105
+ elif combat_posture == "Parity":
106
+ return "PREPARE CM + MONITOR TRAJECTORY"
107
+ else:
108
+ return "PREPARE CM + MAINTAIN DISTANCE"
109
+
110
+ else:
111
+ # Low threat - missile unlikely to hit, time available
112
+ if combat_posture == "Friendly advantage":
113
+ return "STAY SHARP + CLOSE AND ENGAGE"
114
+ elif combat_posture == "Parity":
115
+ return "STAY SHARP + MONITOR TRAJECTORY"
116
+ else:
117
+ # Disadvantaged even in low threat - keep distance
118
+ return "STAY SHARP + MAINTAIN DISTANCE"
src/metadata.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ What is metadata.py
3
+
4
+ When the classifier identifies an aircraft say "F22", the pipeline needs its specs: missile speed, missile range, enemy generation, maneuverability. Those live in your aircraft_metadata.csv.
5
+ metadata.py has one job: take an aircraft name, look it up in the CSV, return its specs as a dictionary.
6
+ """
7
+
8
+ import pandas as pd
9
+ from src.schemas import BASE_DIR
10
+
11
+ CSV_PATH = BASE_DIR / "data" / "aircraft_metadata.csv"
12
+
13
+ # Load the aircraft metadata
14
+ aircraft_df = pd.read_csv(CSV_PATH)
15
+
16
+ # Function to get the Aircraft specs
17
+ def get_aircraft_metadata(aircraft_name:str) -> dict:
18
+ """
19
+ Look up aircraft specifications from the metadata CSV by aircraft name.
20
+
21
+ Args:
22
+ aircraft_name (str): Aircraft name (case-insensitive). e.g. "F22", "rafale"
23
+
24
+ Returns:
25
+ dict: All CSV columns for that aircraft. Empty dict if not found.
26
+ """
27
+
28
+ # Upper case everything to search
29
+ aircraft_name = aircraft_name.upper()
30
+
31
+ # Find the row
32
+ aircraft_row = aircraft_df[aircraft_df['aircraft'].str.upper() == aircraft_name]
33
+
34
+ # Handling egde cases
35
+ if not aircraft_row.empty:
36
+ return aircraft_row.iloc[0].to_dict()
37
+ else:
38
+ return {}
39
+
40
+
src/models.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ models.py — Inference Layer
3
+ ----------------------------
4
+ Loads the trained ETA regressor and hit classifier from disk once at module
5
+ import time. Exposes a single function that accepts a feature dict, converts
6
+ it to an ordered numpy array using FEATURE_COLUMNS, runs both models, and
7
+ returns ETA in seconds and hit probability as a float.
8
+
9
+ This module knows nothing about physics, metadata, or decisions.
10
+ Its only job is: feature array in, predictions out.
11
+ """
12
+
13
+
14
+ from src.schemas import FEATURE_COLUMNS, MODEL_PATHS
15
+ from joblib import load
16
+ import numpy as np
17
+
18
+ # Empty dictionary to hold the active loaded model
19
+ loaded_model = {}
20
+
21
+ # Load the model once in the memory
22
+ for model_name, path in MODEL_PATHS.items():
23
+ if model_name in ('eta', 'hit'):
24
+ try:
25
+ loaded_model[model_name] = load(path)
26
+ print(f"Successfully loaded: {model_name} model")
27
+ except FileNotFoundError:
28
+ print(f"Error: The file at {path} could not be found.")
29
+ except Exception as e:
30
+ print(f"An error occurred while loading model {model_name}: {e}")
31
+
32
+
33
+
34
+ # Function to make predictions using loaded models
35
+ def make_predictions(feature_dict):
36
+ """
37
+ Converts a feature dict to a numpy array and runs both models.
38
+
39
+ Args:
40
+ feature_dict (dict): 14-feature dict in FEATURE_COLUMNS order,
41
+ produced by build_feature_array().
42
+
43
+ Returns:
44
+ dict: {
45
+ "eta_seconds": float, # predicted evasion time, clipped at 0
46
+ "hit_probability": float # probability of hit after evasion (0.0 - 1.0)
47
+ }
48
+ """
49
+
50
+ # Convert the dictionary into ndarray using the order of FEATURE_COLUMNS
51
+ feature_values = np.array([feature_dict[col] for col in FEATURE_COLUMNS])
52
+
53
+ # Reshape the the feature_values into (1, 14) as a line of table
54
+ feature_values = feature_values.reshape(1, -1)
55
+
56
+ # Performing predictions
57
+ for model_name, model in loaded_model.items():
58
+ if model_name == 'eta':
59
+ eta_prediction = model.predict(feature_values)
60
+ # Clip the negative time value to 0
61
+ eta_prediction = np.maximum(eta_prediction, 0)[0]
62
+
63
+ else:
64
+ hit_prediction = model.predict_proba(feature_values)[0][1] # [probability of miss, probability of hit]
65
+
66
+
67
+ return {
68
+ "eta_seconds": float(eta_prediction),
69
+ "hit_probability": float(hit_prediction)
70
+ }
71
+
src/physics_generator.py ADDED
@@ -0,0 +1,461 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Importing the required libraries
2
+ import numpy as np
3
+ import pandas as pd
4
+ import math
5
+ from pathlib import Path
6
+
7
+ # ── Paths ─────────────────────────────────────────────────────────────────────
8
+ PROJECT_ROOT = Path(__file__).resolve().parent.parent
9
+ DATA_ROOT = PROJECT_ROOT / "data"
10
+ OUTPUT_PATH = DATA_ROOT / "synthetic_engagements.csv"
11
+ METADATA_PATH = DATA_ROOT / "aircraft_metadata.csv"
12
+
13
+ # ── Generation config ─────────────────────────────────────────────────────────
14
+ N_ROWS = 1_000_000 # Python ignores underscores in numbers, just readability
15
+
16
+ # ── Feature ranges ────────────────────────────────────────────────────────────
17
+ # Launch distance: how far the missile was fired from (metres)
18
+ # Floor is 500m — below this, guns are more effective than missiles and
19
+ # the missile doesn't have enough distance to arm and track properly
20
+ # Upper bound is capped to each aircraft's missile_range at row generation
21
+ LAUNCH_DISTANCE_MIN = 500
22
+
23
+ # Your aircraft speed range - 61 m/s (TB2 drone) → 983 m/s (SR-71), full metadata range
24
+ YOUR_SPEED_RANGE = (61, 983)
25
+
26
+ # Your aircraft altitude (metres)
27
+ # 0 = ground level, 30,000m = upper combat/operational ceiling
28
+ # SR-71 operates at ~24,000m, most fighters top out around 20,000m
29
+ # 30,000m gives headroom for all 102 aircraft in metadata
30
+ YOUR_ALTITUDE_RANGE = (0, 30_000)
31
+
32
+ # Enemy aircraft altitude (metres) — same envelope as your own aircraft
33
+ # Determines vertical geometry of the engagement alongside elevation angle
34
+ ENEMY_ALTITUDE_RANGE = (0, 30_000)
35
+
36
+ # Azimuth: horizontal angle of incoming threat, clockwise from North
37
+ # 0° = head-on, 90° = right side, 180° = tail-chase, 270° = left side
38
+ AZIMUTH_RANGE = (0, 360)
39
+
40
+ # Elevation: vertical angle of incoming threat
41
+ # 0° = same altitude, +90° = directly above, -90° = directly below
42
+ ELEVATION_RANGE = (-90, 90)
43
+
44
+ # Maneuverability: 0 = low (bomber/transport), 1 = medium (older jets),
45
+ # 2 = high (modern fighters)
46
+ MANEUVERABILITY_VALUES = [0, 1, 2]
47
+
48
+ # Countermeasure: 0 = not deployed, 1 = deployed (flares/chaff)
49
+ COUNTERMEASURE_VALUES = [0, 1]
50
+
51
+
52
+ # ── Runs once — loads combat-capable aircraft from metadata CSV ───────────────
53
+ def _load_metadata():
54
+ """
55
+ Loads combat-capable aircraft from the metadata CSV (no_aa_capability == 0).
56
+
57
+ Returns:
58
+ pd.DataFrame: Filtered metadata containing only aircraft with air-to-air missile capability.
59
+ """
60
+
61
+ if not METADATA_PATH.exists():
62
+ raise FileNotFoundError(f"Metadata CSV not found: {METADATA_PATH}")
63
+ metadata_df = pd.read_csv(METADATA_PATH)
64
+ metadata_df = metadata_df[metadata_df["no_aa_capability"]==0]
65
+ return metadata_df
66
+
67
+
68
+ # ── Derives missile phase from how far it has already traveled ────────────────
69
+ # phase 0 = boost (just launched), 1 = mid-course, 2 = terminal (final approach)
70
+ def _derive_missile_phase(remaining_distance, launch_distance):
71
+ """
72
+ Derives missile flight phase from how much of its journey it has completed.
73
+
74
+ Args:
75
+ remaining_distance (float): Distance remaining between missile and target (metres).
76
+ launch_distance (float): Total distance at the moment of launch (metres).
77
+
78
+ Returns:
79
+ int: 0 = boost, 1 = mid-course, 2 = terminal.
80
+
81
+ Phase 0 — engine burning, accelerating, just launched
82
+ Phase 1 — flying toward your predicted position, guided but not actively tracking you yet
83
+ Phase 2 — active seeker on, tracking you specifically, hardest to fool
84
+ """
85
+
86
+ traveled_distance = launch_distance - remaining_distance
87
+ ratio = traveled_distance / launch_distance
88
+
89
+ if ratio < 0.33:
90
+ return 0
91
+ elif 0.33 <= ratio < 0.66:
92
+ return 1
93
+ else:
94
+ return 2
95
+
96
+
97
+ # ── Derives closure rate using 3D geometry (azimuth + elevation + speeds) ─────
98
+ # How fast the gap between you and the missile is closing, in m/s
99
+ # Enemy altitude adds the vertical dimension to the engagement geometry
100
+ def _derive_closure_rate(missile_speed, your_speed,
101
+ azimuth, elevation):
102
+ """
103
+ Derives the closure rate - how fast the gap between the missile and the target is closing (m/s).
104
+
105
+ Uses 3D geometry: azimuth accounts for horizontal approach angle,
106
+ elevation accounts for vertical approach angle.
107
+
108
+ Args:
109
+ missile_speed (float): Speed of the incoming missile (m/s).
110
+ your_speed (float): Speed of the friendly aircraft (m/s).
111
+ azimuth (float): Horizontal angle of incoming threat in degrees (0° = head-on).
112
+ elevation (float): Vertical angle of incoming threat in degrees (0° = same altitude).
113
+
114
+ Returns:
115
+ float: Closure rate in m/s.
116
+ """
117
+
118
+ # Convert the angles into radians and prepare for cosine
119
+ azimuth = math.radians(azimuth)
120
+ elevation = math.radians(elevation)
121
+
122
+ # Extract the closure rate
123
+ closure_rate = missile_speed + (your_speed * (math.cos(azimuth) * math.cos(elevation)))
124
+
125
+ return closure_rate
126
+
127
+
128
+ """
129
+ Note: enemy_speed is not included as a feature. This is a known simplification, in real engagements,
130
+ enemy aircraft velocity at launch contributes to effective missile speed. This can be added in a future iteration
131
+ of the physics generator.
132
+ """
133
+
134
+
135
+ def _derive_evasion_time(remaining_distance, closure_rate,
136
+ missile_phase, enemy_generation,
137
+ your_speed, your_altitude, enemy_altitude):
138
+ """
139
+ Derives the minimum evasion time - seconds before the missile reaches you.
140
+
141
+ Base calculation is pure kinematics: remaining_distance / closure_rate.
142
+ Four modifiers are applied to account for factors the base formula cannot capture.
143
+
144
+ Modifiers:
145
+ - missile_phase == 2 (terminal): seeker has locked on, countermeasures
146
+ need 2-3s overhead to be effective. Shrinks window by 15%. (x 0.85)
147
+ - enemy_generation == 5: HOBS (High Off-Boresight)
148
+ seeker + ECCM make the missile harder to
149
+ defeat, compressing effective reaction time by ~10%. (x 0.90)
150
+ - your_speed > 522 m/s (above median): high energy state gives more
151
+ lateral geometry per second during evasion. Slight expansion. (x 1.05)
152
+ - abs(your_altitude - enemy_altitude) > 5000m: large altitude gap pushes
153
+ engagement toward edge of missile performance envelope, degrading
154
+ terminal accuracy. Slight expansion. (x 1.10)
155
+
156
+ Args:
157
+ remaining_distance (float): Distance between missile and you right now (metres).
158
+ closure_rate (float): Combined closing speed from _derive_closure_rate() (m/s).
159
+ missile_phase (int): 0 = boost, 1 = mid-course, 2 = terminal.
160
+ enemy_generation (float): Enemy aircraft generation (3.5, 4, 4.5, or 5).
161
+ your_speed (float): Your current airspeed (m/s).
162
+ your_altitude (float): Your current altitude (metres).
163
+ enemy_altitude (float): Enemy aircraft altitude (metres).
164
+
165
+ Returns:
166
+ float: Evasion time in seconds.
167
+ """
168
+
169
+ # Calculate the evasion time
170
+ evasion_time = remaining_distance / closure_rate
171
+
172
+ # Terminal phase - seeker locked on, countermeasures need 2–3s overhead
173
+ if missile_phase == 2:
174
+ evasion_time *= 0.85
175
+
176
+ # Gen 5 aircraft missile (HOBS + ECCM) compresses effective reaction time
177
+ if enemy_generation == 5:
178
+ evasion_time *= 0.90
179
+
180
+ # High speed — more room to maneuver
181
+ if your_speed > 522:
182
+ evasion_time *= 1.05
183
+
184
+ # Large altitude gap - missile at edge of performance envelope
185
+ if abs(your_altitude - enemy_altitude) > 5000:
186
+ evasion_time *= 1.10
187
+
188
+ return evasion_time
189
+
190
+
191
+
192
+ def _derive_hit_label(countermeasure_deployed, your_maneuverability,
193
+ azimuth, elevation, missile_phase, enemy_generation,
194
+ your_altitude, enemy_altitude):
195
+ """
196
+ Derives whether the missile hits after an evasion attempt.
197
+
198
+ Starts from a neutral survival score of 0.5. Modifiers push it up (more
199
+ likely to hit) or down (more likely to miss). Final label is 1 if the
200
+ missile hits, 0 if it misses.
201
+
202
+ This is where azimuth and elevation belong — they affect survival after
203
+ evasion, not when the missile arrives (that is handled by closure_rate
204
+ in _derive_evasion_time).
205
+
206
+ Modifiers:
207
+ - countermeasure_deployed == 1: active countermeasures significantly
208
+ reduce hit probability. Score drops.
209
+ - your_maneuverability == 2: high maneuverability makes evasion more
210
+ effective. Score drops.
211
+ - azimuth near 0° (head-on): least time and geometry to evade.
212
+ Score rises.
213
+ - missile_phase == 2 (terminal): seeker locked on, hardest to defeat.
214
+ Score rises.
215
+ - enemy_generation == 5: HOBS + ECCM make the missile more lethal.
216
+ Score rises.
217
+ - elevation far from 0°: steep approach angle reduces evasion options.
218
+ Score rises.
219
+ - large altitude differential > 5000m: pushes missile toward edge of
220
+ performance envelope. Score drops.
221
+
222
+ Args:
223
+ countermeasure_deployed (int): 0 = not deployed, 1 = deployed.
224
+ your_maneuverability (int): 0 = low, 1 = medium, 2 = high.
225
+ azimuth (float): Horizontal angle of incoming threat in degrees (0° = head-on).
226
+ elevation (float): Vertical angle of incoming threat in degrees (0° = level).
227
+ missile_phase (int): 0 = boost, 1 = mid-course, 2 = terminal.
228
+ enemy_generation (float): Enemy aircraft generation (3.5, 4, 4.5, or 5).
229
+ your_altitude (float): Your current altitude (metres).
230
+ enemy_altitude (float): Enemy aircraft altitude (metres).
231
+
232
+ Returns:
233
+ int: 1 if missile hits, 0 if missile misses.
234
+ """
235
+
236
+ # Neutral starting score - modifiers push it toward hit or miss
237
+ score = 0.5
238
+
239
+ # Countermeasures active - significantly reduces hit chance
240
+ if countermeasure_deployed == 1:
241
+ score -= 0.20
242
+
243
+ # High maneuverability - evasion more effective
244
+ if your_maneuverability == 2:
245
+ score -= 0.10
246
+
247
+ # Head-on approach - least time and geometry to evade
248
+ if azimuth < 30:
249
+ score += 0.15
250
+
251
+ # Terminal phase - seeker locked on, hardest to defeat
252
+ if missile_phase == 2:
253
+ score += 0.15
254
+
255
+ # Gen 5 missile - HOBS + ECCM make it more lethal
256
+ if enemy_generation == 5:
257
+ score += 0.10
258
+
259
+ # Steep approach angle - reduces evasion options
260
+ if abs(elevation) > 45:
261
+ score += 0.10
262
+
263
+ # Large altitude gap - missile at edge of performance envelope
264
+ if abs(your_altitude - enemy_altitude) > 5000:
265
+ score -= 0.10
266
+
267
+ # Hit if score crosses 0.5
268
+ return 1 if score >= 0.5 else 0
269
+
270
+
271
+ # ── Generates one complete engagement scenario as a dict ─────────────────────
272
+ def _generate_row(aircraft_row):
273
+ """
274
+ Generates one synthetic engagement scenario for a given aircraft.
275
+
276
+ Samples random values for all situational features, derives computed
277
+ features using physics, and returns a complete row with labels.
278
+
279
+ Args:
280
+ aircraft_row (pd.Series): One row from the metadata CSV.
281
+
282
+ Returns:
283
+ dict: All 14 features plus evasion_time and hit labels.
284
+ """
285
+
286
+ # Sample situational features
287
+ your_speed = np.random.uniform(*YOUR_SPEED_RANGE)
288
+ your_altitude = np.random.uniform(*YOUR_ALTITUDE_RANGE)
289
+ enemy_altitude = np.random.uniform(*ENEMY_ALTITUDE_RANGE)
290
+ azimuth = np.random.uniform(*AZIMUTH_RANGE)
291
+ elevation = np.random.uniform(*ELEVATION_RANGE)
292
+
293
+ # Sample categorical features
294
+ your_maneuverability = np.random.choice(MANEUVERABILITY_VALUES)
295
+ countermeasure_deployed = np.random.choice(COUNTERMEASURE_VALUES)
296
+
297
+ # Pull threat specs from metadata
298
+ missile_speed = aircraft_row["missile_speed"]
299
+ missile_range = aircraft_row["missile_range"]
300
+ enemy_generation = aircraft_row["enemy_generation"]
301
+
302
+ # Sample engagement distances
303
+ launch_distance = np.random.uniform(LAUNCH_DISTANCE_MIN, missile_range)
304
+ remaining_distance = np.random.uniform(0, launch_distance) # remaining_distance <= launch_distance always
305
+
306
+ # Derive computed features
307
+ missile_phase = _derive_missile_phase(remaining_distance, launch_distance)
308
+ closure_rate = _derive_closure_rate(missile_speed, your_speed,
309
+ azimuth, elevation)
310
+
311
+ # Derive labels
312
+ evasion_time = _derive_evasion_time(remaining_distance, closure_rate,
313
+ missile_phase, enemy_generation,
314
+ your_speed, your_altitude, enemy_altitude)
315
+ hit = _derive_hit_label(countermeasure_deployed, your_maneuverability,
316
+ azimuth, elevation, missile_phase, enemy_generation,
317
+ your_altitude, enemy_altitude)
318
+
319
+ # Return complete row
320
+ return {
321
+ "launch_distance": launch_distance,
322
+ "remaining_distance": remaining_distance,
323
+ "closure_rate": closure_rate,
324
+ "azimuth": azimuth,
325
+ "elevation": elevation,
326
+ "missile_phase": missile_phase,
327
+ "your_speed": your_speed,
328
+ "your_altitude": your_altitude,
329
+ "your_maneuverability": your_maneuverability,
330
+ "enemy_altitude": enemy_altitude,
331
+ "missile_speed": missile_speed,
332
+ "missile_range": missile_range,
333
+ "enemy_generation": enemy_generation,
334
+ "countermeasure_deployed": countermeasure_deployed,
335
+ "evasion_time": evasion_time,
336
+ "hit": hit
337
+ }
338
+
339
+
340
+
341
+ # ── Saves completed DataFrame to CSV ─────────────────────────────────────────
342
+ def _save_dataset(df):
343
+ """
344
+ Saves the generated dataset to a CSV file.
345
+
346
+ Args:
347
+ df (pd.DataFrame): The complete synthetic engagement dataset.
348
+ """
349
+
350
+ # Create data directory if it doesn't exist
351
+ OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
352
+
353
+ # Save to CSV
354
+ df.to_csv(OUTPUT_PATH, index=False)
355
+
356
+
357
+ # ── Public entry point — the only function the notebook calls ─────────────────
358
+ def generate_dataset():
359
+ """
360
+ Generates 1,000,000 synthetic engagement scenarios and saves to CSV.
361
+
362
+ Loads combat-capable aircraft from metadata, samples each aircraft
363
+ equally across all rows, derives all features and labels using physics,
364
+ and writes the final dataset to disk.
365
+
366
+ Only valid threat scenarios are included (closure_rate > 0).
367
+
368
+ Returns:
369
+ pd.DataFrame: The complete synthetic engagement dataset.
370
+ """
371
+ metadata = _load_metadata()
372
+
373
+ rows_per_aircraft = N_ROWS // len(metadata) # 1_000_000 // 56 → 17,857 rows per aircraft
374
+ rows = []
375
+
376
+ for _, aircraft_row in metadata.iterrows():
377
+ count = 0
378
+ while count < rows_per_aircraft:
379
+ row = _generate_row(aircraft_row)
380
+ if row["closure_rate"] > 0:
381
+ rows.append(row)
382
+ count += 1
383
+
384
+ # Fill remaining rows to hit exactly N_ROWS
385
+ remaining_rows = N_ROWS - len(rows)
386
+ if remaining_rows:
387
+ count = 0
388
+ while count < remaining_rows:
389
+ row = _generate_row(metadata.sample(1).iloc[0])
390
+ if row["closure_rate"] > 0:
391
+ rows.append(row)
392
+ count += 1
393
+
394
+ df = pd.DataFrame(rows)
395
+ _save_dataset(df)
396
+ print(f"Done. {len(rows):,} rows saved to {OUTPUT_PATH}")
397
+
398
+
399
+
400
+ # Inference only - assembles 14-feature array from HUD inputs and metadata for model prediction
401
+ def build_feature_array(
402
+ # From HUD sliders
403
+ launch_distance, remaining_distance,
404
+ azimuth, elevation,
405
+ your_speed, your_altitude, enemy_altitude,
406
+ countermeasure_deployed,
407
+ # From metadata lookup (enemy aircraft)
408
+ missile_speed, missile_range, enemy_generation,
409
+ # From metadata lookup (friendly aircraft)
410
+ your_maneuverability
411
+ ):
412
+ """
413
+ Assembles the 14-feature array required by the ETA and hit models at inference time.
414
+
415
+ All inputs arrive pre-collected from the HUD sliders and metadata lookups.
416
+ Closure rate and missile phase are derived here from those inputs using the
417
+ same physics helpers used during training. The returned dict is in the exact
418
+ column order the models were trained on.
419
+
420
+ Args:
421
+ launch_distance (float): Distance at moment of missile launch (metres).
422
+ remaining_distance (float): Distance remaining between missile and target (metres).
423
+ azimuth (float): Horizontal angle of incoming threat in degrees (0 = head-on).
424
+ elevation (float): Vertical angle of incoming threat in degrees (0 = level).
425
+ your_speed (float): Friendly aircraft airspeed (m/s).
426
+ your_altitude (float): Friendly aircraft altitude (metres).
427
+ enemy_altitude (float): Enemy aircraft altitude (metres).
428
+ countermeasure_deployed (int): 0 = not deployed, 1 = deployed.
429
+ missile_speed (float): Incoming missile speed (m/s), from enemy metadata.
430
+ missile_range (float): Missile maximum effective range (metres), from enemy metadata.
431
+ enemy_generation (float): Enemy aircraft generation, from enemy metadata.
432
+ your_maneuverability (int): Friendly aircraft maneuverability, from friendly metadata.
433
+ 0 = low, 1 = medium, 2 = high. Maneuverability is an aircraft property
434
+ and follows the same metadata lookup pattern for both friendly and enemy platforms.
435
+
436
+ Returns:
437
+ dict: 14 features in training column order, ready for model inference.
438
+ """
439
+
440
+ # Derive the two features that are computed from inputs rather than sourced directly
441
+ missile_phase = _derive_missile_phase(remaining_distance, launch_distance)
442
+ closure_rate = _derive_closure_rate(missile_speed, your_speed,
443
+ azimuth, elevation)
444
+
445
+ # Assemble and return all 14 features in exact training column order
446
+ return {
447
+ "launch_distance": launch_distance,
448
+ "remaining_distance": remaining_distance,
449
+ "closure_rate": closure_rate,
450
+ "azimuth": azimuth,
451
+ "elevation": elevation,
452
+ "missile_phase": missile_phase,
453
+ "your_speed": your_speed,
454
+ "your_altitude": your_altitude,
455
+ "your_maneuverability": your_maneuverability,
456
+ "enemy_altitude": enemy_altitude,
457
+ "missile_speed": missile_speed,
458
+ "missile_range": missile_range,
459
+ "enemy_generation": enemy_generation,
460
+ "countermeasure_deployed": countermeasure_deployed
461
+ }
src/schemas.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ What is schemas.py
3
+
4
+ A single Python file that holds shared constants — column names, their order, and any fixed values the pipeline depends on.
5
+
6
+ Why it exists
7
+
8
+ Three files will each build or consume the 14-feature array: physics_generator.py, models.py, and main.py. If each one hardcodes its own column list, one typo in one file breaks the whole pipeline silently.
9
+ schemas.py means they all import from the same list. One source of truth.
10
+
11
+ What goes inside it
12
+
13
+ Three things:
14
+ FEATURE_COLUMNS - the 14 feature names in exact training order
15
+ MODEL_PATHS - file paths to your two saved .joblib models
16
+ DECISION_THRESHOLDS - the ETA and hit probability cutoffs your decision layer will use
17
+ """
18
+
19
+
20
+ from pathlib import Path
21
+
22
+ # Path pointing towards project ATAS main folder
23
+ BASE_DIR = Path(__file__).parent.parent
24
+ # Path pointing towards saved models
25
+ MODEL_DIR = BASE_DIR / "release_models"
26
+
27
+ # Feature columns to be feed to models
28
+ FEATURE_COLUMNS = [
29
+ 'launch_distance', 'remaining_distance', 'closure_rate', 'azimuth', 'elevation',
30
+ 'missile_phase', 'your_speed', 'your_altitude', 'your_maneuverability',
31
+ 'enemy_altitude', 'missile_speed', 'missile_range', 'enemy_generation',
32
+ 'countermeasure_deployed'
33
+ ]
34
+
35
+ # Model paths as dict to use them to make predictions
36
+ MODEL_PATHS = {
37
+ 'classifier' : MODEL_DIR / "aircraft_classifier" / "atas_final_fine_tuned_aircraft_classifier_model.keras",
38
+ 'eta' : MODEL_DIR / "eta" / "atas_final_eta_regressor_model.joblib",
39
+ 'hit' : MODEL_DIR / "hit" / "atas_final_hit_classifier_model.joblib"
40
+ }
41
+
42
+ # Cutoffs used by decision.py to convert model outputs into pilot recommendations
43
+ DECISION_THRESHOLDS = {
44
+ 'hit_prob_high': 0.75,
45
+ 'hit_prob_medium': 0.60,
46
+ 'eta_critical': 10,
47
+ 'gen_advantage_threshold': 0.5, # friendly gen must be this much higher to be considered advantaged
48
+ 'gen_disadvantage_threshold': 0.5 # enemy gen must be this much higher to be considered disadvantaged
49
+ }