TiGa-RCE commited on
Commit
ce0b812
·
verified ·
1 Parent(s): 63c56ce

Support all five Formula controls and maximum game networks

Browse files

Keep existing Space access settings and simulator binaries. Expand hosted adapter and editor to all five Formula controls, 20 inputs and ten hidden layers, with original sensor defaults and backward compatibility.

Files changed (7) hide show
  1. BETA_TESTING.md +3 -5
  2. CUDA_REFERENCE.md +42 -46
  3. FRONTEND_SOURCE.json +2 -2
  4. README.md +1 -1
  5. RELEASE_MANIFEST.json +6 -6
  6. index.html +61 -23
  7. worker.py +60 -23
BETA_TESTING.md CHANGED
@@ -36,11 +36,9 @@ restricted source repository as the app entry point.
36
 
37
  ## Supported runner contract
38
 
39
- The worker accepts Formula steering/acceleration networks with two outputs,
40
- two to four layers, at most 16 nodes per layer, speed and raycast sensors, and
41
- bounded execution settings. Unsupported network shapes or sensors are not
42
- accepted for training. The track preview shows tile layout and terrain, not
43
- collision geometry. Star challenges are not evaluated.
44
 
45
  ## CUDA reference check
46
 
 
36
 
37
  ## Supported runner contract
38
 
39
+ The worker accepts Formula networks with any ordered unique subset of the five game controls: Steering, Acceleration, Handbrake, Brake, and Boost. The vanilla maximum is 20 inputs (up to 13 raycasts and 7 other sensors), 12 total layers, 16 neurons per hidden layer, and 2,869 dense weights and biases, with shape `[20]+[16]*10+[5]`. Networks with fewer outputs and layers remain supported. All 13 game sensor types are accepted, including game defaults for omitted parameter values. This is the bounded vanilla Formula contract; it does not claim support for arbitrary modified-game networks.
40
+
41
+ The preview shows tile layout and terrain, not collision geometry. Star challenges are not evaluated.
 
 
42
 
43
  ## CUDA reference check
44
 
CUDA_REFERENCE.md CHANGED
@@ -1,67 +1,63 @@
1
- # Bounded CUDA reference check
2
 
3
- This protocol checks that the hosted CUDA path can accept a known small input,
4
- complete one bounded Formula training run, return a validated artifact, and
5
- delete the server copy after verified download. It is not a test of broad game
6
- parity, training quality, or general CPU/GPU equivalence.
7
 
8
  ## Public-package fixture
9
 
10
- - Track: `builtin_tracks/tracks/Replica B01 Oval.track`, catalog ID
11
- `787f73f5-3b8c-5d95-b0e3-0016ed076970`, SHA-256
12
- `0df5755150c145a35edc6c8693f429e5b9ff4b12210ba67cbfcb932c58faf158`.
13
- - Network: deterministic, generated Formula network, shape `[6,4,2]`; one
14
- speed sensor and five raycasts at `-60,-30,0,30,60` degrees with length 800;
15
- outputs `[0,1]`; for zero-based indices, first-layer weight `W[i][j]` is
16
- `(i+1)*(j+1)/100`, second-layer `W[i][j]` is `(i+1)*(j-0.5)/10`, and every
17
- bias is zero. Serialize the JSON with sorted keys and compact separators.
18
- - Generated network bytes: 554; SHA-256
19
- `d9851cb1eee40dee78fb8c4492caeac8f130b5543f6d4551bb2175c13c579c3b`.
20
-
21
- The following standard-library Python snippet is the complete network fixture
22
- generator. It prints the exact bytes and digest; `Name` is intentionally absent
23
- because the worker does not require it.
24
 
25
  ```python
 
26
  import hashlib
27
  import json
 
 
28
 
29
  def generated_network():
30
- shape = [6, 4, 2]
31
- first = [[(i + 1) * (j + 1) / 100 for j in range(4)] for i in range(6)]
32
- second = [[(i + 1) * (j - 0.5) / 10 for j in range(2)] for i in range(4)]
33
- value = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  "Type": 0,
35
- "Outputs": [0, 1],
36
- "Sensors": [
37
- {"$type": "speed"},
38
- *[
39
- {"$type": "raycast", "Degrees": angle, "Length": 800}
40
- for angle in (-60, -30, 0, 30, 60)
41
- ],
42
- ],
43
  "NeuralNetwork": {
44
  "Shape": shape,
45
- "Weights": [first, second],
46
- "Biases": [[0.0] * 4, [0.0, 0.0]],
47
  },
48
  }
49
- return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
50
 
51
  network = generated_network()
52
- print(len(network), hashlib.sha256(network).hexdigest())
 
 
 
53
  ```
54
 
55
- Pair these network bytes with the unchanged `Replica B01 Oval.track` file from
56
- the package; do not rewrite the compressed track file.
 
57
 
58
- - Bounded run settings: population 1, generation 1, 120 ticks, mutation 0.01,
59
- seed 42, one worker, default total-score reward. These are a smoke fixture,
60
- not a quality recommendation.
61
 
62
- For a live CUDA qualification, first verify the target build ID and included HF
63
- allowance. Run the fixture once through CPU and once through GPU on the same
64
- build when CUDA quota permits. Record both reports, output SHA-256 values, binary
65
- hashes, candidate validation, and deletion receipts. A mismatch is a finding to
66
- investigate; do not widen the claim from this one track/network/settings tuple.
67
- Do not spend paid overage. No live GPU job was run to prepare this protocol.
 
1
+ # Bounded CPU/CUDA reference check
2
 
3
+ This protocol defines a reproducible generated input for qualifying the package's largest vanilla Formula network through the native simulator. A passing run covers only the selected B01 track, fixture, settings, and build. It does not establish broad original-game parity, training quality, or general CPU/CUDA equivalence.
 
 
 
4
 
5
  ## Public-package fixture
6
 
7
+ - Track: `builtin_tracks/tracks/Replica B01 Oval.track`, catalog ID `787f73f5-3b8c-5d95-b0e3-0016ed076970`, SHA-256 `0df5755150c145a35edc6c8693f429e5b9ff4b12210ba67cbfcb932c58faf158`.
8
+ - Network shape: `[20]+[16]*10+[5]`, with five outputs in game order `[0,1,2,3,4]` (Steering, Acceleration, Handbrake, Brake, Boost), 13 raycasts plus the seven unparameterized sensors. This is 12 total layers and 2,869 dense weights and biases. The source fixture includes a `Name` to make the generated JSON self-contained.
9
+ - The Python 3 standard-library snippet below creates deterministic compact JSON bytes and deterministic gzip bytes. It mirrors the generated maximum-shape fixture used by the package contract tests. The generated network is a synthetic reference input, not a trained lapper.
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  ```python
12
+ import gzip
13
  import hashlib
14
  import json
15
+ import random
16
+
17
 
18
  def generated_network():
19
+ shape = [20] + [16] * 10 + [5]
20
+ sensors = [
21
+ {"$type": "raycast", "Degrees": -90 + i * 15, "Length": 800}
22
+ for i in range(13)
23
+ ] + [
24
+ {"$type": name}
25
+ for name in (
26
+ "speed", "velocityFront", "velocitySide", "angularVelocity",
27
+ "wheelAngle", "boostCapacity", "correctDirection",
28
+ )
29
+ ]
30
+ rng = random.Random(20261013)
31
+ weights = [
32
+ [[rng.uniform(-0.2, 0.2) for _ in range(width_out)]
33
+ for _ in range(width_in)]
34
+ for width_in, width_out in zip(shape, shape[1:])
35
+ ]
36
+ biases = [[0.0] * width for width in shape[1:]]
37
+ return {
38
  "Type": 0,
39
+ "Name": "Disposable full-network qualification",
40
+ "Outputs": [0, 1, 2, 3, 4],
41
+ "Sensors": sensors,
 
 
 
 
 
42
  "NeuralNetwork": {
43
  "Shape": shape,
44
+ "Weights": weights,
45
+ "Biases": biases,
46
  },
47
  }
48
+
49
 
50
  network = generated_network()
51
+ raw = json.dumps(network, separators=(",", ":")).encode("utf-8")
52
+ compressed = gzip.compress(raw, mtime=0)
53
+ print("json_bytes", len(raw), "json_sha256", hashlib.sha256(raw).hexdigest())
54
+ print("gzip_bytes", len(compressed), "gzip_sha256", hashlib.sha256(compressed).hexdigest())
55
  ```
56
 
57
+ Feed the compressed bytes with the unchanged B01 track file. Do not decompress and rewrite the bundled track. The script prints the hashes for the exact Python/runtime-generated bytes; retain the printed values with the test receipt.
58
+
59
+ ## Bounded run and receipts
60
 
61
+ Use population 1, generation 1, 120 ticks, mutation 0.01, seed 42, one worker, and the default total-score reward as a smoke setting. The package contract suite separately exercises actual training with the maximum shape; a small smoke setting should not be presented as a training-quality recommendation.
 
 
62
 
63
+ For hosted qualification, record Space/build ID, CPU or CUDA selection, package and binary hashes, fixture hashes, native report, candidate output hash, candidate validation result, download receipt, and server deletion receipt. Compare CPU and CUDA only on the exact same fixture/settings/build where the deployed service supports both routes and CUDA allowance is available. Do not spend paid overage. A mismatch is a finding to investigate; a match on this one fixture does not establish broad parity. No live GPU job is claimed by this protocol; deployed status and executed qualification belong in the release receipt.
 
 
 
 
 
