Spaces:
Running on Zero
Running on Zero
Support all five Formula controls and maximum game networks
Browse filesKeep 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.
- BETA_TESTING.md +3 -5
- CUDA_REFERENCE.md +42 -46
- FRONTEND_SOURCE.json +2 -2
- README.md +1 -1
- RELEASE_MANIFEST.json +6 -6
- index.html +61 -23
- 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
|
| 40 |
-
|
| 41 |
-
|
| 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
|
| 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 |
-
|
| 12 |
-
|
| 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 = [
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
"Type": 0,
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
|
| 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":
|
| 46 |
-
"Biases":
|
| 47 |
},
|
| 48 |
}
|
| 49 |
-
|
| 50 |
|
| 51 |
network = generated_network()
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
| 53 |
```
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
|
|
|
| 57 |
|
| 58 |
-
|
| 59 |
-
seed 42, one worker, default total-score reward. These are a smoke fixture,
|
| 60 |
-
not a quality recommendation.
|
| 61 |
|
| 62 |
-
For
|
| 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": "
|
| 5 |
"scope": "Frontend only; no engine, private data, track assets, API calls or storage copied",
|
| 6 |
-
"status": "
|
| 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
|
| 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": "
|
| 6 |
"BINARY_PROVENANCE.json": "1116e1757afc6c8629cb2d957e3d77380fe4f7e3186c3832d16a5a08abce6aa8",
|
| 7 |
-
"CUDA_REFERENCE.md": "
|
| 8 |
"DATA_POLICY.md": "38da17f8528ede1ef1850e98832f75ba35ebdc86a0cde87e27674eb0ff9c7a7f",
|
| 9 |
"FEEDBACK.md": "f95b8f6afbf4a360976c0edca5d52f5b3a0b668c9a944d4947b840a7c53a122a",
|
| 10 |
-
"FRONTEND_SOURCE.json": "
|
| 11 |
"GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
|
| 12 |
"IMPORTER_BUILD_RECEIPT.json": "4ba5c533cc617105478e4d85c83f456a7dfa68b1388049624619871ba9e42e9d",
|
| 13 |
"LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
|
| 14 |
-
"README.md": "
|
| 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": "
|
| 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": "
|
| 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
|
| 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>.
|
| 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>
|
|
|
|
| 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>
|
| 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||
|
| 186 |
-
if(n.Shape
|
| 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==='
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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]||!
|
| 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<
|
|
|
|
|
|
|
| 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:
|
| 209 |
-
for(let i=0;i<
|
| 210 |
-
const a=
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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
|
| 37 |
-
if not isinstance(
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
sensors=value['Sensors']
|
| 40 |
if not isinstance(sensors,list) or len(sensors)!=shape[0]: raise ValueError('Sensor count')
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 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:
|
|
|
|
| 57 |
for i,(a,b) in enumerate(zip(shape,shape[1:])):
|
| 58 |
-
if
|
|
|
|
|
|
|
| 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':[
|
| 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':['
|
| 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'],
|