FRONTEND_SOURCE.json CHANGED
@@ -1,9 +1,9 @@
1
  {
2
  "source": "Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas",
3
  "source_sha256": "65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8",
4
- "output_sha256": "aeb602565bf2da9d40cb6c3aae0b6e713b89e79a81557fed3e7f3dd8d16f195c",
5
  "scope": "Frontend only; no engine, private data, track assets, API calls or storage copied",
6
- "status": "Public beta release candidate; local verification pending hosted qualification",
7
  "track_atlas_sha256": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
8
  "client_sha256": "b6c2e0439bc1e4cf55c5e751e497e842e1e911b229f7480d7b1e04dea246536f",
9
  "track_map_sha256": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0"
 
1
  {
2
  "source": "Runner r14 single-engine browser UI; selected .track tile renderer added from simulator atlas",
3
  "source_sha256": "65d1e5f2c4e19b4951b828e82510005ab99a5270cf3dcb9f4eaa0fa1af8c40f8",
4
+ "output_sha256": "7b1b7d41f6ff69353d6101658d6baab4c50c30442e6b3627a9739f93a00b1a2e",
5
  "scope": "Frontend only; no engine, private data, track assets, API calls or storage copied",
6
+ "status": "Five-output and full vanilla-network candidate; hosted qualification recorded separately",
7
  "track_atlas_sha256": "d97c8900e3e5fb17734b5bd61d1f4f4b196d9be30af9848e2e273ae7e4079ae4",
8
  "client_sha256": "b6c2e0439bc1e4cf55c5e751e497e842e1e911b229f7480d7b1e04dea246536f",
9
  "track_map_sha256": "ab5374aa49c7ccec90a787ee8c56cd71beb56b903bf2bbc322783e6f43c009f0"
README.md CHANGED
@@ -10,7 +10,7 @@ hf_oauth_expiration_minutes: 60
10
 
11
  Public GPU app package. Its intended topology is a publicly reachable app at https://tiga-rce-ald-tao-gpu.hf.space/ with the GPU Space source repository restricted; the standalone CPU Space remains private. Deployment status and build-specific qualification are recorded in the release receipt. The native Formula runner uses the matched NVIDIA CUDA simulator/library pair from Zorro909/driving_sim_rs at commit d8ab74c25df70b6a86ed101080e33d5fb785ea6f. The newer b90b8af source was checked, but its published library targets AMD HIP and its added five-wheel snowmobile support is outside this Formula-only package. CPU runs anonymously. GPU is explicitly selected and subject to available HF allowance; optional HF sign-in uses the official OAuth flow and needs no personal token entry.
12
 
13
- The selected track map previews tile layout and asphalt/dirt/ice directly from the selected file. Twenty-eight reconstructed editor-format tracks are included. Settings-only recipes and batches save in the visitor's browser. Formula steering/acceleration networks within the current worker limits are supported.
14
 
15
  Application-controlled inputs and intermediate files are temporary. Outputs are held in memory for at most five minutes and deleted sooner after verified download by default; a single-run visitor may explicitly keep the result until that five-minute deadline. This is not a provider-wide zero-data-retention guarantee. Read DATA_POLICY.md and TRACK_LIBRARY.md. Upstream code and license: https://github.com/Zorro909/driving_sim_rs. Open upstream PRs 19 and 21 are not included.
16
 
 
10
 
11
  Public GPU app package. Its intended topology is a publicly reachable app at https://tiga-rce-ald-tao-gpu.hf.space/ with the GPU Space source repository restricted; the standalone CPU Space remains private. Deployment status and build-specific qualification are recorded in the release receipt. The native Formula runner uses the matched NVIDIA CUDA simulator/library pair from Zorro909/driving_sim_rs at commit d8ab74c25df70b6a86ed101080e33d5fb785ea6f. The newer b90b8af source was checked, but its published library targets AMD HIP and its added five-wheel snowmobile support is outside this Formula-only package. CPU runs anonymously. GPU is explicitly selected and subject to available HF allowance; optional HF sign-in uses the official OAuth flow and needs no personal token entry.
12
 
13
+ The selected track map previews tile layout and asphalt/dirt/ice directly from the selected file. Twenty-eight reconstructed editor-format tracks are included. Settings-only recipes and batches save in the visitor's browser. Formula networks may use any ordered unique subset of the five game controls. Supported networks have at most 20 inputs (13 raycasts plus 7 other sensors), 12 total layers, 16 neurons per hidden layer, and 2,869 dense weights and biases; the largest vanilla shape is `[20]+[16]*10+[5]`. All 13 game sensor types are supported with game defaults for omitted parameters. These limits describe the vanilla Formula worker contract, not unrestricted modified-game networks.
14
 
15
  Application-controlled inputs and intermediate files are temporary. Outputs are held in memory for at most five minutes and deleted sooner after verified download by default; a single-run visitor may explicitly keep the result until that five-minute deadline. This is not a provider-wide zero-data-retention guarantee. Read DATA_POLICY.md and TRACK_LIBRARY.md. Upstream code and license: https://github.com/Zorro909/driving_sim_rs. Open upstream PRs 19 and 21 are not included.
16
 
RELEASE_MANIFEST.json CHANGED
@@ -2,16 +2,16 @@
2
  "backend": "cpu+zerogpu",
3
  "files": {
4
  ".gitattributes": "c2ad16718fc51c888d32efc88c8f6e23ebe5fcf6f48e2954f896ba9770f24d72",
5
- "BETA_TESTING.md": "c2d4bc2eff0f87144a862282f20585868c95533103221f5d42726242b2eeba21",
6
  "BINARY_PROVENANCE.json": "1116e1757afc6c8629cb2d957e3d77380fe4f7e3186c3832d16a5a08abce6aa8",
7
- "CUDA_REFERENCE.md": "56a07521fc3d6acd11e3b3c110293d1a5ff710d1b7d7b93115e7b98d5ee7c256",
8
  "DATA_POLICY.md": "38da17f8528ede1ef1850e98832f75ba35ebdc86a0cde87e27674eb0ff9c7a7f",
9
  "FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
10
- "FRONTEND_SOURCE.json": "4a6b79b38053a0db37eb36b6e7209a14ad7f9f505c373003b89f950fd0ff4654",
11
  "GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
12
  "IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
13
  "LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
14
- "README.md": "b41c614f81339e6392a018ba7133211a42e0416eb03544a6d54ac34efc7daa81",
15
  "STORAGE_BOUNDARY.md": "9a1ffb5854230690dba3f0895f9a8e570716de47be2c553de13a6892dd740015",
16
  "TRACK_LIBRARY.md": "66b3656ed84f137d5b6116a0075731cd467818f555cca0969084aff67875319f",
17
  "UPSTREAM.json": "40a2f31fa9abc11e47e247c56909b93b587d1f5be524ab158b24f1f9eed93765",
@@ -58,7 +58,7 @@
58
  "gpu_tickets.py": "b81aadb5ed42cec9212d44da7e7bbb7b413c5bd517383cac0b49ec9b4d659c92",
59
  "hf-auth.js": "d5cf17b961d1f2efa9d4648d33ba86a148eff0d542c74a121710c4df35c92936",
60
  "hf_identity.py": "6993d0238ada835606dbdc3275c17a3fe5a5cac5ad2156ab78230acf2d051eba",
61
- "index.html": "aeb602565bf2da9d40cb6c3aae0b6e713b89e79a81557fed3e7f3dd8d16f195c",
62
  "requirements.txt": "9b532747e51bd052e82090ece860d6dafe027407f4512dc74272c148ea104c22",
63
  "server.py": "de98db424b96a4fbab7ce5b98ee42a94e846e2b3f317593e2346c5f87489ede8",
64
  "track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
@@ -70,7 +70,7 @@
70
  "vendor/hf-oauth/LICENSE": "d6e7b19451c1b3d1d66353bb4b1274138953b3bfd9ae60c56e77b1f548796982",
71
  "vendor/hf-oauth/index.js": "53282844000a171011e547f6472a3846bb4cecb5071adafea079fcac3a2aee7e",
72
  "vendor/hf-oauth/package.json": "fafd4d8891023a1961546a7d3bc25cd24f64923a2ebfc449fe6d516a2949bda8",
73
- "worker.py": "b30e12135f27f870a5094d98c19039262fcb5498f7582bdf4dce89cf11db520e"
74
  },
75
  "interface": "single-engine-runner-with-map",
76
  "upstream_revision": "d8ab74c25df70b6a86ed101080e33d5fb785ea6f"
 
2
  "backend": "cpu+zerogpu",
3
  "files": {
4
  ".gitattributes": "c2ad16718fc51c888d32efc88c8f6e23ebe5fcf6f48e2954f896ba9770f24d72",
5
+ "BETA_TESTING.md": "840192b5dec11ab903b05b358d14bd266efd3aaceedcf6d2794f7dc874b9caeb",
6
  "BINARY_PROVENANCE.json": "1116e1757afc6c8629cb2d957e3d77380fe4f7e3186c3832d16a5a08abce6aa8",
7
+ "CUDA_REFERENCE.md": "ecee71c9a509702011026ee62bb60ba4e41dac868d6710bcc8c49c8995a2ffeb",
8
  "DATA_POLICY.md": "38da17f8528ede1ef1850e98832f75ba35ebdc86a0cde87e27674eb0ff9c7a7f",
9
  "FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
10
+ "FRONTEND_SOURCE.json": "0248c308a8e49a21205916ee67f115c9e2b7fd02d70c2bdb7ec9cb1e981a1d76",
11
  "GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
12
  "IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
13
  "LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
14
+ "README.md": "164f805743bb6338d469c741d49dab3deac2862ea909481110e8faa358e4f798",
15
  "STORAGE_BOUNDARY.md": "9a1ffb5854230690dba3f0895f9a8e570716de47be2c553de13a6892dd740015",
16
  "TRACK_LIBRARY.md": "66b3656ed84f137d5b6116a0075731cd467818f555cca0969084aff67875319f",
17
  "UPSTREAM.json": "40a2f31fa9abc11e47e247c56909b93b587d1f5be524ab158b24f1f9eed93765",
 
58
  "gpu_tickets.py": "b81aadb5ed42cec9212d44da7e7bbb7b413c5bd517383cac0b49ec9b4d659c92",
59
  "hf-auth.js": "d5cf17b961d1f2efa9d4648d33ba86a148eff0d542c74a121710c4df35c92936",
60
  "hf_identity.py": "6993d0238ada835606dbdc3275c17a3fe5a5cac5ad2156ab78230acf2d051eba",
61
+ "index.html": "7b1b7d41f6ff69353d6101658d6baab4c50c30442e6b3627a9739f93a00b1a2e",
62
  "requirements.txt": "9b532747e51bd052e82090ece860d6dafe027407f4512dc74272c148ea104c22",
63
  "server.py": "de98db424b96a4fbab7ce5b98ee42a94e846e2b3f317593e2346c5f87489ede8",
64
  "track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
 
70
  "vendor/hf-oauth/LICENSE": "d6e7b19451c1b3d1d66353bb4b1274138953b3bfd9ae60c56e77b1f548796982",
71
  "vendor/hf-oauth/index.js": "53282844000a171011e547f6472a3846bb4cecb5071adafea079fcac3a2aee7e",
72
  "vendor/hf-oauth/package.json": "fafd4d8891023a1961546a7d3bc25cd24f64923a2ebfc449fe6d516a2949bda8",
73
+ "worker.py": "1bc381d807ef86d9de20616095e3cd08e5f9cb20e1712e84b9b87e8d35c9a6ef"
74
  },
75
  "interface": "single-engine-runner-with-map",
76
  "upstream_revision": "d8ab74c25df70b6a86ed101080e33d5fb785ea6f"
index.html CHANGED
@@ -1,7 +1,7 @@
1
  <!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>AI Learns To Drive</title>
2
  <style>
3
  :root{color-scheme:dark;font:16px system-ui;background:#0c1420;color:#e5edf5}body{max-width:1400px;margin:auto;padding:22px}h1{font-size:32px;margin:0}h2{font-size:21px;margin:0 0 14px}small,.hint{color:#a2b4c8;line-height:1.6}.grid{display:grid;grid-template-columns:minmax(360px,.9fr) minmax(0,1.25fr);gap:24px}.card{background:#152233;border:1px solid #2a3d52;border-radius:16px;padding:22px;margin-top:20px}label{display:block;margin:12px 0 5px}input,select,button{font:inherit;background:#0c1725;color:#e5edf5;border:1px solid #49627c;border-radius:7px;padding:9px}input[type=number],select{width:100%;box-sizing:border-box}input[type=file]{max-width:100%;box-sizing:border-box}button{cursor:pointer}button.primary{background:#62dfb1;color:#102522;font-weight:700}button:disabled{opacity:.4;cursor:default}.row{display:grid;grid-template-columns:1fr 1fr;gap:15px}.reward{display:grid;grid-template-columns:1.7fr 1fr 70px 30px;gap:5px;margin:8px 0}.reward input{width:70px;box-sizing:border-box}a{color:#7ce4c3}canvas{width:100%;background:#09111b;border-radius:10px}#status{white-space:pre-wrap;line-height:1.7}table{width:100%;text-align:left;border-collapse:collapse;font-size:14px}td,th{padding:9px;border-bottom:1px solid #2a3d52}.pill{display:inline-block;background:#284539;border-radius:20px;padding:5px 12px;color:#91efc6;margin:6px 0}#error{color:#ffb5a2;white-space:pre-wrap}.toolbar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;padding-bottom:16px;border-bottom:1px solid #2a3d52;margin-bottom:15px}.run-card{position:sticky;top:20px}details{margin-top:18px;border-top:1px solid #2a3d52;padding-top:14px}summary{cursor:pointer;font-weight:600;color:#c6d9e9}details[open]>summary{margin-bottom:12px}[hidden]{display:none!important}#map_note{font-size:14px}#runs{max-height:380px;overflow:auto}.table-scroll{overflow:auto}@media(max-width:850px){.grid{grid-template-columns:minmax(0,1fr)}.card{min-width:0}.run-card{position:static}body{padding:16px}}
4
- .tabs{display:flex;flex-wrap:wrap;gap:10px;margin:20px 0}.tabs button[aria-selected=true]{background:#62dfb1;color:#102522}.library{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px}.library input[type=text]{width:100%;box-sizing:border-box}.queue-row{display:flex;gap:8px;margin:8px 0}.queue-row select{flex:1}.pane{min-width:0}.map{min-height:300px;background:#09111b;border-radius:12px;padding:10px}.map canvas{display:block;width:100%;height:auto;max-height:320px;object-fit:contain}.map-legend{display:flex;gap:18px;flex-wrap:wrap;margin:10px 0;color:#c6d9e9;font-size:14px}pre{white-space:pre-wrap;overflow-wrap:anywhere}input[type=text]{width:100%;box-sizing:border-box}#libraries .row label>input,#libraries .row label>select{display:block;margin-top:6px}#editor_parameters input{max-width:240px} .eyebrow{color:#62dfb1;font-size:12px;letter-spacing:.18em;font-weight:800}.intro{font-size:19px;color:#b9cddd;max-width:820px;margin:10px 0 0}header{padding:12px 0 4px}header h1{font-size:clamp(30px,4vw,45px);letter-spacing:-.03em;margin-top:7px}.about{max-width:850px;font-size:14px;margin-top:15px}.about p{line-height:1.65}.card{box-shadow:0 10px 35px #050a1280}.tabs{position:sticky;top:0;z-index:3;background:#0c1420eF;padding:12px 0;border-bottom:1px solid #2a3d52}.tabs button{border-radius:12px;padding:11px 15px}.map-status{margin:7px 0;color:#dbe9f1;font-size:14px}#run{background:#62dfb1;color:#102522;font-weight:800;width:100%;margin-top:12px;padding:13px}.privacy-note{font-size:13px;line-height:1.5;color:#a2b4c8}.keep-result{display:flex;gap:10px;align-items:flex-start;padding:12px;border:1px solid #3b566b;border-radius:10px;background:#102030;line-height:1.4}.keep-result input{margin:3px 0 0;flex:none} .terrain{display:inline-block;width:11px;height:11px;border-radius:3px;margin-right:4px;background:#607e96}.terrain.dirt{background:#b89561}.terrain.ice{background:#b9d9ed}.terrain.start{background:#66e5aa}@media(max-width:850px){.map{min-height:0}}dialog#appConfirm{box-sizing:border-box;width:min(92vw,440px);border:1px solid #54718d;border-radius:16px;padding:24px;background:#152233;color:#e5edf5;box-shadow:0 24px 80px #000b}dialog#appConfirm::backdrop{background:#02060bcc}#appConfirm h2{margin:0 0 10px}#appConfirm p{line-height:1.55;color:#c4d3e1;white-space:pre-wrap}#appConfirm .actions{display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;margin-top:22px}#appConfirm button{min-width:100px}#appConfirm button.danger{background:#8e3b37;color:#fff;border-color:#d7837e}</style>
5
  <dialog id="appConfirm" aria-labelledby="appConfirmTitle" aria-describedby="appConfirmMessage">
6
  <h2 id="appConfirmTitle">Confirm action</h2>
7
  <p id="appConfirmMessage"></p>
@@ -29,7 +29,7 @@
29
  })();
30
  </script>
31
  <header><div class="eyebrow">AI LEARNS TO DRIVE · TRAINING LAB</div><h1>Train, inspect, repeat.</h1><p class="intro">Pick a track and network, run a bounded training session, and keep your recipes in this browser.</p><details class="about"><summary>About this beta and its data handling</summary><p>Simulator by <a href="https://github.com/Zorro909/driving_sim_rs">Zorro909</a>; interface and hosting by <a href="https://github.com/seeker-cyber-maker/driving_sim_rs">TiGa</a>. Inspired by the <a href="https://store.steampowered.com/app/3312030/AI_Learns_To_Drive/">game by ActivationFunction</a>. Built-in tracks are reconstructed replicas. Star challenges are not evaluated here.</p><p>Uploaded files are used for the requested run. Results are held briefly in application memory and deleted after verified download or expiry. Browser recipes contain settings only. <a href="/policy" target="_blank" rel="noopener">Read the data policy</a>.</p></details><p><a id="feedbackLink" href="https://github.com/seeker-cyber-maker/driving_sim_rs/issues/new" target="_blank" rel="noopener">Report a problem on GitHub</a> · Opens an editable draft and never submits it. Review details before posting; export a run receipt separately if it helps explain the issue.</p><p id="buildLabel" class="hint">Build: connecting…</p></header>
32
- <nav class="tabs" aria-label="Lab sections"><button data-tab="drive" aria-selected="true">1 · Drive & view</button><button data-tab="training" aria-selected="false">2 · Training runs & presets</button><button data-tab="batch" aria-selected="false">3 · Batches</button><button data-tab="editor" aria-selected="false">4 · Network editor</button></nav><div id="drive" class="pane"><div class="grid"><section class="card"><h2>Simulation</h2><button id="run" disabled>Run training</button><p class="hint">Select or create a network below to enable training. Review settings on tab 2 before running.</p><p>Supports Formula steering/acceleration, speed/raycast sensors, and 2–4 layers of up to 16 nodes.</p>
33
  <p id="computeBlock"><label for="backend">Compute</label><select id="backend" disabled><option value="cpu">CPU — no GPU allowance</option><option id="gpuOption" value="gpu" disabled>GPU — uses GPU allowance</option></select></p><p id="backendNote" class="hint">Checking available backends…</p>
34
  <div id="connectionRecovery" hidden><button id="retry" type="button">Reconnect to app</button><p class="hint">Checks server availability. Does not restart training, sign you in, or reset GPU allowance.</p></div>
35
  <div id="gpuAccount" aria-label="Optional GPU account"><p><button id="hfSignIn" type="button" disabled>Sign in with HF for account GPU allowance</button><span id="hfAccount" hidden></span> <button id="hfSignOut" type="button" disabled hidden>Disconnect account</button></p><p id="hfIdentity" role="status" class="hint">CPU needs no sign-in. Checking optional GPU account connection…</p>
@@ -75,7 +75,7 @@
75
  <p>Recipes are specific to this browser and site. Clearing site data removes them. Export a backup to move them between the local runner and HF. Browser privacy settings may prevent saving.</p>
76
  <details><summary>Advanced recipe JSON</summary><textarea id="recipe_draft" rows="12" style="width:100%" aria-label="Recipe settings JSON"></textarea><button id="recipe_apply_draft">Apply JSON to form</button></details></div></section>
77
  <section id="batch" class="pane" hidden><div class="card"><h2>Training batches · A–J</h2><p>Choose files and CPU/GPU on Drive & view, then start a saved batch here. Up to 20 runs and 4,000,000 vehicle-ticks in total. Recipes freeze at start. Each server result is automatically deleted after verified receipt. The last candidate is kept in browser memory. Stop prevents the next run; the current run finishes.</p><label for="batch_slot">Batch slot</label><select id="batch_slot" disabled><option>A</option><option>B</option><option>C</option><option>D</option><option>E</option><option>F</option><option>G</option><option>H</option><option>I</option><option>J</option></select><label for="batch_name">Display name</label><input id="batch_name" type="text" maxlength="80" value="My curriculum" disabled><label><input id="carry" type="checkbox" checked disabled> Continue from the previous run’s candidate</label><p class="hint">Unchecked: each run starts from the original selected network. Repeated run references are allowed. Recipes freeze when the batch starts.</p><label for="batch_recipe_sequence">Recipe sequence (comma-separated slots)</label><input id="batch_recipe_sequence" value="A" maxlength="300"><label for="batch_add_recipe">Add recipe<select id="batch_add_recipe"><option>A</option><option>B</option><option>C</option><option>D</option><option>E</option><option>F</option><option>G</option><option>H</option><option>I</option><option>J</option></select></label><button id="queue_add" disabled>+ Add run</button><div class="toolbar"><button id="batch_save" disabled>Save batch</button><button id="batch_load" disabled>Load batch</button><button class="primary" id="batch_start" disabled>Start saved batch</button><button id="batch_stop" disabled>Stop after current run</button><button id="batch_export" disabled>Export batch receipt</button></div><div id="batch_status" role="status">No batch selected.</div><div id="batch_history"></div><div id="batches_feedback" role="status" aria-live="polite"></div></div></section>
78
- <section id="editor" class="pane" hidden><div class="card"><h2>Network editor</h2><p class="hint">An original editor inspired by <a href="https://the-mars-guy.github.io/AI-Learns-To-Drive-Tool/" target="_blank" rel="noopener">Mars’s network creator</a>. Fresh networks use one speed input and five raycasts (−60°, −30°, 0°, 30°, 60°; length 800), with steering and acceleration outputs. This is an editor starter configuration, not a claim of identical game presets. Larger or other-control networks can be inspected, but the current worker accepts only the displayed supported contract.</p><div class="toolbar"><label for="editor_file">Load an existing .evolution file</label><input id="editor_file" type="file" accept=".evolution"><button id="editor_import" disabled>Inspect selected network</button><button id="editor_create" disabled>Create fresh network</button><button id="editor_apply" disabled>Use edited network</button><a id="editor_download" hidden download>Download .evolution</a></div><div class="row"><div><label for="editor_name">Network name</label><input id="editor_name" type="text" maxlength="80" value="Fresh network" disabled><label for="editor_shape">Layer sizes for fresh creation</label><input id="editor_shape" type="text" value="6,4,2" disabled><small>Fresh: 6 inputs → up to two hidden layers of 1–16 nodes → 2 outputs. Loaded networks may be inspected up to 20 layers / 100,000 parameters.</small></div><div><label for="editor_seed">Initialization seed</label><input id="editor_seed" type="number" min="0" max="2147483647" value="42105" disabled><label><input id="editor_xavier" type="checkbox" checked disabled> Seeded Xavier uniform weights (browser algorithm)</label><label><input id="editor_bias" type="checkbox" disabled> Xavier uniform biases</label><small>Unchecked: zeros. Biases default to zero.</small></div></div><p id="editor_status" role="status">Choose a file or create a fresh network. Editor changes are browser-local until you explicitly start a run.</p><p id="editor_metadata" class="hint"></p><p id="editor_legality" class="hint"></p><details id="editor_history_panel"><summary>Training history · recorded generations</summary><div class="table-scroll"><table><thead><tr><th>Record</th><th>Time</th><th>Mutation</th><th>Best score</th><th>Best lap</th></tr></thead><tbody id="editor_history"></tbody></table></div><details><summary>Full saved training metadata</summary><pre id="editor_stats"></pre></details></details><canvas aria-label="Network architecture preview" id="network_map" width="1100" height="450"></canvas><details id="weight_values"><summary>Individual weight and bias values</summary><div class="table-scroll" style="max-height:420px;overflow:auto"><table><thead><tr><th>Connection / bias</th><th>Value</th></tr></thead><tbody id="editor_parameters"></tbody></table></div></details><details><summary>Weights, biases and input contract</summary><pre id="editor_contract"></pre><label for="editor_json">NeuralNetwork JSON · editable weights and biases</label><textarea id="editor_json" rows="16" style="width:100%;box-sizing:border-box;background:#0c1725;color:#e5edf5" disabled></textarea></details></div></section><script>/* Settings-only recipe contract. Storage is injected; no network or job data. */
79
  (function(root){
80
  const KEY='tiga.ald.recipes.v1';
81
  const defaults={population:4,generations:1,ticks:60,mutation:0.01,seed:42,
@@ -167,8 +167,11 @@
167
  })(typeof window!=='undefined'?window:globalThis);
168
  </script><script>/* Pure browser/Node structural checks and deterministic fresh initialization. */
169
  (function(root){
 
 
170
  function neural(value){
171
- if(!value||!Array.isArray(value.Shape)||value.Shape.length<2||value.Shape.length>20||value.Shape.some(n=>!Number.isInteger(n)||n<1||n>64))throw Error('Shape: 2–20 layers of 1–64 nodes');
 
172
  const {Shape:shape,Weights:weights,Biases:biases}=value;
173
  if(!Array.isArray(weights)||!Array.isArray(biases)||weights.length!==shape.length-1||biases.length!==weights.length)throw Error('Invalid layer counts');
174
  let count=0;
@@ -177,43 +180,72 @@
177
  for(const row of matrix){if(!Array.isArray(row)||row.length!==shape[i+1]||row.some(n=>typeof n!=='number'||!Number.isFinite(n)||Math.abs(n)>1e6))throw Error('Invalid weights');count+=row.length;}
178
  if(biases[i].some(n=>typeof n!=='number'||!Number.isFinite(n)||Math.abs(n)>1e6))throw Error('Invalid biases');count+=biases[i].length;
179
  });
180
- if(count>100000)throw Error('Editor limit: 100,000 parameters');
181
  return {Shape:[...shape],Weights:weights.map(m=>m.map(r=>[...r])),Biases:biases.map(r=>[...r])};
182
  }
 
 
 
183
  function compatible(value){
184
- const n=neural(value.NeuralNetwork),s=value.Sensors;
185
- if(value.Type!==0||JSON.stringify(value.Outputs)!=='[0,1]')throw Error('Worker supports Formula steering and acceleration only');
186
- if(n.Shape.length>4||n.Shape.some(x=>x>16)||n.Shape.at(-1)!==2)throw Error('Worker supports 2–4 layers, up to 16 nodes, ending in 2 outputs');
187
  if(!Array.isArray(s)||s.length!==n.Shape[0])throw Error('Sensor count does not match input shape');
 
188
  for(const x of s){
189
- if(!x||typeof x!=='object')throw Error('Invalid sensor');
190
- if(x.$type==='speed')continue;
191
- if(x.$type!=='raycast'||typeof x.Degrees!=='number'||!Number.isFinite(x.Degrees)||Math.abs(x.Degrees)>360||typeof x.Length!=='number'||!Number.isFinite(x.Length)||x.Length<1||x.Length>10000)throw Error('Worker supports speed and bounded raycast sensors only');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
 
193
  return true;
194
  }
195
  function edited(source,n,name){
196
  const network=neural(n),shape=network.Shape;
197
- if(!Array.isArray(source.Sensors)||source.Sensors.length!==shape[0]||!Array.isArray(source.Outputs)||source.Outputs.length!==shape.at(-1))throw Error('Editing must preserve the sensor/output contract');
198
  if(typeof name!=='string'||!name.trim()||name.length>80)throw Error('Network name required (80 characters maximum)');
199
- // Derived artifact only: never carry historical scores onto changed weights.
200
  return {Name:name.trim(),Type:source.Type,Outputs:[...source.Outputs],Sensors:JSON.parse(JSON.stringify(source.Sensors)),NeuralNetwork:network};
201
  }
202
- function fresh({shape,seed,xavier=true,bias=false,name='Fresh network'}){
203
- if(!Array.isArray(shape)||shape.length<2||shape.length>4||shape[0]!==6||shape.at(-1)!==2||shape.some(n=>!Number.isInteger(n)||n<1||n>16))throw Error('Fresh shape: 6 inputs, 2 outputs, 2–4 layers, at most 16 nodes');
 
 
204
  if(!Number.isInteger(seed)||seed<0||seed>2147483647)throw Error('Seed must be 0–2147483647');
205
  if(typeof xavier!=='boolean'||typeof bias!=='boolean')throw Error('Invalid initialization switches');
206
  let state=seed>>>0;
207
  const uniform=()=>{state=(state+0x6D2B79F5)>>>0;let x=state;x=Math.imul(x^(x>>>15),x|1);x^=x+Math.imul(x^(x>>>7),x|61);return ((x^(x>>>14))>>>0)/4294967296;};
208
- const n={Shape:shape,Weights:[],Biases:[]};
209
- for(let i=0;i<shape.length-1;i++){
210
- const a=shape[i],b=shape[i+1],bound=Math.sqrt(6/(a+b)),draw=()=> (2*uniform()-1)*bound;
211
  n.Weights.push(Array.from({length:a},()=>Array.from({length:b},()=>xavier?draw():0)));
212
  n.Biases.push(Array.from({length:b},()=>bias?draw():0));
213
  }
214
- return edited({Type:0,Outputs:[0,1],Sensors:[{$type:'speed'},...[-60,-30,0,30,60].map(Degrees=>({$type:'raycast',Degrees,Length:800}))]},n,name);
 
 
 
215
  }
216
- const api={neural,compatible,edited,fresh};
217
  if(typeof module!=='undefined'&&module.exports)module.exports=api;else root.NetworkEditor=api;
218
  })(typeof window!=='undefined'?window:globalThis);
219
  </script><script>(() => {
@@ -417,7 +449,13 @@ document.querySelectorAll('[data-tab]').forEach(button=>button.addEventListener(
417
  const q=id=>document.getElementById(id),api=window.NetworkEditor;
418
  const status=text=>q('editor_status').textContent=text;
419
  let source=null,exportURL=null;
420
- for(const id of ['editor_name','editor_shape','editor_seed','editor_xavier','editor_bias','editor_create','editor_import'])q(id).disabled=false;
 
 
 
 
 
 
421
  window.setEditorBusy=busy=>{q('editor_apply').disabled=busy||!source;};
422
  function graph(n){
423
  const canvas=q('network_map'),ctx=canvas.getContext('2d');ctx.clearRect(0,0,canvas.width,canvas.height);
@@ -432,7 +470,7 @@ document.querySelectorAll('[data-tab]').forEach(button=>button.addEventListener(
432
  const n=api.neural(value.NeuralNetwork);source=value;
433
  const count=n.Weights.reduce((sum,m)=>sum+m.reduce((s,r)=>s+r.length,0),0)+n.Biases.reduce((s,r)=>s+r.length,0);
434
  q('editor_json').value=JSON.stringify(n,null,2);q('editor_json').disabled=false;
435
- q('editor_name').value=value.Name||'Edited network';q('editor_shape').value=n.Shape.join(',');
436
  q('editor_metadata').textContent=n.Shape.join(' → ')+' · '+count+' parameters. Structure checked; no game/leaderboard validation claimed.';
437
  q('editor_contract').textContent=JSON.stringify({sensors:value.Sensors,outputs:value.Outputs,vehicle:value.Type},null,2);
438
  try{api.compatible(value);q('editor_legality').textContent='Supported by the current training worker.';}
@@ -485,7 +523,7 @@ document.querySelectorAll('[data-tab]').forEach(button=>button.addEventListener(
485
  return {value,file:new File([blob],'edited-network.evolution',{type:'application/octet-stream'})};
486
  }
487
  q('editor_file').onchange=load;q('editor_import').onclick=load;
488
- q('editor_create').onclick=()=>{try{show(api.fresh({shape:q('editor_shape').value.split(',').map(Number),seed:Number(q('editor_seed').value),xavier:q('editor_xavier').checked,bias:q('editor_bias').checked,name:q('editor_name').value}));status('Fresh browser-seeded network created. No training history, upload or storage.');}catch(error){status('Not created: '+error.message);}};
489
  q('editor_apply').onclick=async()=>{try{const {value,file}=await artifact();api.compatible(value);window.TrainingClient.useNetwork(file);show(value);status('Edited network selected for the next run. No upload yet.');}catch(error){status('Not applied: '+error.message);}};
490
  q('editor_download').onclick=async event=>{
491
  event.preventDefault();try{const {file}=await artifact();if(exportURL)URL.revokeObjectURL(exportURL);exportURL=URL.createObjectURL(file);const a=document.createElement('a');a.href=exportURL;a.download=file.name;a.click();status('Export requested. This is a new edited artifact with no inherited training scores.');}catch(error){status('Not exported: '+error.message);}
 
1
  <!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>AI Learns To Drive</title>
2
  <style>
3
  :root{color-scheme:dark;font:16px system-ui;background:#0c1420;color:#e5edf5}body{max-width:1400px;margin:auto;padding:22px}h1{font-size:32px;margin:0}h2{font-size:21px;margin:0 0 14px}small,.hint{color:#a2b4c8;line-height:1.6}.grid{display:grid;grid-template-columns:minmax(360px,.9fr) minmax(0,1.25fr);gap:24px}.card{background:#152233;border:1px solid #2a3d52;border-radius:16px;padding:22px;margin-top:20px}label{display:block;margin:12px 0 5px}input,select,button{font:inherit;background:#0c1725;color:#e5edf5;border:1px solid #49627c;border-radius:7px;padding:9px}input[type=number],select{width:100%;box-sizing:border-box}input[type=file]{max-width:100%;box-sizing:border-box}button{cursor:pointer}button.primary{background:#62dfb1;color:#102522;font-weight:700}button:disabled{opacity:.4;cursor:default}.row{display:grid;grid-template-columns:1fr 1fr;gap:15px}.reward{display:grid;grid-template-columns:1.7fr 1fr 70px 30px;gap:5px;margin:8px 0}.reward input{width:70px;box-sizing:border-box}a{color:#7ce4c3}canvas{width:100%;background:#09111b;border-radius:10px}#status{white-space:pre-wrap;line-height:1.7}table{width:100%;text-align:left;border-collapse:collapse;font-size:14px}td,th{padding:9px;border-bottom:1px solid #2a3d52}.pill{display:inline-block;background:#284539;border-radius:20px;padding:5px 12px;color:#91efc6;margin:6px 0}#error{color:#ffb5a2;white-space:pre-wrap}.toolbar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;padding-bottom:16px;border-bottom:1px solid #2a3d52;margin-bottom:15px}.run-card{position:sticky;top:20px}details{margin-top:18px;border-top:1px solid #2a3d52;padding-top:14px}summary{cursor:pointer;font-weight:600;color:#c6d9e9}details[open]>summary{margin-bottom:12px}[hidden]{display:none!important}#map_note{font-size:14px}#runs{max-height:380px;overflow:auto}.table-scroll{overflow:auto}@media(max-width:850px){.grid{grid-template-columns:minmax(0,1fr)}.card{min-width:0}.run-card{position:static}body{padding:16px}}
4
+ .tabs{display:flex;flex-wrap:wrap;gap:10px;margin:20px 0}.tabs button[aria-selected=true]{background:#62dfb1;color:#102522}.library{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px}.library input[type=text]{width:100%;box-sizing:border-box}.queue-row{display:flex;gap:8px;margin:8px 0}.queue-row select{flex:1}.pane{min-width:0}.map{min-height:300px;background:#09111b;border-radius:12px;padding:10px}.map canvas{display:block;width:100%;height:auto;max-height:320px;object-fit:contain}.map-legend{display:flex;gap:18px;flex-wrap:wrap;margin:10px 0;color:#c6d9e9;font-size:14px}pre{white-space:pre-wrap;overflow-wrap:anywhere}input[type=text]{width:100%;box-sizing:border-box}#libraries .row label>input,#libraries .row label>select{display:block;margin-top:6px}#editor_parameters input{max-width:240px} .eyebrow{color:#62dfb1;font-size:12px;letter-spacing:.18em;font-weight:800}.intro{font-size:19px;color:#b9cddd;max-width:820px;margin:10px 0 0}header{padding:12px 0 4px}header h1{font-size:clamp(30px,4vw,45px);letter-spacing:-.03em;margin-top:7px}.about{max-width:850px;font-size:14px;margin-top:15px}.about p{line-height:1.65}.card{box-shadow:0 10px 35px #050a1280}.tabs{position:sticky;top:0;z-index:3;background:#0c1420eF;padding:12px 0;border-bottom:1px solid #2a3d52}.tabs button{border-radius:12px;padding:11px 15px}.map-status{margin:7px 0;color:#dbe9f1;font-size:14px}#run{background:#62dfb1;color:#102522;font-weight:800;width:100%;margin-top:12px;padding:13px}.privacy-note{font-size:13px;line-height:1.5;color:#a2b4c8}.keep-result{display:flex;gap:10px;align-items:flex-start;padding:12px;border:1px solid #3b566b;border-radius:10px;background:#102030;line-height:1.4}.keep-result input{margin:3px 0 0;flex:none} .terrain{display:inline-block;width:11px;height:11px;border-radius:3px;margin-right:4px;background:#607e96}.terrain.dirt{background:#b89561}.terrain.ice{background:#b9d9ed}.terrain.start{background:#66e5aa}.output-options{display:flex;flex-wrap:wrap;gap:5px 14px}.output-options label{display:flex;align-items:center;gap:5px;margin:5px 0}.output-options input{margin:0}#editor_outputs{margin:12px 0;padding:10px 12px;border:1px solid #49627c;border-radius:8px}#editor_outputs legend{padding:0 5px;color:#c6d9e9}@media(max-width:850px){.map{min-height:0}}dialog#appConfirm{box-sizing:border-box;width:min(92vw,440px);border:1px solid #54718d;border-radius:16px;padding:24px;background:#152233;color:#e5edf5;box-shadow:0 24px 80px #000b}dialog#appConfirm::backdrop{background:#02060bcc}#appConfirm h2{margin:0 0 10px}#appConfirm p{line-height:1.55;color:#c4d3e1;white-space:pre-wrap}#appConfirm .actions{display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;margin-top:22px}#appConfirm button{min-width:100px}#appConfirm button.danger{background:#8e3b37;color:#fff;border-color:#d7837e}</style>
5
  <dialog id="appConfirm" aria-labelledby="appConfirmTitle" aria-describedby="appConfirmMessage">
6
  <h2 id="appConfirmTitle">Confirm action</h2>
7
  <p id="appConfirmMessage"></p>
 
29
  })();
30
  </script>
31
  <header><div class="eyebrow">AI LEARNS TO DRIVE · TRAINING LAB</div><h1>Train, inspect, repeat.</h1><p class="intro">Pick a track and network, run a bounded training session, and keep your recipes in this browser.</p><details class="about"><summary>About this beta and its data handling</summary><p>Simulator by <a href="https://github.com/Zorro909/driving_sim_rs">Zorro909</a>; interface and hosting by <a href="https://github.com/seeker-cyber-maker/driving_sim_rs">TiGa</a>. Inspired by the <a href="https://store.steampowered.com/app/3312030/AI_Learns_To_Drive/">game by ActivationFunction</a>. Built-in tracks are reconstructed replicas. Star challenges are not evaluated here.</p><p>Uploaded files are used for the requested run. Results are held briefly in application memory and deleted after verified download or expiry. Browser recipes contain settings only. <a href="/policy" target="_blank" rel="noopener">Read the data policy</a>.</p></details><p><a id="feedbackLink" href="https://github.com/seeker-cyber-maker/driving_sim_rs/issues/new" target="_blank" rel="noopener">Report a problem on GitHub</a> · Opens an editable draft and never submits it. Review details before posting; export a run receipt separately if it helps explain the issue.</p><p id="buildLabel" class="hint">Build: connecting…</p></header>
32
+ <nav class="tabs" aria-label="Lab sections"><button data-tab="drive" aria-selected="true">1 · Drive & view</button><button data-tab="training" aria-selected="false">2 · Training runs & presets</button><button data-tab="batch" aria-selected="false">3 · Batches</button><button data-tab="editor" aria-selected="false">4 · Network editor</button></nav><div id="drive" class="pane"><div class="grid"><section class="card"><h2>Simulation</h2><button id="run" disabled>Run training</button><p class="hint">Select or create a network below to enable training. Review settings on tab 2 before running.</p><p>Supports the native Formula controls: steering, acceleration, handbrake, brake and boost. Network shape and supported sensors are bounded by the original game contract.</p>
33
  <p id="computeBlock"><label for="backend">Compute</label><select id="backend" disabled><option value="cpu">CPU — no GPU allowance</option><option id="gpuOption" value="gpu" disabled>GPU — uses GPU allowance</option></select></p><p id="backendNote" class="hint">Checking available backends…</p>
34
  <div id="connectionRecovery" hidden><button id="retry" type="button">Reconnect to app</button><p class="hint">Checks server availability. Does not restart training, sign you in, or reset GPU allowance.</p></div>
35
  <div id="gpuAccount" aria-label="Optional GPU account"><p><button id="hfSignIn" type="button" disabled>Sign in with HF for account GPU allowance</button><span id="hfAccount" hidden></span> <button id="hfSignOut" type="button" disabled hidden>Disconnect account</button></p><p id="hfIdentity" role="status" class="hint">CPU needs no sign-in. Checking optional GPU account connection…</p>
 
75
  <p>Recipes are specific to this browser and site. Clearing site data removes them. Export a backup to move them between the local runner and HF. Browser privacy settings may prevent saving.</p>
76
  <details><summary>Advanced recipe JSON</summary><textarea id="recipe_draft" rows="12" style="width:100%" aria-label="Recipe settings JSON"></textarea><button id="recipe_apply_draft">Apply JSON to form</button></details></div></section>
77
  <section id="batch" class="pane" hidden><div class="card"><h2>Training batches · A–J</h2><p>Choose files and CPU/GPU on Drive & view, then start a saved batch here. Up to 20 runs and 4,000,000 vehicle-ticks in total. Recipes freeze at start. Each server result is automatically deleted after verified receipt. The last candidate is kept in browser memory. Stop prevents the next run; the current run finishes.</p><label for="batch_slot">Batch slot</label><select id="batch_slot" disabled><option>A</option><option>B</option><option>C</option><option>D</option><option>E</option><option>F</option><option>G</option><option>H</option><option>I</option><option>J</option></select><label for="batch_name">Display name</label><input id="batch_name" type="text" maxlength="80" value="My curriculum" disabled><label><input id="carry" type="checkbox" checked disabled> Continue from the previous run’s candidate</label><p class="hint">Unchecked: each run starts from the original selected network. Repeated run references are allowed. Recipes freeze when the batch starts.</p><label for="batch_recipe_sequence">Recipe sequence (comma-separated slots)</label><input id="batch_recipe_sequence" value="A" maxlength="300"><label for="batch_add_recipe">Add recipe<select id="batch_add_recipe"><option>A</option><option>B</option><option>C</option><option>D</option><option>E</option><option>F</option><option>G</option><option>H</option><option>I</option><option>J</option></select></label><button id="queue_add" disabled>+ Add run</button><div class="toolbar"><button id="batch_save" disabled>Save batch</button><button id="batch_load" disabled>Load batch</button><button class="primary" id="batch_start" disabled>Start saved batch</button><button id="batch_stop" disabled>Stop after current run</button><button id="batch_export" disabled>Export batch receipt</button></div><div id="batch_status" role="status">No batch selected.</div><div id="batch_history"></div><div id="batches_feedback" role="status" aria-live="polite"></div></div></section>
78
+ <section id="editor" class="pane" hidden><div class="card"><h2>Network editor</h2><p class="hint">An original editor inspired by <a href="https://the-mars-guy.github.io/AI-Learns-To-Drive-Tool/" target="_blank" rel="noopener">Mars’s network creator</a>. Create networks for any ordered subset of the five native controls, or use the largest original-game shape. Loaded networks keep their original output order and sensor parameters. Unsupported files can still be inspected without changing the original.</p><div class="toolbar"><label for="editor_file">Load an existing .evolution file</label><input id="editor_file" type="file" accept=".evolution"><button id="editor_import" disabled>Inspect selected network</button><button id="editor_create" disabled>Create fresh network</button><button id="editor_apply" disabled>Use edited network</button><a id="editor_download" hidden download>Download .evolution</a></div><div class="row"><div><label for="editor_name">Network name</label><input id="editor_name" type="text" maxlength="80" value="Fresh network" disabled><label for="editor_preset">Fresh network preset</label><select id="editor_preset" disabled><option value="small">Small starter · 6 inputs, 4 hidden nodes, 5 outputs</option><option value="maximum">Largest game network · 20 inputs, 10 × 16 hidden nodes, 5 outputs</option><option value="custom">Custom</option></select><label for="editor_shape">Input and hidden layer sizes</label><input id="editor_shape" type="text" value="6,4" disabled><small>Enter 1–20 inputs, followed by 0–10 hidden layers of 1–16 nodes. Output width follows the selected controls. Maximum shape: 20 → ten layers of 16 → 5 (2,869 parameters).</small><fieldset id="editor_outputs" disabled><legend>Fresh network outputs</legend><div class="output-options"><label><input id="editor_output_0" type="checkbox" value="0" checked disabled> Steering (0)</label><label><input id="editor_output_1" type="checkbox" value="1" checked disabled> Acceleration (1)</label><label><input id="editor_output_2" type="checkbox" value="2" checked disabled> Handbrake (2)</label><label><input id="editor_output_3" type="checkbox" value="3" checked disabled> Brake (3)</label><label><input id="editor_output_4" type="checkbox" value="4" checked disabled> Boost (4)</label></div><small>Choose at least one. Fresh output IDs follow this order. Loaded network output order is preserved when editing or exporting.</small></fieldset></div><div><label for="editor_seed">Initialization seed</label><input id="editor_seed" type="number" min="0" max="2147483647" value="42105" disabled><label><input id="editor_xavier" type="checkbox" checked disabled> Seeded Xavier uniform weights (browser algorithm)</label><label><input id="editor_bias" type="checkbox" disabled> Xavier uniform biases</label><small>Unchecked: zeros. Biases default to zero.</small></div></div><p id="editor_status" role="status">Choose a file or create a fresh network. Editor changes are browser-local until you explicitly start a run.</p><p id="editor_metadata" class="hint"></p><p id="editor_legality" class="hint"></p><details id="editor_history_panel"><summary>Training history · recorded generations</summary><div class="table-scroll"><table><thead><tr><th>Record</th><th>Time</th><th>Mutation</th><th>Best score</th><th>Best lap</th></tr></thead><tbody id="editor_history"></tbody></table></div><details><summary>Full saved training metadata</summary><pre id="editor_stats"></pre></details></details><canvas aria-label="Network architecture preview" id="network_map" width="1100" height="450"></canvas><details id="weight_values"><summary>Individual weight and bias values</summary><div class="table-scroll" style="max-height:420px;overflow:auto"><table><thead><tr><th>Connection / bias</th><th>Value</th></tr></thead><tbody id="editor_parameters"></tbody></table></div></details><details><summary>Weights, biases and input contract</summary><pre id="editor_contract"></pre><label for="editor_json">NeuralNetwork JSON · editable weights and biases</label><textarea id="editor_json" rows="16" style="width:100%;box-sizing:border-box;background:#0c1725;color:#e5edf5" disabled></textarea></details></div></section><script>/* Settings-only recipe contract. Storage is injected; no network or job data. */
79
  (function(root){
80
  const KEY='tiga.ald.recipes.v1';
81
  const defaults={population:4,generations:1,ticks:60,mutation:0.01,seed:42,
 
167
  })(typeof window!=='undefined'?window:globalThis);
168
  </script><script>/* Pure browser/Node structural checks and deterministic fresh initialization. */
169
  (function(root){
170
+ const scalarTypes=['speed','velocityFront','velocitySide','angularVelocity','wheelAngle','boostCapacity','correctDirection'];
171
+ const outputsValid=outputs=>Array.isArray(outputs)&&outputs.length>=1&&outputs.length<=5&&outputs.every(x=>Number.isInteger(x)&&x>=0&&x<=4)&&new Set(outputs).size===outputs.length;
172
  function neural(value){
173
+ if(!value||!Array.isArray(value.Shape)||value.Shape.length<2||value.Shape.length>12||value.Shape.some(n=>!Number.isInteger(n)||n<1||n>20))throw Error('Shape must contain 2–12 layers with 1–20 inputs');
174
+ if(value.Shape.slice(1,-1).some(n=>n>16))throw Error('Hidden layers allow at most 16 nodes');
175
  const {Shape:shape,Weights:weights,Biases:biases}=value;
176
  if(!Array.isArray(weights)||!Array.isArray(biases)||weights.length!==shape.length-1||biases.length!==weights.length)throw Error('Invalid layer counts');
177
  let count=0;
 
180
  for(const row of matrix){if(!Array.isArray(row)||row.length!==shape[i+1]||row.some(n=>typeof n!=='number'||!Number.isFinite(n)||Math.abs(n)>1e6))throw Error('Invalid weights');count+=row.length;}
181
  if(biases[i].some(n=>typeof n!=='number'||!Number.isFinite(n)||Math.abs(n)>1e6))throw Error('Invalid biases');count+=biases[i].length;
182
  });
183
+ if(count>2869)throw Error('Network exceeds the original-game maximum of 2,869 parameters');
184
  return {Shape:[...shape],Weights:weights.map(m=>m.map(r=>[...r])),Biases:biases.map(r=>[...r])};
185
  }
186
+ function parameter(value,name,{positive=false,float32=false}={}){
187
+ if(typeof value!=='number'||!Number.isFinite(value)||Math.abs(value)>1e6||(positive&&value<=0)||(float32&&Math.fround(value)===0))throw Error('Invalid '+name+' sensor parameter');
188
+ }
189
  function compatible(value){
190
+ const n=neural(value?.NeuralNetwork),s=value.Sensors;
191
+ if(value.Type!==0||!outputsValid(value.Outputs)||n.Shape.at(-1)!==value.Outputs.length)throw Error('Formula outputs must be a unique ordered subset of IDs 0–4 matching the output width');
192
+ if(n.Shape[0]>20||n.Shape.slice(1,-1).some(x=>x>16)||n.Shape.length>12)throw Error('Shape exceeds the original-game limits');
193
  if(!Array.isArray(s)||s.length!==n.Shape[0])throw Error('Sensor count does not match input shape');
194
+ let rays=0,other=0;
195
  for(const x of s){
196
+ if(!x||typeof x!=='object'||Array.isArray(x)||typeof x.$type!=='string')throw Error('Invalid sensor');
197
+ if(x.$type==='raycast'){
198
+ rays++;if(typeof x.Degrees!=='number'||!Number.isFinite(x.Degrees)||x.Degrees < -360||x.Degrees>360||typeof x.Length!=='number'||!Number.isFinite(x.Length)||x.Length<1||x.Length>10000)throw Error('Raycasts require degrees −360…360 and length 1…10,000');
199
+ }else{
200
+ other++;
201
+ if(scalarTypes.includes(x.$type))continue;
202
+ if(x.$type==='accelerationFront'||x.$type==='accelerationSide'){
203
+ const key='MaxAcceleration';if(x[key]!==undefined)parameter(x[key],key,{positive:true,float32:true});continue;
204
+ }
205
+ if(x.$type==='distanceFromWall'){
206
+ if(x.MaxDistance!==undefined)parameter(x.MaxDistance,'MaxDistance',{positive:true,float32:true});continue;
207
+ }
208
+ if(x.$type==='trackCurvature'){
209
+ for(const key of ['MinLookaheadDistance','MaxLookaheadDistance'])if(x[key]!==undefined)parameter(x[key],key);continue;
210
+ }
211
+ if(x.$type==='grip'){
212
+ const pos=x.PositionOffset;if(pos===undefined)continue;
213
+ if(Array.isArray(pos)){if(pos.length!==2)throw Error('Grip PositionOffset array must contain exactly two values');pos.forEach((v,i)=>parameter(v,'PositionOffset['+i+']'));}
214
+ else if(pos&&typeof pos==='object'&&!Array.isArray(pos)){for(const key of ['X','Y'])if(pos[key]!==undefined)parameter(pos[key],'PositionOffset.'+key);}
215
+ else throw Error('Grip PositionOffset must be a two-value array or coordinate object');continue;
216
+ }
217
+ throw Error('Unsupported sensor type: '+x.$type);
218
+ }
219
  }
220
+ if(rays>13||other>7)throw Error('Original game supports at most 13 raycasts and 7 other sensors');
221
  return true;
222
  }
223
  function edited(source,n,name){
224
  const network=neural(n),shape=network.Shape;
225
+ if(!Array.isArray(source.Sensors)||source.Sensors.length!==shape[0]||!outputsValid(source.Outputs)||source.Outputs.length!==shape.at(-1))throw Error('Editing must preserve the sensor/output contract');
226
  if(typeof name!=='string'||!name.trim()||name.length>80)throw Error('Network name required (80 characters maximum)');
 
227
  return {Name:name.trim(),Type:source.Type,Outputs:[...source.Outputs],Sensors:JSON.parse(JSON.stringify(source.Sensors)),NeuralNetwork:network};
228
  }
229
+ function fresh({shape,outputs=[0,1,2,3,4],seed,xavier=true,bias=false,name='Fresh network'}){
230
+ if(!Array.isArray(shape)||shape.length<1||shape.length>11||shape.some(n=>!Number.isInteger(n)||n<1)||shape[0]>20||shape.slice(1).some(n=>n>16))throw Error('Fresh shape: 1–20 inputs and 0–10 hidden layers of 1–16 nodes');
231
+ if(!outputsValid(outputs))throw Error('Choose one to five unique output IDs from 0–4');
232
+ const fullShape=[...shape,outputs.length];if(fullShape.length>12)throw Error('Fresh networks allow at most 12 layers');
233
  if(!Number.isInteger(seed)||seed<0||seed>2147483647)throw Error('Seed must be 0–2147483647');
234
  if(typeof xavier!=='boolean'||typeof bias!=='boolean')throw Error('Invalid initialization switches');
235
  let state=seed>>>0;
236
  const uniform=()=>{state=(state+0x6D2B79F5)>>>0;let x=state;x=Math.imul(x^(x>>>15),x|1);x^=x+Math.imul(x^(x>>>7),x|61);return ((x^(x>>>14))>>>0)/4294967296;};
237
+ const n={Shape:fullShape,Weights:[],Biases:[]};
238
+ for(let i=0;i<fullShape.length-1;i++){
239
+ const a=fullShape[i],b=fullShape[i+1],bound=Math.sqrt(6/(a+b)),draw=()=> (2*uniform()-1)*bound;
240
  n.Weights.push(Array.from({length:a},()=>Array.from({length:b},()=>xavier?draw():0)));
241
  n.Biases.push(Array.from({length:b},()=>bias?draw():0));
242
  }
243
+ const rays=Math.min(13,shape[0]-1),extra=shape[0]-1-rays;
244
+ const sensors=[{$type:'speed'},...scalarTypes.slice(1,1+extra).map($type=>({$type}))];
245
+ for(let i=0;i<rays;i++)sensors.push({$type:'raycast',Degrees:rays===1?0:-90+180*i/(rays-1),Length:800});
246
+ return edited({Type:0,Outputs:[...outputs],Sensors:sensors},n,name);
247
  }
248
+ const api={neural,compatible,edited,fresh,outputsValid};
249
  if(typeof module!=='undefined'&&module.exports)module.exports=api;else root.NetworkEditor=api;
250
  })(typeof window!=='undefined'?window:globalThis);
251
  </script><script>(() => {
 
449
  const q=id=>document.getElementById(id),api=window.NetworkEditor;
450
  const status=text=>q('editor_status').textContent=text;
451
  let source=null,exportURL=null;
452
+ for(const id of ['editor_name','editor_preset','editor_shape','editor_seed','editor_xavier','editor_bias','editor_create','editor_import','editor_outputs',...Array.from({length:5},(_,i)=>'editor_output_'+i)])q(id).disabled=false;
453
+ const outputIds=[0,1,2,3,4],outputName=id=>'editor_output_'+id;
454
+ function setOutputs(ids){for(const id of outputIds)q(outputName(id)).checked=ids.includes(id);}
455
+ function preset(value){if(value==='small'){q('editor_shape').value='6,4';setOutputs(outputIds);}else if(value==='maximum'){q('editor_shape').value=[20,...Array(10).fill(16)].join(',');setOutputs(outputIds);}}
456
+ q('editor_preset').onchange=()=>preset(q('editor_preset').value);
457
+ q('editor_shape').oninput=()=>q('editor_preset').value='custom';
458
+ for(const id of outputIds)q(outputName(id)).onchange=()=>q('editor_preset').value='custom';
459
  window.setEditorBusy=busy=>{q('editor_apply').disabled=busy||!source;};
460
  function graph(n){
461
  const canvas=q('network_map'),ctx=canvas.getContext('2d');ctx.clearRect(0,0,canvas.width,canvas.height);
 
470
  const n=api.neural(value.NeuralNetwork);source=value;
471
  const count=n.Weights.reduce((sum,m)=>sum+m.reduce((s,r)=>s+r.length,0),0)+n.Biases.reduce((s,r)=>s+r.length,0);
472
  q('editor_json').value=JSON.stringify(n,null,2);q('editor_json').disabled=false;
473
+ q('editor_name').value=value.Name||'Edited network';q('editor_shape').value=n.Shape.slice(0,-1).join(',');q('editor_preset').value='custom';setOutputs(Array.isArray(value.Outputs)?value.Outputs:[]);
474
  q('editor_metadata').textContent=n.Shape.join(' → ')+' · '+count+' parameters. Structure checked; no game/leaderboard validation claimed.';
475
  q('editor_contract').textContent=JSON.stringify({sensors:value.Sensors,outputs:value.Outputs,vehicle:value.Type},null,2);
476
  try{api.compatible(value);q('editor_legality').textContent='Supported by the current training worker.';}
 
523
  return {value,file:new File([blob],'edited-network.evolution',{type:'application/octet-stream'})};
524
  }
525
  q('editor_file').onchange=load;q('editor_import').onclick=load;
526
+ q('editor_create').onclick=()=>{try{const outputs=outputIds.filter(id=>q(outputName(id)).checked);show(api.fresh({shape:q('editor_shape').value.split(',').map(Number),outputs,seed:Number(q('editor_seed').value),xavier:q('editor_xavier').checked,bias:q('editor_bias').checked,name:q('editor_name').value}));status('Fresh browser-seeded network created. No training history, upload or storage.');}catch(error){status('Not created: '+error.message);}};
527
  q('editor_apply').onclick=async()=>{try{const {value,file}=await artifact();api.compatible(value);window.TrainingClient.useNetwork(file);show(value);status('Edited network selected for the next run. No upload yet.');}catch(error){status('Not applied: '+error.message);}};
528
  q('editor_download').onclick=async event=>{
529
  event.preventDefault();try{const {file}=await artifact();if(exportURL)URL.revokeObjectURL(exportURL);exportURL=URL.createObjectURL(file);const a=document.createElement('a');a.href=exportURL;a.download=file.name;a.click();status('Export requested. This is a new edited artifact with no inherited training scores.');}catch(error){status('Not exported: '+error.message);}
worker.py CHANGED
@@ -1,6 +1,6 @@
1
  """Bounded native adapter. No engine modifications; no network calls.
2
 
3
- This is NOT a network sandbox. Private deployment pending integration verification.
4
  """
5
  import gzip
6
  import hashlib
@@ -10,6 +10,7 @@ import math
10
  import os
11
  from pathlib import Path
12
  import subprocess
 
13
  import tempfile
14
  import time
15
  from training_config import validate_config
@@ -30,35 +31,71 @@ def read_json(data):
30
  if not isinstance(result,dict): raise ValueError('Expected JSON object')
31
  return result
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def network_input(data):
34
  value=read_json(data)
35
- network=value['NeuralNetwork']; shape=network['Shape']
36
- if type(value.get('Type')) is not int or value['Type']!=0 or value.get('Outputs')!=[0,1] or any(type(n) is not int for n in value['Outputs']): raise ValueError('Formula steering/acceleration only')
37
- if not isinstance(shape,list) or not 2<=len(shape)<=4 or any(type(n)!=int or not 1<=n<=16 for n in shape) or shape[-1]!=2:
38
- raise ValueError('Unsupported shape')
 
 
 
 
 
39
  sensors=value['Sensors']
40
  if not isinstance(sensors,list) or len(sensors)!=shape[0]: raise ValueError('Sensor count')
41
- # Deliberately small first supported contract. Do not forward unknown settings.
42
- clean_sensors=[]
43
- for sensor in sensors:
44
- if sensor.get('$type')=='speed': clean_sensors.append({'$type':'speed'})
45
- elif sensor.get('$type')=='raycast':
46
- selected={'$type':'raycast'}
47
- for key in ('Degrees','Length'):
48
- n=sensor[key]
49
- if type(n) not in (int,float) or not math.isfinite(n): raise ValueError('Sensor value')
50
- if key=='Degrees' and not -360<=n<=360: raise ValueError('Degrees')
51
- if key=='Length' and not 1<=n<=10000: raise ValueError('Length')
52
- selected[key]=n
53
- clean_sensors.append(selected)
54
- else: raise ValueError('Only speed and raycast sensors supported in smoke version')
55
  weights,biases=network['Weights'],network['Biases']
56
- if len(weights)!=len(shape)-1 or len(biases)!=len(shape)-1: raise ValueError('Layer count')
 
57
  for i,(a,b) in enumerate(zip(shape,shape[1:])):
58
- if len(weights[i])!=a or any(len(row)!=b for row in weights[i]) or len(biases[i])!=b: raise ValueError('Dimensions')
 
 
59
  for n in [x for row in weights[i] for x in row]+biases[i]:
60
  if type(n) not in (int,float) or not math.isfinite(n) or abs(n)>1e6: raise ValueError('Weight value')
61
- return {'Type':0,'Outputs':[0,1],'Sensors':clean_sensors,
62
  'Name':'HF candidate','NeuralNetwork':{'Shape':shape,'Weights':weights,'Biases':biases}}
63
 
64
  def track_input(data):
@@ -108,7 +145,7 @@ def run_native(track_bytes,network_bytes,*,binary=None,importer=None,scene_templ
108
  imported=json.loads(scene.read_text())
109
  spawn=save('spawn.json',{'frames':[{'position':imported['reset_position'],'rotation':imported['reset_rotation']}]})
110
  n=template['NeuralNetwork']; names=[f'sensor-{i}' for i in range(n['Shape'][0])]
111
- network=save('network.json',{'shape':n['Shape'],'weights':n['Weights'],'biases':n['Biases'],'inputs':names,'outputs':['Steering','Acceleration']})
112
  model=save('model.json',{'sensor_layout':{'names':names,'sensors':template['Sensors']}})
113
  settings=save('settings.json',{'population':config['population'],'selection_algorithm':config['selection'],'selection_size':config['parents'],
114
  'crossover':config['crossover'],'mutation_rate':config['mutation'],'adaptive_mutation':config['adaptive_mutation'],'weight_decay':config['decay'],
 
1
  """Bounded native adapter. No engine modifications; no network calls.
2
 
3
+ The adapter validates the vanilla game network envelope before native dispatch.
4
  """
5
  import gzip
6
  import hashlib
 
10
  import os
11
  from pathlib import Path
12
  import subprocess
13
+ import struct
14
  import tempfile
15
  import time
16
  from training_config import validate_config
 
31
  if not isinstance(result,dict): raise ValueError('Expected JSON object')
32
  return result
33
 
34
+ OUTPUT_NAMES = ('Steering', 'Acceleration', 'Handbrake', 'Brake', 'Boost')
35
+ SCALAR_SENSORS = {'speed', 'velocityFront', 'velocitySide', 'angularVelocity',
36
+ 'wheelAngle', 'boostCapacity', 'correctDirection'}
37
+
38
+ def sensor_input(sensor):
39
+ if not isinstance(sensor, dict): raise ValueError('Sensor object required')
40
+ kind = sensor.get('$type')
41
+ if not isinstance(kind, str): raise ValueError('Sensor type required')
42
+ clean = {'$type': kind}
43
+ def number(key, default=None, positive=False):
44
+ value = sensor.get(key, default)
45
+ if type(value) not in (int, float) or not math.isfinite(value) or abs(value)>1e6:
46
+ raise ValueError('Invalid sensor parameter '+key)
47
+ # The simulator converts descriptor values to float32 before use.
48
+ if positive and (value<=0 or struct.unpack('f', struct.pack('f', value))[0]<=0):
49
+ raise ValueError('Positive sensor parameter required: '+key)
50
+ clean[key] = value
51
+ return value
52
+ if kind in SCALAR_SENSORS: return clean
53
+ if kind == 'raycast':
54
+ if not -360<=number('Degrees')<=360: raise ValueError('Degrees')
55
+ if not 1<=number('Length')<=10000: raise ValueError('Length')
56
+ elif kind in ('accelerationFront', 'accelerationSide'):
57
+ number('MaxAcceleration', 10 if kind=='accelerationFront' else 12, positive=True)
58
+ elif kind == 'distanceFromWall': number('MaxDistance', 150, positive=True)
59
+ elif kind == 'trackCurvature':
60
+ number('MinLookaheadDistance', 256)
61
+ number('MaxLookaheadDistance', 512)
62
+ elif kind == 'grip':
63
+ offset = sensor.get('PositionOffset', {})
64
+ if isinstance(offset, dict): coords = [offset.get('X',0), offset.get('Y',0)]
65
+ elif isinstance(offset, list) and len(offset)==2: coords = offset
66
+ else: raise ValueError('Grip offset must have two components')
67
+ if any(type(n) not in (int,float) or not math.isfinite(n) or abs(n)>1e6 for n in coords):
68
+ raise ValueError('Invalid grip offset')
69
+ clean['PositionOffset'] = {'X':coords[0], 'Y':coords[1]}
70
+ else: raise ValueError('Unsupported sensor type')
71
+ return clean
72
+
73
  def network_input(data):
74
  value=read_json(data)
75
+ network=value['NeuralNetwork']; shape=network['Shape']; outputs=value.get('Outputs')
76
+ if type(value.get('Type')) is not int or value['Type']!=0: raise ValueError('Formula vehicle required')
77
+ if (not isinstance(outputs,list) or not 1<=len(outputs)<=5 or
78
+ any(type(n) is not int or not 0<=n<5 for n in outputs) or len(set(outputs))!=len(outputs)):
79
+ raise ValueError('Use an ordered unique subset of the five game outputs')
80
+ if (not isinstance(shape,list) or not 2<=len(shape)<=12 or
81
+ any(type(n) is not int or n<1 for n in shape) or shape[0]>20 or
82
+ any(n>16 for n in shape[1:-1]) or shape[-1]!=len(outputs)):
83
+ raise ValueError('Shape: up to 20 inputs, 10 hidden layers of 16, and 1–5 outputs')
84
  sensors=value['Sensors']
85
  if not isinstance(sensors,list) or len(sensors)!=shape[0]: raise ValueError('Sensor count')
86
+ clean_sensors=[sensor_input(sensor) for sensor in sensors]
87
+ rays=sum(s['$type']=='raycast' for s in clean_sensors)
88
+ if rays>13 or len(sensors)-rays>7: raise ValueError('Use up to 13 vision and 7 other sensors')
 
 
 
 
 
 
 
 
 
 
 
89
  weights,biases=network['Weights'],network['Biases']
90
+ if not isinstance(weights,list) or not isinstance(biases,list) or len(weights)!=len(shape)-1 or len(biases)!=len(shape)-1:
91
+ raise ValueError('Layer count')
92
  for i,(a,b) in enumerate(zip(shape,shape[1:])):
93
+ if (not isinstance(weights[i],list) or len(weights[i])!=a or
94
+ any(not isinstance(row,list) or len(row)!=b for row in weights[i]) or
95
+ not isinstance(biases[i],list) or len(biases[i])!=b): raise ValueError('Dimensions')
96
  for n in [x for row in weights[i] for x in row]+biases[i]:
97
  if type(n) not in (int,float) or not math.isfinite(n) or abs(n)>1e6: raise ValueError('Weight value')
98
+ return {'Type':0,'Outputs':outputs[:],'Sensors':clean_sensors,
99
  'Name':'HF candidate','NeuralNetwork':{'Shape':shape,'Weights':weights,'Biases':biases}}
100
 
101
  def track_input(data):
 
145
  imported=json.loads(scene.read_text())
146
  spawn=save('spawn.json',{'frames':[{'position':imported['reset_position'],'rotation':imported['reset_rotation']}]})
147
  n=template['NeuralNetwork']; names=[f'sensor-{i}' for i in range(n['Shape'][0])]
148
+ network=save('network.json',{'shape':n['Shape'],'weights':n['Weights'],'biases':n['Biases'],'inputs':names,'outputs':[OUTPUT_NAMES[i] for i in template['Outputs']]})
149
  model=save('model.json',{'sensor_layout':{'names':names,'sensors':template['Sensors']}})
150
  settings=save('settings.json',{'population':config['population'],'selection_algorithm':config['selection'],'selection_size':config['parents'],
151
  'crossover':config['crossover'],'mutation_rate':config['mutation'],'adaptive_mutation':config['adaptive_mutation'],'weight_decay':config['decay'],