diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index f3aba3e..b7e3b47 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -31,6 +31,8 @@ Promote to a prose `structures/…md` file when a format needs behavioural notes |-----------|-------|------|-------| | Live guest-memory read | ✅ | [`tools/re-capture/gmem.py`](../../tools/re-capture/gmem.py) | Canary backs the guest address space with `/dev/shm/xenia_memory_*`; guest VAs map in through Xenia's fixed table. Full-RAM search ~0.2 s (sparse, `SEEK_DATA`). No debugger, no emulator patch, game keeps running | | IDXD object layout solver | ✅ | [`tools/re-capture/weapon_runtime.py`](../../tools/re-capture/weapon_runtime.py) | Scan RAM for a class's vtable → enumerate its objects → brute-force `(field, offset, encoding)` against the disc records. Accepts a binding only on **zero** contradictions. Generalizes to any IDXD-backed definition | +| Live entity state, anchored on the definition | ✅ | [`tools/re-capture/own_state.py`](../../tools/re-capture/own_state.py) · [autopilot](autopilot-memory-driven.md) | An undamaged craft holds its definition's own numbers, so a *solved definition field* locates the matching live field without a value scan: definition `HP` (1500) → **hull at `position+0x154`**, confirmed by a trace across a death (30/60/90 per hit, negative at 0). Reusable for any live counter whose maximum the definition carries | +| Input → dynamics calibration | ✅ | [`tools/re-capture/ctrl_probe.py`](../../tools/re-capture/ctrl_probe.py) · [`binq.py`](../../tools/re-capture/binq.py) | Hold each pad input in turn and measure the craft's speed as displacement/s of its own position triple — no speed field needed first. Settled the throttle: **`RT` accelerates, `LT` brakes, and the setting persists** (488 → 1510 → 174 units/s), overturning an earlier field-scan conclusion | ## Functions / code paths diff --git a/docs/re/autopilot-memory-driven.md b/docs/re/autopilot-memory-driven.md index 4b699b2..9551d65 100644 --- a/docs/re/autopilot-memory-driven.md +++ b/docs/re/autopilot-memory-driven.md @@ -1,12 +1,99 @@ # Memory-driven autopilot — build log and current state -**Status: 🟢 IT FLIES AND SHOOTS — it does not yet survive.** -Updated 2026-07-29 (second pass). The autopilot reads the live world, picks -hostile targets, pursues them and opens fire. What it cannot do is stay alive: -there is no evasion or shield management, so it dies before a mission ends. -This is the honest state, not a plan. +**Status: 🟢 IT FLIES, KILLS AND SURVIVES — it has not yet finished a mission.** +Updated 2026-07-30. `pilot.py` flew Stage 02 for **300 s with the hull untouched +at 1500/1500** and scored the first confirmed autopilot kill (`YOU KILLED +WARPLANES 0001` on the HUD, screenshots `shots/pilot1-*.png`); the scene's +hostile count fell from 134 to 111 over the run. The day before, every run was +dead inside 35 s. What is still missing is the *end* of a mission: the objective +counter (`REMAINING OB`) rises as new waves spawn, and nothing yet tracks which +targets actually close it out. -## What the loop does now, observed +## 2026-07-30 — the numbers survival needs + +Three things the loop was missing were measured this session, each by +consequence rather than by reading a field and hoping. + +### Hull is `position + 0x154` ✅ CONFIRMED + +The unit definition already had `HP` solved at `+0x054` +([unit-struct-runtime](structures/unit-struct-runtime.md)); the Delta Saber's is +**1500**. A craft that has taken no damage must therefore *contain that number*, +which turns "find the HP field" into a two-float lookup rather than a value scan +(`own_state.py`). It appears once in the entity object, at `pos+0x154`, and the +trace across a death settles it (`ctrl_probe.py` capture, `binq.py trace`): + +``` + t phase hull + 0.00 base 1500.00 <- == definition HP + 23.25 rest_A 1380.00 <- first hit, -120 + 26.68 … 27.18 B 1320 … 930 <- seven hits in 0.5 s + 31.93 X 150.00 + 35.21 Y -30.00 <- goes negative + 35.26 Y -180.00 -> GAME OVER on screen +``` + +Damage arrives in 30/60/90-point steps and the field goes *negative* at death, +so it is the raw hull counter, not a clamped display value. **1500 hull lost in +12 s** of sitting in a turret's line of fire is the whole reason every earlier +run died. + +### Shield is `position + 0x430` 🟡 PROBABLE — not yet confirmed live + +Same anchor trick: the definition's shield `MaxValue` is **400** and +`ChargeSpeed` **25**, and the entity object holds `400.0` at `+0x430`, `+0x434` +and `+0x438`, with `25.0` at `+0x448`. Which of the three is the *current* value +is unproven — the capture that spanned the death used a ±0x400 window and +cropped them out. `ctrl_probe.py` now samples ±0x800. + +### `RT` accelerates, `LT` brakes, and the throttle is a *setting* ✅ CONFIRMED + +`ctrl_probe.py` holds each input in turn and measures the craft's own speed as +displacement per second from the position triple, so no speed field is needed. +Distance flown / phase duration, one 3 s hold each, sticks neutral: + +| phase | speed (units/s) | | phase | speed (units/s) | +|---|---|---|---|---| +| base (no input) | 488 | | A | 287 | +| **RT** | **1510** | | B | 139 | +| rest after RT | 1056 | | X | 125 | +| **LT** | **174** | | Y | (dying) | +| rest after LT | 428 | | LB / LS / RS / RY / RX / dpad | no effect | + +RT triples the speed, LT cuts it to a third, and **the braked state persists**: +after the LT phase the craft sat at 125–140 units/s with the sticks and triggers +neutral for the remaining 40 s, and nothing but RT brought it back. So these are +a throttle setting, not a momentary boost — which also means a control loop must +send only the *changes*. + +This **corrects** the earlier note in this file ("`RT` is *not* the throttle, +and no button tested is"). That conclusion came from `findspeed.py`, which +assumed the control and went looking for a *field* that rose; measuring the +speed directly reverses it. + +### Two method corrections + +* **Pick the attitude block by the flight path, not by address order.** The + player object contains **20** orthonormal 3×3 blocks (identity frames, bone + or camera frames), and `pos-0x70` and `pos-0x30` hold the *same* matrix. + Taking `found[0]` wrote a config with `rot_delta = -0x764` and a nonsense + forward axis; `entities2.py self` now scores every block against the measured + direction of travel and picks the best (`cos = +1.000`, row 2, sign +1). +* **The entity-heap scan has to be numpy.** A per-word Python loop over the + 16 MB entity region costs seconds per scan, which is the whole budget of a + 10 Hz control loop; `np.isin` over a `>u4` view is milliseconds. + +### Stage 02 as an autopilot testbed (from the in-flight HUD) + +`OBJECTIVE: shoot down all invading enemy fighters while watching out for +attacks on the ACROPOLIS` · `DEFEAT: your fighter is shot down, or the ACROPOLIS +is sunk` · `HINT: you can resupply at the ACROPOLIS`. The HUD shows +**`REMAINING OB 004`** — only four objective targets — so this mission is +winnable by an autopilot that survives. It also shows separate **SHIELD** and +**ARMOR** bars (matching a 400-point shield over 1500 hull), `A/B 7,635` +afterburner, and `NOSE BM 06000` / `MAIN MPM 00300` ammo. + +## What the loop did before that, observed ``` [ 82.5] tgt=e007_ADAN_Turret d=3384 yaw= -7.3 pit=+14.6 stick=(-0.13,-0.34) fire=0 @@ -33,8 +120,8 @@ rescan reports the scene as e.g. `136 entities {'TCAF': 16, 'ADAN': 120}`. direction of travel with **cos = +1.000**. 3. **The fire button is RB** — established by consequence, not by guessing: of RB/LB/A/B/X/Y/RT/LT, pressing RB is the only one that makes the nose-ammo - counter in RAM fall (5958 → 5940). `RT` is *not* the throttle, and no button - tested is. + counter in RAM fall (5958 → 5940). (This entry also claimed `RT` is *not* the + throttle — **wrong**, see the 2026-07-30 measurement above.) 4. **Control.** PD on the aiming error with the derivative taken from the craft's own body angular velocity (from two consecutive rotation matrices), and target selection weighted by off-boresight angle @@ -113,7 +200,36 @@ statements about `0x820af030`, which is *not* the live entity — (6 Hz) and re-check orthonormality on every read — blocks found by a scan get overwritten between the scan and the read. -## The next step that unblocks the most +## After survival, the blocker is lethality (2026-07-30) + +The 300 s run took **no damage at all** and killed **one** warplane, spending +~800 rounds of nose ammo (`06000` → `05193`) to do it, while `REMAINING OB` rose +from `004` to `011` as fresh waves spawned. So attrition at this rate never +finishes the mission, and the ranking of open problems has changed: + +1. **Hit rate.** It opens fire at 2–5 km with a 9° cone and a crude lead + (`p + v·d/speed`, no projectile speed). The `Shell` records in + [weapon-struct-runtime](structures/weapon-struct-runtime.md) carry the real + projectile speed and `MaximumRange` per weapon — the lead and the firing + range should come from *those*, not from constants. +2. **Which targets count.** `REMAINING OB` is the mission's own objective + counter and it is on screen, so it is in RAM; finding it turns "shoot + whatever is nearest" into "shoot what closes the mission". Objective-marked + entities also draw an `OB` badge in the HUD, so the flag is likely a word in + the entity object. +3. **Confirming the shield word** — needs a run that actually takes damage; the + pilot is now good enough at avoiding that to make it awkward, so drive + straight at a turret on purpose with `--dry` steering disabled. +4. **Does the ACROPOLIS repair?** RETIRE mode has never triggered (the hull + never fell), so the resupply hint is still untested. + +## The next step that unblocks the most (superseded — kept for the reasoning) + +**Update 2026-07-30: this is no longer the blocker.** Entity typing via the +definition pointer already solved target selection, so the game's own target +pointer is now a convenience rather than a prerequisite. It would still be the +cheapest route to problem 2 above (objective targets), because whatever the HUD +locks on to is what the game itself considers a target. **Find the game's own target pointer instead of typing entities ourselves.** The HUD has a lock-on system (a `TARGET` marker and a target-cycle button), so @@ -143,6 +259,12 @@ with the pad and watch which pointer-shaped global changes in step. ## Files +`pilot.py` (**the survival loop**) · `ctrl_probe.py` (input → speed calibration, +plus a per-tick window of the player object) · `binq.py` (query that capture) · +`own_state.py` (definition-anchored hull/shield lookup) · `fly_session.sh` +(boot → mission → bind → fly, one task) · `wait_flight.sh` (wait for the real +HUD instead of a fixed sleep) · `navigator.py` (drift-aware steering + CPA +avoidance, reused by the pilot) · `gworld.py` (live reader + entity list) · `flight_probe.py` (scripted inputs + sampling, and the `Pad` FIFO client) · `flight_analyze.py` · `whatchanges.py` (encoding-agnostic "which words are live") · `findplayer.py` · `findself.py` · diff --git a/docs/re/captures/autopilot-300s-undamaged-stage02.png b/docs/re/captures/autopilot-300s-undamaged-stage02.png new file mode 100644 index 0000000..765a896 Binary files /dev/null and b/docs/re/captures/autopilot-300s-undamaged-stage02.png differ diff --git a/docs/re/captures/autopilot-first-kill-stage02.png b/docs/re/captures/autopilot-first-kill-stage02.png new file mode 100644 index 0000000..24dbf29 Binary files /dev/null and b/docs/re/captures/autopilot-first-kill-stage02.png differ diff --git a/docs/re/captures/ctrl-probe-stage02.csv b/docs/re/captures/ctrl-probe-stage02.csv new file mode 100644 index 0000000..7706bb9 --- /dev/null +++ b/docs/re/captures/ctrl-probe-stage02.csv @@ -0,0 +1,1781 @@ +t,phase,x,y,z,speed,dodged +0.000,base,-8384.302,881.910,1004.407,1096.036,0 +0.051,base,-8448.422,881.910,1004.407,884.732,0 +0.101,base,-8495.042,881.910,1004.407,692.471,0 +0.152,base,-8518.352,881.910,1004.407,374.931,0 +0.202,base,-8524.162,881.910,1004.407,375.108,0 +0.253,base,-8524.162,881.910,1004.407,404.694,0 +0.303,base,-8570.816,881.910,1004.407,549.023,0 +0.353,base,-8599.937,881.910,1004.407,696.628,0 +0.404,base,-8634.901,881.910,1004.407,464.217,0 +0.454,base,-8664.021,881.910,1004.407,318.964,0 +0.504,base,-8664.021,881.910,1004.407,400.791,0 +0.554,base,-8664.021,881.910,1004.407,429.495,0 +0.607,base,-8716.486,881.910,1004.406,601.290,0 +0.657,base,-8751.451,881.910,1004.406,687.535,0 +0.707,base,-8786.417,881.910,1004.406,432.846,0 +0.757,base,-8803.882,881.910,1004.406,259.680,0 +0.809,base,-8803.882,881.910,1004.406,375.122,0 +0.859,base,-8803.882,881.910,1004.406,513.739,0 +0.909,base,-8862.156,881.910,1004.406,661.632,0 +0.962,base,-8908.776,881.910,1004.406,661.649,0 +1.012,base,-8937.896,881.910,1004.406,373.944,0 +1.062,base,-8937.896,881.910,1004.406,436.391,0 +1.112,base,-8937.896,881.910,1004.406,403.006,0 +1.162,base,-8996.172,881.910,1004.406,575.714,0 +1.214,base,-9019.481,881.910,1004.406,661.973,0 +1.264,base,-9054.446,881.910,1004.406,374.117,0 +1.314,base,-9071.911,881.910,1004.406,261.793,0 +1.364,base,-9071.911,881.910,1004.406,430.666,0 +1.414,base,-9071.911,881.910,1004.406,516.904,0 +1.467,base,-9141.842,881.910,1004.406,689.208,0 +1.517,base,-9176.807,881.910,1004.406,717.559,0 +1.567,base,-9211.771,881.910,1004.405,378.033,0 +1.617,base,-9217.581,881.910,1004.405,407.215,0 +1.667,base,-9217.581,881.910,1004.405,378.049,0 +1.718,base,-9258.392,881.910,1004.405,523.763,0 +1.768,base,-9287.512,881.910,1004.405,669.165,0 +1.818,base,-9322.477,881.910,1004.405,494.397,0 +1.868,base,-9351.597,881.910,1004.405,348.998,0 +1.918,base,-9357.406,881.910,1004.405,435.717,0 +1.968,base,-9357.406,881.910,1004.405,464.850,0 +2.018,base,-9409.871,881.910,1004.405,604.968,0 +2.068,base,-9444.837,881.910,1004.405,691.201,0 +2.120,base,-9479.802,881.910,1004.405,432.409,0 +2.170,base,-9497.267,881.910,1004.405,259.344,0 +2.220,base,-9497.267,881.910,1004.405,405.567,0 +2.271,base,-9497.267,881.910,1004.405,492.600,0 +2.321,base,-9561.387,881.910,1004.405,666.678,0 +2.371,base,-9596.352,881.909,1004.405,680.154,0 +2.421,base,-9631.316,881.909,1004.405,369.655,0 +2.476,base,-9637.127,881.909,1004.405,426.721,0 +2.526,base,-9637.127,881.909,1004.405,398.220,0 +2.576,base,-9683.781,881.909,1004.405,552.976,0 +2.626,base,-9712.901,881.909,1004.404,669.383,0 +2.676,base,-9747.866,881.909,1004.404,461.767,0 +2.726,base,-9771.177,881.909,1004.404,317.543,0 +2.778,base,-9776.986,881.909,1004.404,404.251,0 +2.828,base,-9776.986,881.909,1004.404,461.995,0 +2.878,base,-9829.451,881.909,1004.404,552.719,0 +2.928,base,-9864.416,881.909,1004.404,697.921,0 +2.978,base,-9887.727,881.909,1004.404,435.640,0 +3.029,base,-9916.847,881.909,1004.404,261.351,0 +3.079,base,-9916.847,881.909,1004.404,431.223,0 +3.129,base,-9916.847,881.909,1004.404,460.060,0 +3.181,base,-9975.121,881.909,1004.404,633.274,0 +3.231,base,-10010.086,881.909,1004.404,659.248,0 +3.281,base,-10045.052,881.909,1004.404,376.611,0 +3.332,base,-10050.861,881.909,1004.404,434.739,0 +3.382,base,-10050.861,881.909,1004.404,347.730,0 +3.432,base,-10097.517,881.909,1004.404,548.621,0 +3.482,base,-10114.981,881.909,1004.404,692.869,0 +3.534,base,-10161.602,881.909,1004.404,461.211,0 +3.584,base,-10190.722,881.909,1004.404,373.754,0 +3.634,base,-10190.722,881.909,1004.404,434.658,0 +3.685,base,-10190.722,881.909,1004.404,405.821,0 +3.735,base,-10248.996,881.909,1004.403,580.410,0 +3.785,base,-10272.307,881.909,1004.403,698.297,0 +3.835,base,-10307.271,881.909,1004.403,407.291,0 +3.885,base,-10330.546,881.909,1004.403,290.829,0 +3.935,base,-10330.546,881.909,1004.403,432.685,0 +3.985,base,-10330.546,881.909,1004.403,513.986,0 +4.037,RT,-10394.666,881.909,1004.403,758.592,0 +4.087,RT,-10434.370,881.909,1004.403,800.832,0 +4.137,RT,-10483.787,881.909,1004.403,487.713,0 +4.188,RT,-10492.908,881.909,1004.403,755.322,0 +4.239,RT,-10492.908,881.909,1004.403,826.994,0 +4.289,RT,-10586.520,881.909,1004.403,1064.161,0 +4.339,RT,-10650.520,881.909,1004.403,1542.428,0 +4.390,RT,-10707.835,881.909,1004.402,1150.848,0 +4.440,RT,-10803.733,881.909,1004.402,837.001,0 +4.492,RT,-10820.657,881.909,1004.402,1422.797,0 +4.542,RT,-10820.657,881.909,1004.402,1433.964,0 +4.592,RT,-10995.492,881.909,1004.402,1759.204,0 +4.643,RT,-11094.777,881.909,1004.402,2351.705,0 +4.693,RT,-11174.253,881.909,1004.401,1481.919,0 +4.743,RT,-11293.348,881.909,1004.401,991.676,0 +4.793,RT,-11293.348,881.909,1004.401,1487.801,0 +4.843,RT,-11293.348,881.909,1004.401,1488.390,0 +4.893,RT,-11472.229,881.909,1004.401,2081.624,0 +4.943,RT,-11591.442,881.908,1004.401,2576.931,0 +4.994,RT,-11710.656,881.908,1004.400,1682.860,0 +5.044,RT,-11809.942,881.908,1004.400,1078.458,0 +5.094,RT,-11809.942,881.908,1004.400,1374.384,0 +5.146,RT,-11809.942,881.908,1004.400,1472.860,0 +5.196,RT,-11988.822,881.908,1004.400,1867.550,0 +5.246,RT,-12108.036,881.908,1004.400,2381.347,0 +5.296,RT,-12187.513,881.908,1004.399,1586.739,0 +5.346,RT,-12286.798,881.908,1004.399,991.449,0 +5.396,RT,-12306.607,881.908,1004.399,1487.760,0 +5.446,RT,-12306.607,881.908,1004.399,1587.252,0 +5.497,RT,-12485.488,881.908,1004.399,2083.616,0 +5.547,RT,-12604.702,881.908,1004.399,2480.446,0 +5.597,RT,-12723.916,881.908,1004.398,1667.772,0 +5.647,RT,-12803.392,881.908,1004.398,1078.413,0 +5.699,RT,-12823.201,881.908,1004.398,1275.145,0 +5.749,RT,-12823.201,881.908,1004.398,1371.198,0 +5.799,RT,-12982.272,881.908,1004.398,1866.436,0 +5.850,RT,-13081.558,881.908,1004.398,2457.148,0 +5.901,RT,-13200.771,881.908,1004.397,1762.697,0 +5.951,RT,-13319.985,881.908,1004.397,1274.287,0 +6.002,RT,-13339.795,881.908,1004.397,1482.988,0 +6.052,RT,-13339.795,881.908,1004.397,1381.276,0 +6.102,RT,-13498.866,881.907,1004.397,1859.893,0 +6.153,RT,-13598.151,881.907,1004.397,2443.666,0 +6.205,RT,-13717.366,881.907,1004.396,1758.678,0 +6.256,RT,-13836.580,881.907,1004.396,1272.638,0 +6.306,RT,-13856.389,881.907,1004.396,1485.688,0 +6.356,RT,-13856.389,881.907,1004.396,1190.522,0 +6.406,RT,-14015.461,881.907,1004.396,1885.301,0 +6.456,RT,-14075.008,881.907,1004.396,2480.590,0 +6.506,RT,-14233.960,881.907,1004.395,1775.633,0 +6.556,RT,-14353.174,881.907,1004.395,1451.513,0 +6.607,RT,-14372.983,881.907,1004.395,677.203,0 +6.661,RT,-14372.983,881.907,1004.395,1156.572,0 +6.711,RT,-14372.983,881.907,1004.395,1841.182,0 +6.762,RT,-14591.602,881.907,1004.395,2469.917,0 +6.812,RT,-14750.554,881.907,1004.394,2568.458,0 +6.862,RT,-14869.768,881.907,1004.394,1482.453,0 +6.912,RT,-14889.577,881.907,1004.394,691.652,0 +6.963,RT,-14889.577,881.907,1004.394,1185.055,0 +7.013,RT,-14889.577,881.907,1004.394,1865.932,0 +7.063,rest_RT,-15108.195,881.907,1004.394,2249.942,0 +7.114,rest_RT,-15265.005,881.906,1004.393,2530.126,0 +7.164,rest_RT,-15341.267,881.906,1004.393,1438.577,0 +7.214,rest_RT,-15397.515,881.906,1004.393,658.897,0 +7.265,rest_RT,-15397.515,881.906,1004.393,1262.841,0 +7.315,rest_RT,-15397.515,881.906,1004.393,1501.608,0 +7.365,rest_RT,-15594.876,881.906,1004.393,2008.425,0 +7.415,rest_RT,-15699.091,881.906,1004.392,2249.489,0 +7.465,rest_RT,-15800.091,881.906,1004.392,1266.481,0 +7.516,rest_RT,-15849.382,881.906,1004.392,747.590,0 +7.566,rest_RT,-15849.382,881.906,1004.392,1100.723,0 +7.616,rest_RT,-15849.382,881.906,1004.392,1159.183,0 +7.666,rest_RT,-16021.201,881.906,1004.392,1598.783,0 +7.716,rest_RT,-16081.749,881.906,1004.392,1883.861,0 +7.766,rest_RT,-16169.893,881.906,1004.391,1026.421,0 +7.816,rest_RT,-16226.875,881.906,1004.391,722.626,0 +7.866,rest_RT,-16226.875,881.906,1004.391,1013.443,0 +7.917,rest_RT,-16226.875,881.906,1004.391,986.829,0 +7.968,rest_RT,-16374.138,881.906,1004.391,1477.825,0 +8.018,rest_RT,-16425.760,881.906,1004.391,1594.021,0 +8.068,rest_RT,-16524.715,881.906,1004.391,867.777,0 +8.119,rest_RT,-16548.580,881.906,1004.391,611.020,0 +8.169,rest_RT,-16548.580,881.906,1004.391,728.088,0 +8.219,rest_RT,-16548.580,881.906,1004.391,928.412,0 +8.269,rest_RT,-16671.287,881.906,1004.390,1229.014,0 +8.319,rest_RT,-16734.791,881.906,1004.390,1259.791,0 +8.369,rest_RT,-16795.082,881.906,1004.390,657.526,0 +8.422,rest_RT,-16804.834,881.906,1004.390,344.844,0 +8.472,rest_RT,-16804.834,881.906,1004.390,536.069,0 +8.522,rest_RT,-16804.834,881.906,1004.390,828.604,0 +8.572,rest_RT,-16903.963,881.906,1004.390,1060.110,0 +8.622,rest_RT,-16970.779,881.906,1004.390,1097.319,0 +8.673,rest_RT,-17017.143,881.906,1004.390,602.297,0 +8.723,rest_RT,-17024.580,881.906,1004.390,268.644,0 +8.773,rest_RT,-17024.580,881.906,1004.390,401.848,0 +8.823,rest_RT,-17024.580,881.906,1004.390,547.066,0 +8.874,rest_RT,-17098.168,881.905,1004.390,720.474,0 +8.924,rest_RT,-17134.889,881.905,1004.389,767.289,0 +8.974,rest_RT,-17169.854,881.905,1004.389,407.553,0 +9.027,rest_RT,-17181.473,881.905,1004.389,428.159,0 +9.079,LT,-17181.473,881.905,1004.389,374.683,0 +9.129,LT,-17222.404,881.905,1004.389,450.589,0 +9.179,LT,-17246.438,881.905,1004.389,548.992,0 +9.229,LT,-17272.293,881.905,1004.389,343.895,0 +9.279,LT,-17291.420,881.905,1004.389,224.059,0 +9.329,LT,-17291.420,881.905,1004.389,242.587,0 +9.379,LT,-17291.420,881.905,1004.389,217.109,0 +9.431,LT,-17321.391,881.905,1004.389,273.157,0 +9.484,LT,-17335.998,881.905,1004.389,281.519,0 +9.534,LT,-17347.393,881.905,1004.389,136.290,0 +9.584,LT,-17349.086,881.905,1004.389,65.347,0 +9.634,LT,-17349.086,881.905,1004.389,109.151,0 +9.684,LT,-17349.086,881.905,1004.389,151.160,0 +9.736,LT,-17369.457,881.905,1004.389,184.753,0 +9.786,LT,-17379.643,881.905,1004.389,193.144,0 +9.836,LT,-17386.434,881.905,1004.389,93.224,0 +9.887,LT,-17388.127,881.905,1004.389,110.071,0 +9.937,LT,-17388.127,881.905,1004.389,127.006,0 +9.987,LT,-17401.707,881.905,1004.389,169.372,0 +10.037,LT,-17411.893,881.905,1004.389,194.749,0 +10.087,LT,-17422.080,881.905,1004.389,127.094,0 +10.137,LT,-17427.168,881.905,1004.389,76.250,0 +10.187,LT,-17427.168,881.905,1004.389,118.164,0 +10.237,LT,-17427.168,881.905,1004.389,143.511,0 +10.288,LT,-17445.846,881.905,1004.389,193.682,0 +10.338,LT,-17456.031,881.905,1004.389,210.473,0 +10.389,LT,-17466.219,881.905,1004.389,118.299,0 +10.439,LT,-17469.604,881.905,1004.389,67.579,0 +10.489,LT,-17469.604,881.905,1004.389,109.355,0 +10.539,LT,-17469.604,881.905,1004.389,143.060,0 +10.591,LT,-17488.283,881.905,1004.389,193.543,0 +10.641,LT,-17498.469,881.905,1004.389,209.864,0 +10.691,LT,-17508.654,881.905,1004.389,118.346,0 +10.741,LT,-17512.039,881.905,1004.389,135.317,0 +10.791,LT,-17512.039,881.905,1004.389,126.578,0 +10.841,LT,-17525.631,881.905,1004.389,143.831,0 +10.892,LT,-17534.113,881.905,1004.389,186.111,0 +10.942,LT,-17540.904,881.905,1004.389,126.816,0 +10.992,LT,-17549.387,881.905,1004.389,84.716,0 +11.042,LT,-17551.080,881.905,1004.389,127.115,0 +11.092,LT,-17551.080,881.905,1004.389,135.618,0 +11.142,LT,-17566.363,881.905,1004.389,176.469,0 +11.192,LT,-17576.549,881.905,1004.389,198.764,0 +11.244,LT,-17586.736,881.905,1004.388,124.209,0 +11.297,LT,-17591.824,881.905,1004.388,74.354,0 +11.347,LT,-17591.824,881.905,1004.388,124.768,0 +11.398,LT,-17591.824,881.905,1004.388,135.056,0 +11.448,LT,-17612.195,881.905,1004.388,202.579,0 +11.498,LT,-17618.986,881.905,1004.388,209.643,0 +11.548,LT,-17632.566,881.905,1004.388,109.210,0 +11.600,LT,-17634.260,881.905,1004.388,134.442,0 +11.650,LT,-17634.260,881.905,1004.388,108.798,0 +11.700,LT,-17646.148,881.905,1004.388,168.881,0 +11.751,LT,-17654.631,881.905,1004.388,202.655,0 +11.801,LT,-17668.213,881.905,1004.388,150.155,0 +11.851,LT,-17675.004,881.905,1004.388,108.870,0 +11.904,LT,-17676.695,881.905,1004.388,108.770,0 +11.954,LT,-17676.695,881.905,1004.388,125.502,0 +12.004,LT,-17690.277,881.905,1004.388,169.314,0 +12.054,LT,-17700.463,881.905,1004.388,194.146,0 +12.104,rest_LT,-17710.648,881.905,1004.388,126.809,0 +12.155,rest_LT,-17715.785,881.905,1004.388,76.173,0 +12.205,rest_LT,-17715.785,881.905,1004.388,159.771,0 +12.255,rest_LT,-17715.785,881.905,1004.388,221.992,0 +12.309,rest_LT,-17743.381,881.905,1004.388,325.454,0 +12.359,rest_LT,-17761.086,881.905,1004.388,339.432,0 +12.409,rest_LT,-17782.199,881.905,1004.388,209.149,0 +12.462,rest_LT,-17785.998,881.905,1004.388,276.116,0 +12.513,rest_LT,-17785.998,881.905,1004.388,289.719,0 +12.563,rest_LT,-17817.438,881.905,1004.388,433.437,0 +12.614,rest_LT,-17841.518,881.905,1004.388,548.212,0 +12.664,rest_LT,-17873.689,881.905,1004.388,393.307,0 +12.715,rest_LT,-17896.811,881.905,1004.388,274.919,0 +12.765,rest_LT,-17896.811,881.905,1004.388,405.086,0 +12.815,rest_LT,-17896.811,881.905,1004.388,523.344,0 +12.865,rest_LT,-17955.086,881.905,1004.388,639.640,0 +12.915,rest_LT,-18001.705,881.905,1004.388,667.686,0 +12.966,rest_LT,-18025.016,881.905,1004.388,377.681,0 +13.016,rest_LT,-18030.826,881.905,1004.388,406.831,0 +13.066,rest_LT,-18030.826,881.905,1004.388,406.837,0 +13.116,rest_LT,-18083.291,881.905,1004.388,552.907,0 +13.166,rest_LT,-18106.602,881.905,1004.387,698.284,0 +13.216,rest_LT,-18141.566,881.905,1004.387,435.074,0 +13.266,rest_LT,-18170.686,881.905,1004.387,319.031,0 +13.317,rest_LT,-18170.686,881.905,1004.387,434.445,0 +13.367,rest_LT,-18170.686,881.905,1004.387,405.567,0 +13.417,rest_LT,-18228.961,881.905,1004.387,581.071,0 +13.467,rest_LT,-18252.271,881.905,1004.387,638.926,0 +13.517,rest_LT,-18287.236,881.905,1004.387,348.868,0 +13.568,rest_LT,-18298.855,881.905,1004.387,465.411,0 +13.618,rest_LT,-18298.855,881.905,1004.387,429.081,0 +13.668,rest_LT,-18345.512,881.905,1004.387,486.530,0 +13.721,rest_LT,-18374.631,881.905,1004.387,658.354,0 +13.771,rest_LT,-18397.941,881.905,1004.387,455.964,0 +13.821,rest_LT,-18432.906,881.905,1004.387,317.297,0 +13.872,rest_LT,-18438.715,881.905,1004.387,426.447,0 +13.923,rest_LT,-18438.715,881.905,1004.387,426.447,0 +13.976,rest_LT,-18485.336,881.905,1004.387,570.920,0 +14.026,rest_LT,-18520.301,881.905,1004.387,676.836,0 +14.076,rest_LT,-18555.266,881.905,1004.387,457.745,0 +14.130,RT+LT,-18578.541,881.905,1004.386,286.013,0 +14.180,RT+LT,-18578.541,881.905,1004.386,473.212,0 +14.230,RT+LT,-18578.541,881.905,1004.386,497.855,0 +14.280,RT+LT,-18651.820,881.905,1004.386,763.308,0 +14.330,RT+LT,-18678.461,881.905,1004.386,827.578,0 +14.380,RT+LT,-18731.740,881.905,1004.386,464.255,0 +14.431,RT+LT,-18745.021,881.905,1004.386,331.565,0 +14.481,RT+LT,-18745.021,881.905,1004.386,430.115,0 +14.531,RT+LT,-18745.021,881.905,1004.386,630.342,0 +14.582,RT+LT,-18818.301,881.905,1004.386,763.032,0 +14.632,RT+LT,-18871.580,881.905,1004.386,828.585,0 +14.682,RT+LT,-18898.221,881.905,1004.386,465.337,0 +14.732,RT+LT,-18911.639,881.905,1004.386,436.084,0 +14.782,RT+LT,-18911.639,881.905,1004.386,477.484,0 +14.832,RT+LT,-18959.055,881.904,1004.386,698.258,0 +14.882,RT+LT,-18994.000,881.904,1004.386,844.993,0 +14.933,RT+LT,-19051.494,881.904,1004.386,608.293,0 +14.983,RT+LT,-19080.889,881.904,1004.385,433.828,0 +15.033,RT+LT,-19080.889,881.904,1004.385,521.224,0 +15.083,RT+LT,-19080.889,881.904,1004.385,529.063,0 +15.133,RT+LT,-19155.885,881.904,1004.385,763.409,0 +15.183,RT+LT,-19186.848,881.904,1004.385,963.426,0 +15.233,RT+LT,-19234.123,881.904,1004.385,589.823,0 +15.283,RT+LT,-19274.273,881.904,1004.385,435.565,0 +15.334,RT+LT,-19274.273,881.904,1004.385,561.310,0 +15.384,RT+LT,-19274.273,881.904,1004.385,601.561,0 +15.435,RT+LT,-19347.369,881.904,1004.385,845.751,0 +15.485,RT+LT,-19395.641,881.904,1004.385,1051.933,0 +15.535,RT+LT,-19444.912,881.904,1004.385,694.752,0 +15.585,RT+LT,-19486.508,881.904,1004.385,453.723,0 +15.635,RT+LT,-19486.508,881.904,1004.385,581.934,0 +15.685,RT+LT,-19486.508,881.904,1004.385,623.644,0 +15.736,RT+LT,-19561.457,881.904,1004.385,873.029,0 +15.786,RT+LT,-19611.408,881.904,1004.384,1039.292,0 +15.836,RT+LT,-19661.357,881.904,1004.384,701.894,0 +15.886,RT+LT,-19694.658,881.904,1004.384,447.626,0 +15.937,RT+LT,-19702.957,881.904,1004.384,569.864,0 +15.990,RT+LT,-19702.957,881.904,1004.384,651.269,0 +16.040,RT+LT,-19777.908,881.904,1004.384,860.469,0 +16.090,RT+LT,-19827.857,881.904,1004.384,1080.721,0 +16.140,RT+LT,-19877.809,881.904,1004.384,706.532,0 +16.190,RT+LT,-19919.408,881.904,1004.384,457.124,0 +16.240,RT+LT,-19919.408,881.904,1004.384,539.018,0 +16.291,RT+LT,-19919.408,881.904,1004.384,537.144,0 +16.341,RT+LT,-19986.059,881.904,1004.384,860.141,0 +16.392,RT+LT,-20027.658,881.904,1004.384,1023.947,0 +16.444,RT+LT,-20094.258,881.904,1004.383,730.892,0 +16.494,RT+LT,-20127.559,881.904,1004.383,529.744,0 +16.546,RT+LT,-20135.857,881.904,1004.383,575.551,0 +16.596,RT+LT,-20135.857,881.904,1004.383,575.558,0 +16.646,RT+LT,-20210.809,881.904,1004.383,789.910,0 +16.696,RT+LT,-20244.107,881.904,1004.383,956.172,0 +16.746,RT+LT,-20294.059,881.904,1004.383,621.533,0 +16.796,RT+LT,-20327.357,881.904,1004.383,455.749,0 +16.847,RT+LT,-20335.658,881.904,1004.383,534.687,0 +16.897,RT+LT,-20335.658,881.904,1004.383,575.689,0 +16.949,RT+LT,-20402.309,881.904,1004.383,781.814,0 +16.999,RT+LT,-20443.908,881.904,1004.383,987.417,0 +17.049,RT+LT,-20493.857,881.904,1004.383,701.525,0 +17.100,RT+LT,-20535.457,881.904,1004.383,495.140,0 +17.150,rest_RT+LT,-20543.758,881.904,1004.383,620.607,0 +17.201,rest_RT+LT,-20543.758,881.904,1004.383,658.802,0 +17.251,rest_RT+LT,-20618.707,881.904,1004.382,868.506,0 +17.302,rest_RT+LT,-20668.658,881.904,1004.382,992.555,0 +17.352,rest_RT+LT,-20718.607,881.904,1004.382,661.476,0 +17.402,rest_RT+LT,-20743.557,881.904,1004.382,415.321,0 +17.452,rest_RT+LT,-20751.857,881.904,1004.382,537.446,0 +17.502,rest_RT+LT,-20751.857,881.904,1004.382,661.645,0 +17.553,rest_RT+LT,-20826.807,881.904,1004.382,868.599,0 +17.603,rest_RT+LT,-20876.758,881.903,1004.382,991.449,0 +17.653,rest_RT+LT,-20926.707,881.903,1004.382,622.634,0 +17.704,rest_RT+LT,-20951.658,881.903,1004.382,373.526,0 +17.754,rest_RT+LT,-20951.658,881.903,1004.382,492.410,0 +17.804,rest_RT+LT,-20951.658,881.903,1004.382,616.444,0 +17.856,rest_RT+LT,-21026.607,881.903,1004.382,862.888,0 +17.906,rest_RT+LT,-21076.557,881.903,1004.381,1068.185,0 +17.956,rest_RT+LT,-21126.508,881.903,1004.381,706.486,0 +18.006,rest_RT+LT,-21168.107,881.903,1004.381,448.929,0 +18.056,rest_RT+LT,-21168.107,881.903,1004.381,571.558,0 +18.110,rest_RT+LT,-21168.107,881.903,1004.381,608.945,0 +18.160,rest_RT+LT,-21243.057,881.903,1004.381,852.374,0 +18.211,rest_RT+LT,-21293.008,881.903,1004.381,1033.055,0 +18.262,rest_RT+LT,-21342.957,881.903,1004.381,701.433,0 +18.312,rest_RT+LT,-21376.258,881.903,1004.381,456.518,0 +18.362,rest_RT+LT,-21384.557,881.903,1004.381,539.815,0 +18.412,rest_RT+LT,-21384.557,881.903,1004.381,581.271,0 +18.462,rest_RT+LT,-21451.207,881.903,1004.381,782.686,0 +18.512,rest_RT+LT,-21492.807,881.903,1004.381,1028.090,0 +18.564,rest_RT+LT,-21542.758,881.903,1004.380,730.579,0 +18.614,rest_RT+LT,-21592.707,881.903,1004.380,527.642,0 +18.667,rest_RT+LT,-21601.008,881.903,1004.380,614.629,0 +18.717,rest_RT+LT,-21601.008,881.903,1004.380,574.093,0 +18.767,rest_RT+LT,-21667.658,881.903,1004.380,789.318,0 +18.817,rest_RT+LT,-21709.258,881.903,1004.380,1038.534,0 +18.868,rest_RT+LT,-21759.207,881.903,1004.380,788.848,0 +18.918,rest_RT+LT,-21809.158,881.903,1004.380,581.694,0 +18.968,rest_RT+LT,-21825.758,881.903,1004.380,331.902,0 +19.018,rest_RT+LT,-21825.758,881.903,1004.380,581.079,0 +19.068,rest_RT+LT,-21825.758,881.903,1004.380,741.734,0 +19.118,rest_RT+LT,-21925.707,881.903,1004.380,974.007,0 +19.170,A,-21975.658,881.903,1004.380,1096.857,0 +19.223,A,-22025.607,881.903,1004.380,609.349,0 +19.273,A,-22050.557,881.903,1004.379,368.319,0 +19.323,A,-22050.557,881.903,1004.379,540.547,0 +19.373,A,-22050.557,881.903,1004.379,662.043,0 +19.423,A,-22133.857,881.903,1004.379,910.232,0 +19.474,A,-22183.807,881.903,1004.379,1109.274,0 +19.524,A,-22233.758,881.903,1004.379,689.936,0 +19.574,A,-22273.842,881.903,1004.379,446.054,0 +19.626,A,-22273.842,881.903,1004.379,517.671,0 +19.676,A,-22273.842,881.903,1004.379,518.796,0 +19.726,A,-22338.250,881.903,1004.379,708.181,0 +19.776,A,-22378.559,881.903,1004.379,822.629,0 +19.826,A,-22415.650,881.903,1004.379,526.993,0 +19.876,A,-22438.594,881.903,1004.379,326.370,0 +19.927,A,-22444.135,881.903,1004.379,358.310,0 +19.977,A,-22444.135,881.903,1004.379,333.426,0 +20.027,A,-22487.645,881.903,1004.379,389.239,0 +20.077,A,-22505.588,881.903,1004.379,499.538,0 +20.127,A,-22522.104,881.903,1004.379,299.202,0 +20.178,A,-22544.197,881.903,1004.378,209.656,0 +20.228,A,-22547.602,881.903,1004.378,235.199,0 +20.278,A,-22547.602,881.903,1004.378,184.668,0 +20.328,A,-22569.217,881.903,1004.378,224.568,0 +20.378,A,-22581.193,881.903,1004.378,266.117,0 +20.428,A,-22592.572,881.903,1004.378,166.425,0 +20.478,A,-22600.893,881.903,1004.378,106.644,0 +20.528,A,-22602.553,881.903,1004.378,124.675,0 +20.578,A,-22602.553,881.903,1004.378,133.014,0 +20.628,A,-22617.543,881.903,1004.378,174.606,0 +20.678,A,-22627.533,881.903,1004.378,207.803,0 +20.728,A,-22637.523,881.903,1004.378,132.959,0 +20.778,A,-22644.172,881.903,1004.378,83.078,0 +20.828,A,-22644.172,881.903,1004.378,107.215,0 +20.879,A,-22644.172,881.903,1004.378,123.669,0 +20.930,A,-22659.162,881.903,1004.378,173.088,0 +20.980,A,-22669.152,881.903,1004.378,206.000,0 +21.031,A,-22679.143,881.902,1004.378,132.713,0 +21.081,A,-22685.793,881.902,1004.378,83.000,0 +21.131,A,-22685.793,881.902,1004.378,115.572,0 +21.181,A,-22685.793,881.902,1004.378,132.131,0 +21.232,A,-22702.443,881.902,1004.378,181.840,0 +21.282,A,-22712.432,881.902,1004.378,190.085,0 +21.332,A,-22722.422,881.902,1004.378,108.049,0 +21.382,A,-22724.082,881.902,1004.378,58.172,0 +21.432,A,-22724.082,881.902,1004.378,99.763,0 +21.482,A,-22724.082,881.902,1004.378,141.352,0 +21.533,A,-22742.402,881.902,1004.378,191.224,0 +21.583,A,-22752.393,881.902,1004.378,206.933,0 +21.633,A,-22762.383,881.902,1004.378,115.847,0 +21.684,A,-22765.703,881.902,1004.378,124.149,0 +21.734,A,-22765.703,881.902,1004.378,115.842,0 +21.784,A,-22777.363,881.902,1004.378,149.635,0 +21.834,A,-22785.682,881.902,1004.378,191.184,0 +21.884,A,-22795.672,881.902,1004.378,132.966,0 +21.934,A,-22803.992,881.902,1004.378,91.431,0 +21.984,A,-22803.992,881.902,1004.378,106.474,0 +22.034,A,-22803.992,881.902,1004.378,114.689,0 +22.087,A,-22817.312,881.902,1004.378,180.150,0 +22.137,A,-22827.303,881.902,1004.378,188.314,0 +22.187,rest_A,-22840.623,881.902,1004.378,124.629,0 +22.237,rest_A,-22842.283,881.902,1004.378,74.766,0 +22.287,rest_A,-22842.283,881.902,1004.378,108.041,0 +22.338,rest_A,-22842.283,881.902,1004.378,149.632,0 +22.388,rest_A,-22862.262,881.902,1004.378,191.177,0 +22.438,rest_A,-22872.252,881.902,1004.378,199.464,0 +22.488,rest_A,-22880.572,881.902,1004.378,99.715,0 +22.538,rest_A,-22882.232,881.902,1004.378,49.617,0 +22.588,rest_A,-22882.232,881.902,1004.378,112.961,0 +22.639,rest_A,-22882.232,881.902,1004.378,121.062,0 +22.694,rest_A,-22903.883,881.902,1004.378,177.564,0 +22.744,rest_A,-22907.213,881.902,1004.378,243.141,0 +22.794,rest_A,-22918.873,881.902,1004.378,149.093,0 +22.844,rest_A,-22932.193,881.902,1004.378,132.528,0 +22.895,rest_A,-22933.854,881.902,1004.378,74.524,0 +22.945,rest_A,-22933.854,881.902,1004.378,8.259,0 +22.995,rest_A,-22933.854,881.902,1004.378,99.810,0 +23.045,rest_A,-22933.854,881.902,1004.378,116.063,0 +23.095,rest_A,-22953.842,881.902,1004.378,170.779,0 +23.146,rest_A,-22957.172,881.902,1004.378,235.849,0 +23.200,rest_A,-22968.832,881.902,1004.378,146.350,0 +23.250,rest_A,-22982.152,881.902,1004.378,129.955,0 +23.300,rest_A,-22983.812,881.902,1004.378,74.334,0 +23.351,rest_A,-22983.812,881.902,1004.378,90.892,0 +23.402,rest_A,-22983.812,881.902,1004.378,115.688,0 +23.452,rest_A,-23000.473,881.902,1004.378,173.879,0 +23.502,rest_A,-23007.133,881.902,1004.378,224.022,0 +23.552,rest_A,-23018.793,881.902,1004.378,148.783,0 +23.602,rest_A,-23028.783,881.902,1004.378,115.728,0 +23.653,rest_A,-23030.443,881.902,1004.378,124.296,0 +23.703,rest_A,-23030.443,881.902,1004.378,98.824,0 +23.753,rest_A,-23043.773,881.902,1004.377,139.857,0 +23.805,rest_A,-23048.762,881.902,1004.377,180.965,0 +23.856,rest_A,-23058.752,881.902,1004.377,123.310,0 +23.906,rest_A,-23067.072,881.902,1004.377,98.850,0 +23.956,rest_A,-23068.732,881.902,1004.377,131.896,0 +24.007,rest_A,-23068.732,881.902,1004.377,140.166,0 +24.057,rest_A,-23085.383,881.902,1004.377,181.418,0 +24.108,rest_A,-23095.373,881.902,1004.377,188.410,0 +24.158,rest_A,-23105.363,881.902,1004.377,106.913,0 +24.210,B,-23107.023,881.902,1004.377,115.154,0 +24.260,B,-23107.023,881.902,1004.377,123.382,0 +24.310,B,-23118.682,881.902,1004.377,166.039,0 +24.360,B,-23130.332,881.902,1004.377,190.328,0 +24.410,B,-23140.322,881.902,1004.377,132.255,0 +24.461,B,-23145.312,881.902,1004.377,74.076,0 +24.511,B,-23145.312,881.902,1004.377,98.952,0 +24.562,B,-23145.312,881.902,1004.377,122.385,0 +24.612,B,-23160.303,881.902,1004.377,171.488,0 +24.665,B,-23170.293,881.902,1004.377,196.758,0 +24.715,B,-23180.283,881.902,1004.377,122.163,0 +24.765,B,-23185.273,881.902,1004.377,74.314,0 +24.817,B,-23185.273,881.902,1004.377,107.341,0 +24.867,B,-23185.273,881.902,1004.377,148.664,0 +24.917,B,-23201.922,881.902,1004.377,191.178,0 +24.967,B,-23215.242,881.902,1004.377,199.477,0 +25.017,B,-23223.562,881.902,1004.377,115.866,0 +25.067,B,-23225.223,881.902,1004.377,115.916,0 +25.118,B,-23225.223,881.902,1004.377,115.917,0 +25.168,B,-23238.553,881.902,1004.377,157.336,0 +25.218,B,-23246.873,881.902,1004.377,199.525,0 +25.268,B,-23256.863,881.902,1004.377,132.910,0 +25.318,B,-23265.182,881.902,1004.377,91.380,0 +25.368,B,-23265.182,881.902,1004.377,116.335,0 +25.418,B,-23265.182,881.902,1004.377,124.462,0 +25.468,B,-23280.172,881.902,1004.377,174.299,0 +25.519,B,-23290.162,881.902,1004.377,198.774,0 +25.569,B,-23300.152,881.902,1004.377,123.861,0 +25.619,B,-23305.143,881.902,1004.377,73.708,0 +25.670,B,-23305.143,881.902,1004.377,98.313,0 +25.722,B,-23305.143,881.902,1004.377,122.635,0 +25.772,B,-23320.133,881.902,1004.377,172.156,0 +25.823,B,-23330.123,881.902,1004.377,190.359,0 +25.873,B,-23340.113,881.902,1004.377,115.835,0 +25.923,B,-23343.432,881.902,1004.377,66.454,0 +25.973,B,-23343.432,881.902,1004.377,107.822,0 +26.023,B,-23343.432,881.902,1004.377,157.657,0 +26.074,B,-23361.752,881.902,1004.377,190.838,0 +26.124,B,-23375.072,881.902,1004.377,198.104,0 +26.174,B,-23381.732,881.902,1004.377,107.507,0 +26.225,B,-23383.393,881.902,1004.377,115.800,0 +26.275,B,-23383.393,881.902,1004.377,115.800,0 +26.325,B,-23398.383,881.902,1004.377,157.982,0 +26.375,B,-23405.043,881.902,1004.377,207.805,0 +26.425,B,-23415.033,881.902,1004.377,132.963,0 +26.475,B,-23425.012,881.902,1004.377,99.709,0 +26.526,B,-23425.012,881.902,1004.377,124.671,0 +26.576,B,-23425.012,881.902,1004.377,124.727,0 +26.626,B,-23440.002,881.902,1004.377,174.606,0 +26.676,B,-23449.992,881.902,1004.377,216.140,0 +26.726,B,-23459.982,881.902,1004.377,140.632,0 +26.776,B,-23468.303,881.902,1004.377,90.994,0 +26.827,B,-23468.303,881.902,1004.377,41.348,0 +26.877,B,-23468.303,881.902,1004.377,99.342,0 +26.927,B,-23468.303,881.902,1004.377,132.454,0 +26.977,B,-23488.293,881.902,1004.377,132.455,0 +27.028,B,-23494.953,881.902,1004.377,206.960,0 +27.078,B,-23494.953,881.902,1004.377,157.251,0 +27.128,B,-23509.943,881.902,1004.377,139.534,0 +27.178,B,-23519.932,881.902,1004.376,139.285,0 +27.231,rest_B,-23523.252,881.902,1004.376,65.507,0 +27.281,rest_B,-23523.252,881.902,1004.376,16.344,0 +27.331,rest_B,-23523.252,881.902,1004.376,0.000,0 +27.381,rest_B,-23523.252,881.902,1004.376,0.000,0 +27.432,rest_B,-23523.252,881.902,1004.376,156.901,0 +27.483,rest_B,-23523.252,881.902,1004.376,185.433,0 +27.533,rest_B,-23554.912,881.902,1004.376,218.143,0 +27.588,rest_B,-23561.572,881.902,1004.376,218.827,0 +27.638,rest_B,-23568.232,881.902,1004.376,89.322,0 +27.688,rest_B,-23568.232,881.902,1004.376,116.459,0 +27.738,rest_B,-23573.232,881.902,1004.376,115.477,0 +27.788,rest_B,-23584.893,881.902,1004.376,148.023,0 +27.840,rest_B,-23591.553,881.902,1004.376,123.322,0 +27.891,rest_B,-23598.203,881.902,1004.376,65.736,0 +27.941,rest_B,-23598.203,881.902,1004.376,124.235,0 +27.991,rest_B,-23598.203,881.902,1004.376,141.203,0 +28.041,rest_B,-23616.523,881.902,1004.376,191.035,0 +28.091,rest_B,-23626.512,881.902,1004.376,206.778,0 +28.141,rest_B,-23636.502,881.902,1004.376,115.818,0 +28.192,rest_B,-23639.822,881.902,1004.376,132.431,0 +28.242,rest_B,-23639.822,881.902,1004.376,107.410,0 +28.292,rest_B,-23653.152,881.902,1004.376,157.666,0 +28.343,rest_B,-23658.143,881.902,1004.376,199.224,0 +28.393,rest_B,-23671.463,881.902,1004.376,141.043,0 +28.443,rest_B,-23679.783,881.902,1004.376,116.345,0 +28.493,rest_B,-23681.443,881.902,1004.376,124.674,0 +28.543,rest_B,-23681.443,881.902,1004.376,133.023,0 +28.593,rest_B,-23696.432,881.902,1004.376,174.619,0 +28.643,rest_B,-23706.422,881.902,1004.376,207.829,0 +28.693,rest_B,-23716.412,881.902,1004.376,132.767,0 +28.743,rest_B,-23723.062,881.902,1004.376,82.833,0 +28.794,rest_B,-23723.062,881.902,1004.376,107.720,0 +28.844,rest_B,-23723.062,881.902,1004.376,140.837,0 +28.894,rest_B,-23738.053,881.902,1004.376,174.294,0 +28.944,rest_B,-23751.373,881.902,1004.376,199.450,0 +28.994,rest_B,-23758.033,881.902,1004.376,124.215,0 +29.044,rest_B,-23763.023,881.902,1004.376,57.987,0 +29.095,rest_B,-23763.023,881.902,1004.376,115.493,0 +29.145,rest_B,-23763.023,881.902,1004.376,139.121,0 +29.196,rest_B,-23781.342,881.902,1004.376,187.845,0 +29.248,X,-23791.332,881.902,1004.376,201.131,0 +29.299,X,-23801.322,881.902,1004.376,113.072,0 +29.352,X,-23804.643,881.902,1004.376,130.334,0 +29.402,X,-23804.643,881.902,1004.376,122.816,0 +29.452,X,-23817.973,881.902,1004.376,157.536,0 +29.502,X,-23826.293,881.902,1004.376,198.954,0 +29.553,X,-23836.283,881.902,1004.376,140.202,0 +29.603,X,-23844.602,881.902,1004.376,98.964,0 +29.654,X,-23846.262,881.902,1004.376,124.126,0 +29.704,X,-23846.262,881.902,1004.376,132.445,0 +29.754,X,-23861.252,881.902,1004.376,174.664,0 +29.804,X,-23871.242,881.902,1004.376,216.223,0 +29.854,X,-23881.232,881.902,1004.376,141.322,0 +29.904,X,-23889.553,881.902,1004.376,90.539,0 +29.954,X,-23889.553,881.902,1004.376,115.262,0 +30.006,X,-23889.553,881.902,1004.376,123.517,0 +30.057,X,-23904.543,881.902,1004.376,156.445,0 +30.107,X,-23914.533,881.902,1004.376,207.854,0 +30.157,X,-23921.193,881.902,1004.376,139.876,0 +30.207,X,-23931.182,881.902,1004.376,90.504,0 +30.259,X,-23932.842,881.902,1004.376,57.585,0 +30.309,X,-23932.842,881.902,1004.376,8.199,0 +30.359,X,-23932.842,881.902,1004.376,124.673,0 +30.409,X,-23932.842,881.902,1004.376,156.484,0 +30.459,X,-23957.832,881.902,1004.376,156.481,0 +30.511,X,-23964.492,881.902,1004.376,156.645,0 +30.561,X,-23964.492,881.902,1004.376,115.418,0 +30.611,X,-23964.492,881.902,1004.376,149.712,0 +30.661,X,-23981.152,881.902,1004.376,157.996,0 +30.711,X,-23994.473,881.902,1004.376,157.511,0 +30.761,X,-23996.133,881.902,1004.376,74.578,0 +30.812,X,-23996.133,881.902,1004.376,8.265,0 +30.862,X,-23996.133,881.902,1004.376,0.000,0 +30.912,X,-23996.133,881.902,1004.376,0.000,0 +30.962,X,-23996.133,881.902,1004.376,147.772,0 +31.012,X,-23996.133,881.902,1004.376,188.815,0 +31.065,X,-24026.123,881.902,1004.375,188.821,0 +31.115,X,-24034.453,881.902,1004.375,188.828,0 +31.165,X,-24034.453,881.902,1004.375,149.707,0 +31.215,X,-24034.453,881.902,1004.375,157.992,0 +31.265,X,-24056.102,881.902,1004.375,163.932,0 +31.315,X,-24066.092,881.902,1004.375,163.929,0 +31.368,X,-24067.752,881.902,1004.375,57.357,0 +31.418,X,-24067.752,881.902,1004.375,8.161,0 +31.469,X,-24067.752,881.902,1004.375,0.000,0 +31.519,X,-24067.752,881.902,1004.375,139.719,0 +31.569,X,-24067.752,881.902,1004.375,156.148,0 +31.621,X,-24096.072,881.902,1004.375,154.957,0 +31.671,X,-24099.402,881.902,1004.375,187.571,0 +31.723,X,-24099.402,881.902,1004.375,113.929,0 +31.773,X,-24106.062,881.902,1004.375,146.474,0 +31.826,X,-24119.383,881.902,1004.375,155.250,0 +31.876,X,-24129.373,881.902,1004.375,122.559,0 +31.927,X,-24131.033,881.902,1004.375,57.892,0 +31.977,X,-24131.033,881.902,1004.375,8.047,0 +32.027,X,-24131.033,881.902,1004.375,0.000,0 +32.082,X,-24131.033,881.902,1004.375,0.000,0 +32.132,X,-24131.033,881.902,1004.375,162.262,0 +32.182,X,-24131.033,881.902,1004.375,198.851,0 +32.232,X,-24164.352,881.902,1004.375,198.851,0 +32.283,rest_X,-24171.012,881.902,1004.375,198.861,0 +32.333,rest_X,-24171.012,881.902,1004.375,74.562,0 +32.383,rest_X,-24171.012,881.902,1004.375,133.070,0 +32.433,rest_X,-24179.342,881.902,1004.375,174.606,0 +32.483,rest_X,-24197.662,881.902,1004.375,182.388,0 +32.534,rest_X,-24205.982,881.902,1004.375,140.920,0 +32.584,rest_X,-24207.643,881.902,1004.375,115.456,0 +32.634,rest_X,-24207.643,881.902,1004.375,131.948,0 +32.685,rest_X,-24220.973,881.902,1004.375,172.462,0 +32.735,rest_X,-24232.623,881.902,1004.375,205.296,0 +32.787,rest_X,-24242.613,881.902,1004.375,148.057,0 +32.837,rest_X,-24249.271,881.902,1004.375,90.482,0 +32.888,rest_X,-24250.932,881.902,1004.375,107.173,0 +32.938,rest_X,-24250.932,881.902,1004.375,131.889,0 +32.989,rest_X,-24264.262,881.902,1004.375,157.112,0 +33.039,rest_X,-24275.912,881.902,1004.375,198.444,0 +33.089,rest_X,-24282.572,881.902,1004.375,141.244,0 +33.139,rest_X,-24290.893,881.902,1004.375,83.078,0 +33.189,rest_X,-24292.553,881.902,1004.375,124.696,0 +33.239,rest_X,-24292.553,881.902,1004.375,133.029,0 +33.289,rest_X,-24307.543,881.902,1004.375,174.608,0 +33.339,rest_X,-24317.533,881.902,1004.375,216.154,0 +33.389,rest_X,-24327.521,881.902,1004.375,141.309,0 +33.440,rest_X,-24335.842,881.902,1004.375,91.420,0 +33.490,rest_X,-24335.842,881.902,1004.375,116.396,0 +33.540,rest_X,-24335.842,881.902,1004.375,107.678,0 +33.590,rest_X,-24350.832,881.902,1004.375,173.874,0 +33.641,rest_X,-24357.492,881.902,1004.375,215.242,0 +33.691,rest_X,-24370.812,881.902,1004.375,140.524,0 +33.741,rest_X,-24379.133,881.902,1004.375,107.881,0 +33.791,rest_X,-24379.133,881.902,1004.375,107.218,0 +33.841,rest_X,-24379.133,881.902,1004.375,107.218,0 +33.893,rest_X,-24392.463,881.902,1004.375,156.144,0 +33.943,rest_X,-24400.783,881.902,1004.375,205.448,0 +33.994,rest_X,-24410.771,881.902,1004.375,148.696,0 +34.044,rest_X,-24420.762,881.902,1004.375,107.401,1 +34.094,rest_X,-24422.422,881.902,1004.375,124.584,0 +34.144,rest_X,-24422.422,881.902,1004.375,115.284,0 +34.194,rest_X,-24435.752,881.902,1004.375,147.306,0 +34.244,rest_X,-24443.877,881.901,1004.375,187.290,1 +34.301,Y,-24452.916,881.897,1004.384,130.004,0 +34.352,Y,-24461.385,881.874,1004.423,90.947,0 +34.402,Y,-24462.797,881.867,1004.433,76.968,0 +34.452,Y,-24462.797,881.867,1004.433,46.360,1 +34.502,Y,-24468.408,881.819,1004.501,75.651,0 +34.553,Y,-24470.697,881.786,1004.546,102.301,0 +34.603,Y,-24477.988,881.599,1004.810,80.024,0 +34.653,Y,-24483.334,881.397,1005.090,68.854,1 +34.703,Y,-24484.465,881.347,1005.157,59.888,0 +34.753,Y,-24484.465,881.347,1005.157,51.953,0 +34.803,Y,-24489.947,881.056,1005.523,73.172,0 +34.855,Y,-24493.777,880.756,1005.807,104.241,1 +34.905,Y,-24499.148,880.135,1006.213,81.436,0 +34.955,Y,-24505.348,879.201,1006.645,63.140,0 +35.008,Y,-24506.447,879.019,1006.720,36.363,0 +35.058,Y,-24506.447,879.019,1006.720,5.489,1 +35.109,Y,-24506.447,879.019,1006.720,12.107,0 +35.159,Y,-24506.447,879.019,1006.720,17.440,0 +35.209,Y,-24508.840,878.592,1006.883,24.480,0 +35.259,Y,-24509.852,878.205,1006.963,41.265,1 +35.311,Y,-24511.158,877.516,1007.042,48.664,0 +35.362,Y,-24514.039,875.508,1007.004,54.236,0 +35.412,Y,-24516.955,873.022,1006.558,47.287,0 +35.462,Y,-24518.537,871.546,1006.126,30.101,1 +35.513,Y,-24518.537,871.546,1006.126,10.964,0 +35.563,Y,-24518.537,871.546,1006.126,0.000,0 +35.613,Y,-24518.537,871.546,1006.126,0.000,0 +35.663,Y,-24518.537,871.546,1006.126,48.660,1 +35.713,Y,-24518.537,871.546,1006.126,62.767,0 +35.763,Y,-24525.027,864.970,1003.028,62.760,0 +35.813,Y,-24526.934,863.368,1001.583,77.729,0 +35.863,Y,-24526.934,863.368,1001.583,30.518,1 +35.915,Y,-24529.127,862.056,999.462,49.579,0 +35.965,Y,-24529.127,862.056,999.462,65.654,0 +36.015,Y,-24533.783,860.510,994.857,71.628,0 +36.065,Y,-24536.223,860.195,992.662,70.679,0 +36.115,Y,-24539.699,860.130,989.961,37.885,0 +36.168,Y,-24539.699,860.130,989.961,51.364,0 +36.218,Y,-24539.699,860.130,989.961,39.505,0 +36.268,Y,-24544.887,860.479,986.872,69.710,0 +36.321,Y,-24546.756,860.560,985.951,94.656,0 +36.371,Y,-24552.582,860.236,984.094,79.472,0 +36.421,Y,-24557.594,859.143,983.011,70.541,0 +36.471,Y,-24560.250,858.277,982.448,40.365,0 +36.521,Y,-24560.250,858.277,982.448,65.422,0 +36.571,Y,-24560.250,858.277,982.448,68.602,0 +36.622,Y,-24569.068,853.555,979.942,112.835,0 +36.673,Y,-24572.057,851.903,978.941,153.080,1 +36.724,Y,-24579.379,847.880,975.641,122.275,0 +36.774,Y,-24586.037,844.721,972.011,104.886,0 +36.824,Y,-24589.316,843.416,970.146,60.514,0 +36.875,Y,-24589.316,843.416,970.146,42.885,1 +36.925,Y,-24589.316,843.416,970.146,36.776,0 +36.975,Y,-24593.232,842.168,967.950,49.776,0 +37.028,Y,-24595.805,841.618,966.827,121443.130,0 +37.078,Y,-24598.303,841.243,966.099,120794.743,0 +37.128,Y,0.000,0.000,1.875,122290.801,0 +37.179,Y,0.000,0.000,1.875,122316.884,0 +37.229,Y,0.000,0.000,1.875,0.000,0 +37.279,Y,0.000,0.000,1.875,0.000,0 +37.331,rest_Y,0.000,0.000,1.875,0.000,0 +37.381,rest_Y,0.000,0.000,1.875,0.000,0 +37.431,rest_Y,0.000,0.000,1.875,0.000,0 +37.482,rest_Y,0.000,0.000,1.875,0.000,0 +37.532,rest_Y,0.000,0.000,1.875,0.000,0 +37.582,rest_Y,0.000,0.000,1.875,0.000,0 +37.636,rest_Y,0.000,0.000,1.875,0.000,0 +37.686,rest_Y,0.000,0.000,1.875,0.000,0 +37.736,rest_Y,0.000,0.000,1.875,0.000,0 +37.787,rest_Y,0.000,0.000,1.875,0.000,0 +37.840,rest_Y,0.000,0.000,1.875,0.000,0 +37.890,rest_Y,0.000,0.000,1.875,0.000,0 +37.940,rest_Y,0.000,0.000,1.875,0.000,0 +37.990,rest_Y,0.000,0.000,1.875,0.000,0 +38.040,rest_Y,0.000,0.000,1.875,0.000,0 +38.092,rest_Y,0.000,0.000,1.875,0.000,0 +38.142,rest_Y,0.000,0.000,1.875,0.000,0 +38.192,rest_Y,0.000,0.000,1.875,0.000,0 +38.243,rest_Y,0.000,0.000,1.875,0.000,0 +38.295,rest_Y,0.000,0.000,1.875,0.000,0 +38.347,rest_Y,0.000,0.000,1.875,0.000,0 +38.397,rest_Y,0.000,0.000,1.875,0.000,0 +38.447,rest_Y,0.000,0.000,1.875,0.000,0 +38.497,rest_Y,0.000,0.000,1.875,0.000,0 +38.547,rest_Y,0.000,0.000,1.875,0.000,0 +38.599,rest_Y,0.000,0.000,1.875,0.000,0 +38.649,rest_Y,0.000,0.000,1.875,0.000,0 +38.699,rest_Y,0.000,0.000,1.875,0.000,0 +38.749,rest_Y,0.000,0.000,1.875,0.000,0 +38.799,rest_Y,0.000,0.000,1.875,0.000,0 +38.849,rest_Y,0.000,0.000,1.875,0.000,0 +38.900,rest_Y,0.000,0.000,1.875,0.000,0 +38.950,rest_Y,0.000,0.000,1.875,0.000,0 +39.000,rest_Y,0.000,0.000,1.875,0.000,0 +39.050,rest_Y,0.000,0.000,1.875,0.000,0 +39.100,rest_Y,0.000,0.000,1.875,0.000,0 +39.151,rest_Y,0.000,0.000,1.875,0.000,0 +39.201,rest_Y,0.000,0.000,1.875,0.000,0 +39.252,rest_Y,0.000,0.000,1.875,0.000,0 +39.302,rest_Y,0.000,0.000,1.875,0.000,0 +39.359,LB,0.000,0.000,1.875,0.000,0 +39.409,LB,0.000,0.000,1.875,0.000,0 +39.459,LB,0.000,0.000,1.875,0.000,0 +39.509,LB,0.000,0.000,1.875,0.000,0 +39.559,LB,0.000,0.000,1.875,0.000,0 +39.609,LB,0.000,0.000,1.875,0.000,0 +39.661,LB,0.000,0.000,1.875,0.000,0 +39.711,LB,0.000,0.000,1.875,0.000,0 +39.761,LB,0.000,0.000,1.875,0.000,0 +39.811,LB,0.000,0.000,1.875,0.000,0 +39.861,LB,0.000,0.000,1.875,0.000,0 +39.911,LB,0.000,0.000,1.875,0.000,0 +39.962,LB,0.000,0.000,1.875,0.000,0 +40.012,LB,0.000,0.000,1.875,0.000,0 +40.062,LB,0.000,0.000,1.875,0.000,0 +40.112,LB,0.000,0.000,1.875,0.000,0 +40.162,LB,0.000,0.000,1.875,0.000,0 +40.213,LB,0.000,0.000,1.875,0.000,0 +40.263,LB,0.000,0.000,1.875,0.000,0 +40.313,LB,0.000,0.000,1.875,0.000,0 +40.363,LB,0.000,0.000,1.875,0.000,0 +40.413,LB,0.000,0.000,1.875,0.000,0 +40.463,LB,0.000,0.000,1.875,0.000,0 +40.514,LB,0.000,0.000,1.875,0.000,0 +40.564,LB,0.000,0.000,1.875,0.000,0 +40.614,LB,0.000,0.000,1.875,0.000,0 +40.664,LB,0.000,0.000,1.875,0.000,0 +40.714,LB,0.000,0.000,1.875,0.000,0 +40.765,LB,0.000,0.000,1.875,0.000,0 +40.815,LB,0.000,0.000,1.875,0.000,0 +40.865,LB,0.000,0.000,1.875,0.000,0 +40.916,LB,0.000,0.000,1.875,0.000,0 +40.966,LB,0.000,0.000,1.875,0.000,0 +41.016,LB,0.000,0.000,1.875,0.000,0 +41.066,LB,0.000,0.000,1.875,0.000,0 +41.116,LB,0.000,0.000,1.875,0.000,0 +41.166,LB,0.000,0.000,1.875,0.000,0 +41.216,LB,0.000,0.000,1.875,0.000,0 +41.266,LB,0.000,0.000,1.875,0.000,0 +41.316,LB,0.000,0.000,1.875,0.000,0 +41.368,LB,0.000,0.000,1.875,0.000,0 +41.418,LB,0.000,0.000,1.875,0.000,0 +41.468,LB,0.000,0.000,1.875,0.000,0 +41.521,LB,0.000,0.000,1.875,0.000,0 +41.571,LB,0.000,0.000,1.875,0.000,0 +41.623,LB,0.000,0.000,1.875,0.000,0 +41.673,LB,0.000,0.000,1.875,0.000,0 +41.724,LB,0.000,0.000,1.875,0.000,0 +41.774,LB,0.000,0.000,1.875,0.000,0 +41.824,LB,0.000,0.000,1.875,0.000,0 +41.874,LB,0.000,0.000,1.875,0.000,0 +41.924,LB,0.000,0.000,1.875,0.000,0 +41.976,LB,0.000,0.000,1.875,0.000,0 +42.026,LB,0.000,0.000,1.875,0.000,0 +42.076,LB,0.000,0.000,1.875,0.000,0 +42.126,LB,0.000,0.000,1.875,0.000,0 +42.179,LB,0.000,0.000,1.875,0.000,0 +42.229,LB,0.000,0.000,1.875,0.000,0 +42.279,LB,0.000,0.000,1.875,0.000,0 +42.329,LB,0.000,0.000,1.875,0.000,0 +42.380,rest_LB,0.000,0.000,1.875,0.000,0 +42.430,rest_LB,0.000,0.000,1.875,0.000,0 +42.481,rest_LB,0.000,0.000,1.875,0.000,0 +42.531,rest_LB,0.000,0.000,1.875,0.000,0 +42.582,rest_LB,0.000,0.000,1.875,0.000,0 +42.632,rest_LB,0.000,0.000,1.875,0.000,0 +42.682,rest_LB,0.000,0.000,1.875,0.000,0 +42.732,rest_LB,0.000,0.000,1.875,0.000,0 +42.782,rest_LB,0.000,0.000,1.875,0.000,0 +42.833,rest_LB,0.000,0.000,1.875,0.000,0 +42.883,rest_LB,0.000,0.000,1.875,0.000,0 +42.933,rest_LB,0.000,0.000,1.875,0.000,0 +42.983,rest_LB,0.000,0.000,1.875,0.000,0 +43.033,rest_LB,0.000,0.000,1.875,0.000,0 +43.084,rest_LB,0.000,0.000,1.875,0.000,0 +43.134,rest_LB,0.000,0.000,1.875,0.000,0 +43.185,rest_LB,0.000,0.000,1.875,0.000,0 +43.235,rest_LB,0.000,0.000,1.875,0.000,0 +43.285,rest_LB,0.000,0.000,1.875,0.000,0 +43.336,rest_LB,0.000,0.000,1.875,0.000,0 +43.386,rest_LB,0.000,0.000,1.875,0.000,0 +43.436,rest_LB,0.000,0.000,1.875,0.000,0 +43.486,rest_LB,0.000,0.000,1.875,0.000,0 +43.538,rest_LB,0.000,0.000,1.875,0.000,0 +43.588,rest_LB,0.000,0.000,1.875,0.000,0 +43.639,rest_LB,0.000,0.000,1.875,0.000,0 +43.689,rest_LB,0.000,0.000,1.875,0.000,0 +43.739,rest_LB,0.000,0.000,1.875,0.000,0 +43.792,rest_LB,0.000,0.000,1.875,0.000,0 +43.842,rest_LB,0.000,0.000,1.875,0.000,0 +43.892,rest_LB,0.000,0.000,1.875,0.000,0 +43.942,rest_LB,0.000,0.000,1.875,0.000,0 +43.992,rest_LB,0.000,0.000,1.875,0.000,0 +44.042,rest_LB,0.000,0.000,1.875,0.000,0 +44.093,rest_LB,0.000,0.000,1.875,0.000,0 +44.144,rest_LB,0.000,0.000,1.875,0.000,0 +44.194,rest_LB,0.000,0.000,1.875,0.000,0 +44.244,rest_LB,0.000,0.000,1.875,0.000,0 +44.294,rest_LB,0.000,0.000,1.875,0.000,0 +44.345,rest_LB,0.000,0.000,1.875,0.000,0 +44.397,LS,0.000,0.000,1.875,0.000,0 +44.447,LS,0.000,0.000,1.875,0.000,0 +44.500,LS,0.000,0.000,1.875,0.000,0 +44.550,LS,0.000,0.000,1.875,0.000,0 +44.602,LS,0.000,0.000,1.875,0.000,0 +44.653,LS,0.000,0.000,1.875,0.000,0 +44.703,LS,0.000,0.000,1.875,0.000,0 +44.753,LS,0.000,0.000,1.875,0.000,0 +44.803,LS,0.000,0.000,1.875,0.000,0 +44.853,LS,0.000,0.000,1.875,0.000,0 +44.903,LS,0.000,0.000,1.875,0.000,0 +44.953,LS,0.000,0.000,1.875,0.000,0 +45.004,LS,0.000,0.000,1.875,0.000,0 +45.054,LS,0.000,0.000,1.875,0.000,0 +45.104,LS,0.000,0.000,1.875,0.000,0 +45.154,LS,0.000,0.000,1.875,0.000,0 +45.204,LS,0.000,0.000,1.875,0.000,0 +45.254,LS,0.000,0.000,1.875,0.000,0 +45.304,LS,0.000,0.000,1.875,0.000,0 +45.357,LS,0.000,0.000,1.875,0.000,0 +45.407,LS,0.000,0.000,1.875,0.000,0 +45.457,LS,0.000,0.000,1.875,0.000,0 +45.508,LS,0.000,0.000,1.875,0.000,0 +45.558,LS,0.000,0.000,1.875,0.000,0 +45.608,LS,0.000,0.000,1.875,0.000,0 +45.658,LS,0.000,0.000,1.875,0.000,0 +45.709,LS,0.000,0.000,1.875,0.000,0 +45.759,LS,0.000,0.000,1.875,0.000,0 +45.812,LS,0.000,0.000,1.875,0.000,0 +45.862,LS,0.000,0.000,1.875,0.000,0 +45.912,LS,0.000,0.000,1.875,0.000,0 +45.963,LS,0.000,0.000,1.875,0.000,0 +46.014,LS,0.000,0.000,1.875,0.000,0 +46.064,LS,0.000,0.000,1.875,0.000,0 +46.114,LS,0.000,0.000,1.875,0.000,0 +46.165,LS,0.000,0.000,1.875,0.000,0 +46.215,LS,0.000,0.000,1.875,0.000,0 +46.265,LS,0.000,0.000,1.875,0.000,0 +46.315,LS,0.000,0.000,1.875,0.000,0 +46.365,LS,0.000,0.000,1.875,0.000,0 +46.415,LS,0.000,0.000,1.875,0.000,0 +46.466,LS,0.000,0.000,1.875,0.000,0 +46.516,LS,0.000,0.000,1.875,0.000,0 +46.566,LS,0.000,0.000,1.875,0.000,0 +46.616,LS,0.000,0.000,1.875,0.000,0 +46.667,LS,0.000,0.000,1.875,0.000,0 +46.717,LS,0.000,0.000,1.875,0.000,0 +46.767,LS,0.000,0.000,1.875,0.000,0 +46.817,LS,0.000,0.000,1.875,0.000,0 +46.867,LS,0.000,0.000,1.875,0.000,0 +46.917,LS,0.000,0.000,1.875,0.000,0 +46.968,LS,0.000,0.000,1.875,0.000,0 +47.018,LS,0.000,0.000,1.875,0.000,0 +47.068,LS,0.000,0.000,1.875,0.000,0 +47.118,LS,0.000,0.000,1.875,0.000,0 +47.169,LS,0.000,0.000,1.875,0.000,0 +47.219,LS,0.000,0.000,1.875,0.000,0 +47.269,LS,0.000,0.000,1.875,0.000,0 +47.320,LS,0.000,0.000,1.875,0.000,0 +47.370,LS,0.000,0.000,1.875,0.000,0 +47.420,rest_LS,0.000,0.000,1.875,0.000,0 +47.471,rest_LS,0.000,0.000,1.875,0.000,0 +47.521,rest_LS,0.000,0.000,1.875,0.000,0 +47.571,rest_LS,0.000,0.000,1.875,0.000,0 +47.621,rest_LS,0.000,0.000,1.875,0.000,0 +47.672,rest_LS,0.000,0.000,1.875,0.000,0 +47.722,rest_LS,0.000,0.000,1.875,0.000,0 +47.772,rest_LS,0.000,0.000,1.875,0.000,0 +47.822,rest_LS,0.000,0.000,1.875,0.000,0 +47.872,rest_LS,0.000,0.000,1.875,0.000,0 +47.923,rest_LS,0.000,0.000,1.875,0.000,0 +47.973,rest_LS,0.000,0.000,1.875,0.000,0 +48.023,rest_LS,0.000,0.000,1.875,0.000,0 +48.073,rest_LS,0.000,0.000,1.875,0.000,0 +48.123,rest_LS,0.000,0.000,1.875,0.000,0 +48.173,rest_LS,0.000,0.000,1.875,0.000,0 +48.224,rest_LS,0.000,0.000,1.875,0.000,0 +48.275,rest_LS,0.000,0.000,1.875,0.000,0 +48.326,rest_LS,0.000,0.000,1.875,0.000,0 +48.376,rest_LS,0.000,0.000,1.875,0.000,0 +48.426,rest_LS,0.000,0.000,1.875,0.000,0 +48.476,rest_LS,0.000,0.000,1.875,0.000,0 +48.526,rest_LS,0.000,0.000,1.875,0.000,0 +48.576,rest_LS,0.000,0.000,1.875,0.000,0 +48.626,rest_LS,0.000,0.000,1.875,0.000,0 +48.676,rest_LS,0.000,0.000,1.875,0.000,0 +48.726,rest_LS,0.000,0.000,1.875,0.000,0 +48.776,rest_LS,0.000,0.000,1.875,0.000,0 +48.826,rest_LS,0.000,0.000,1.875,0.000,0 +48.877,rest_LS,0.000,0.000,1.875,0.000,0 +48.927,rest_LS,0.000,0.000,1.875,0.000,0 +48.977,rest_LS,0.000,0.000,1.875,0.000,0 +49.027,rest_LS,0.000,0.000,1.875,0.000,0 +49.078,rest_LS,0.000,0.000,1.875,0.000,0 +49.128,rest_LS,0.000,0.000,1.875,0.000,0 +49.178,rest_LS,0.000,0.000,1.875,0.000,0 +49.228,rest_LS,0.000,0.000,1.875,0.000,0 +49.278,rest_LS,0.000,0.000,1.875,0.000,0 +49.328,rest_LS,0.000,0.000,1.875,0.000,0 +49.378,rest_LS,0.000,0.000,1.875,0.000,0 +49.432,RS,0.000,0.000,1.875,0.000,0 +49.482,RS,0.000,0.000,1.875,0.000,0 +49.532,RS,0.000,0.000,1.875,0.000,0 +49.582,RS,0.000,0.000,1.875,0.000,0 +49.632,RS,0.000,0.000,1.875,0.000,0 +49.682,RS,0.000,0.000,1.875,0.000,0 +49.732,RS,0.000,0.000,1.875,0.000,0 +49.782,RS,0.000,0.000,1.875,0.000,0 +49.832,RS,0.000,0.000,1.875,0.000,0 +49.882,RS,0.000,0.000,1.875,0.000,0 +49.933,RS,0.000,0.000,1.875,0.000,0 +49.984,RS,0.000,0.000,1.875,0.000,0 +50.034,RS,0.000,0.000,1.875,0.000,0 +50.084,RS,0.000,0.000,1.875,0.000,0 +50.134,RS,0.000,0.000,1.875,0.000,0 +50.185,RS,0.000,0.000,1.875,0.000,0 +50.235,RS,0.000,0.000,1.875,0.000,0 +50.285,RS,0.000,0.000,1.875,0.000,0 +50.335,RS,0.000,0.000,1.875,0.000,0 +50.385,RS,0.000,0.000,1.875,0.000,0 +50.435,RS,0.000,0.000,1.875,0.000,0 +50.486,RS,0.000,0.000,1.875,0.000,0 +50.537,RS,0.000,0.000,1.875,0.000,0 +50.587,RS,0.000,0.000,1.875,0.000,0 +50.637,RS,0.000,0.000,1.875,0.000,0 +50.687,RS,0.000,0.000,1.875,0.000,0 +50.737,RS,0.000,0.000,1.875,0.000,0 +50.788,RS,0.000,0.000,1.875,0.000,0 +50.838,RS,0.000,0.000,1.875,0.000,0 +50.889,RS,0.000,0.000,1.875,0.000,0 +50.939,RS,0.000,0.000,1.875,0.000,0 +50.989,RS,0.000,0.000,1.875,0.000,0 +51.039,RS,0.000,0.000,1.875,0.000,0 +51.089,RS,0.000,0.000,1.875,0.000,0 +51.139,RS,0.000,0.000,1.875,0.000,0 +51.189,RS,0.000,0.000,1.875,0.000,0 +51.240,RS,0.000,0.000,1.875,0.000,0 +51.290,RS,0.000,0.000,1.875,0.000,0 +51.340,RS,0.000,0.000,1.875,0.000,0 +51.390,RS,0.000,0.000,1.875,0.000,0 +51.440,RS,0.000,0.000,1.875,0.000,0 +51.491,RS,0.000,0.000,1.875,0.000,0 +51.541,RS,0.000,0.000,1.875,0.000,0 +51.591,RS,0.000,0.000,1.875,0.000,0 +51.641,RS,0.000,0.000,1.875,0.000,0 +51.691,RS,0.000,0.000,1.875,0.000,0 +51.741,RS,0.000,0.000,1.875,0.000,0 +51.791,RS,0.000,0.000,1.875,0.000,0 +51.842,RS,0.000,0.000,1.875,0.000,0 +51.892,RS,0.000,0.000,1.875,0.000,0 +51.942,RS,0.000,0.000,1.875,0.000,0 +51.992,RS,0.000,0.000,1.875,0.000,0 +52.042,RS,0.000,0.000,1.875,0.000,0 +52.092,RS,0.000,0.000,1.875,0.000,0 +52.143,RS,0.000,0.000,1.875,0.000,0 +52.193,RS,0.000,0.000,1.875,0.000,0 +52.243,RS,0.000,0.000,1.875,0.000,0 +52.293,RS,0.000,0.000,1.875,0.000,0 +52.343,RS,0.000,0.000,1.875,0.000,0 +52.393,RS,0.000,0.000,1.875,0.000,0 +52.444,rest_RS,0.000,0.000,1.875,0.000,0 +52.494,rest_RS,0.000,0.000,1.875,0.000,0 +52.544,rest_RS,0.000,0.000,1.875,0.000,0 +52.594,rest_RS,0.000,0.000,1.875,0.000,0 +52.644,rest_RS,0.000,0.000,1.875,0.000,0 +52.694,rest_RS,0.000,0.000,1.875,0.000,0 +52.744,rest_RS,0.000,0.000,1.875,0.000,0 +52.795,rest_RS,0.000,0.000,1.875,0.000,0 +52.845,rest_RS,0.000,0.000,1.875,0.000,0 +52.895,rest_RS,0.000,0.000,1.875,0.000,0 +52.945,rest_RS,0.000,0.000,1.875,0.000,0 +52.996,rest_RS,0.000,0.000,1.875,0.000,0 +53.046,rest_RS,0.000,0.000,1.875,0.000,0 +53.096,rest_RS,0.000,0.000,1.875,0.000,0 +53.146,rest_RS,0.000,0.000,1.875,0.000,0 +53.196,rest_RS,0.000,0.000,1.875,0.000,0 +53.247,rest_RS,0.000,0.000,1.875,0.000,0 +53.297,rest_RS,0.000,0.000,1.875,0.000,0 +53.347,rest_RS,0.000,0.000,1.875,0.000,0 +53.397,rest_RS,0.000,0.000,1.875,0.000,0 +53.447,rest_RS,0.000,0.000,1.875,0.000,0 +53.497,rest_RS,0.000,0.000,1.875,0.000,0 +53.547,rest_RS,0.000,0.000,1.875,0.000,0 +53.597,rest_RS,0.000,0.000,1.875,0.000,0 +53.647,rest_RS,0.000,0.000,1.875,0.000,0 +53.698,rest_RS,0.000,0.000,1.875,0.000,0 +53.748,rest_RS,0.000,0.000,1.875,0.000,0 +53.798,rest_RS,0.000,0.000,1.875,0.000,0 +53.848,rest_RS,0.000,0.000,1.875,0.000,0 +53.899,rest_RS,0.000,0.000,1.875,0.000,0 +53.949,rest_RS,0.000,0.000,1.875,0.000,0 +53.999,rest_RS,0.000,0.000,1.875,0.000,0 +54.049,rest_RS,0.000,0.000,1.875,0.000,0 +54.099,rest_RS,0.000,0.000,1.875,0.000,0 +54.149,rest_RS,0.000,0.000,1.875,0.000,0 +54.200,rest_RS,0.000,0.000,1.875,0.000,0 +54.250,rest_RS,0.000,0.000,1.875,0.000,0 +54.300,rest_RS,0.000,0.000,1.875,0.000,0 +54.350,rest_RS,0.000,0.000,1.875,0.000,0 +54.401,rest_RS,0.000,0.000,1.875,0.000,0 +54.453,RY_up,0.000,0.000,1.875,0.000,0 +54.504,RY_up,0.000,0.000,1.875,0.000,0 +54.554,RY_up,0.000,0.000,1.875,0.000,0 +54.604,RY_up,0.000,0.000,1.875,0.000,0 +54.654,RY_up,0.000,0.000,1.875,0.000,0 +54.704,RY_up,0.000,0.000,1.875,0.000,0 +54.754,RY_up,0.000,0.000,1.875,0.000,0 +54.804,RY_up,0.000,0.000,1.875,0.000,0 +54.854,RY_up,0.000,0.000,1.875,0.000,0 +54.904,RY_up,0.000,0.000,1.875,0.000,0 +54.954,RY_up,0.000,0.000,1.875,0.000,0 +55.004,RY_up,0.000,0.000,1.875,0.000,0 +55.054,RY_up,0.000,0.000,1.875,0.000,0 +55.104,RY_up,0.000,0.000,1.875,0.000,0 +55.154,RY_up,0.000,0.000,1.875,0.000,0 +55.205,RY_up,0.000,0.000,1.875,0.000,0 +55.255,RY_up,0.000,0.000,1.875,0.000,0 +55.305,RY_up,0.000,0.000,1.875,0.000,0 +55.355,RY_up,0.000,0.000,1.875,0.000,0 +55.405,RY_up,0.000,0.000,1.875,0.000,0 +55.455,RY_up,0.000,0.000,1.875,0.000,0 +55.506,RY_up,0.000,0.000,1.875,0.000,0 +55.556,RY_up,0.000,0.000,1.875,0.000,0 +55.606,RY_up,0.000,0.000,1.875,0.000,0 +55.656,RY_up,0.000,0.000,1.875,0.000,0 +55.706,RY_up,0.000,0.000,1.875,0.000,0 +55.756,RY_up,0.000,0.000,1.875,0.000,0 +55.807,RY_up,0.000,0.000,1.875,0.000,0 +55.857,RY_up,0.000,0.000,1.875,0.000,0 +55.908,RY_up,0.000,0.000,1.875,0.000,0 +55.958,RY_up,0.000,0.000,1.875,0.000,0 +56.008,RY_up,0.000,0.000,1.875,0.000,0 +56.058,RY_up,0.000,0.000,1.875,0.000,0 +56.108,RY_up,0.000,0.000,1.875,0.000,0 +56.158,RY_up,0.000,0.000,1.875,0.000,0 +56.208,RY_up,0.000,0.000,1.875,0.000,0 +56.258,RY_up,0.000,0.000,1.875,0.000,0 +56.308,RY_up,0.000,0.000,1.875,0.000,0 +56.359,RY_up,0.000,0.000,1.875,0.000,0 +56.409,RY_up,0.000,0.000,1.875,0.000,0 +56.459,RY_up,0.000,0.000,1.875,0.000,0 +56.509,RY_up,0.000,0.000,1.875,0.000,0 +56.559,RY_up,0.000,0.000,1.875,0.000,0 +56.609,RY_up,0.000,0.000,1.875,0.000,0 +56.659,RY_up,0.000,0.000,1.875,0.000,0 +56.709,RY_up,0.000,0.000,1.875,0.000,0 +56.759,RY_up,0.000,0.000,1.875,0.000,0 +56.809,RY_up,0.000,0.000,1.875,0.000,0 +56.860,RY_up,0.000,0.000,1.875,0.000,0 +56.910,RY_up,0.000,0.000,1.875,0.000,0 +56.960,RY_up,0.000,0.000,1.875,0.000,0 +57.010,RY_up,0.000,0.000,1.875,0.000,0 +57.061,RY_up,0.000,0.000,1.875,0.000,0 +57.111,RY_up,0.000,0.000,1.875,0.000,0 +57.161,RY_up,0.000,0.000,1.875,0.000,0 +57.212,RY_up,0.000,0.000,1.875,0.000,0 +57.262,RY_up,0.000,0.000,1.875,0.000,0 +57.312,RY_up,0.000,0.000,1.875,0.000,0 +57.362,RY_up,0.000,0.000,1.875,0.000,0 +57.412,RY_up,0.000,0.000,1.875,0.000,0 +57.462,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.513,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.563,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.613,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.663,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.713,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.763,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.814,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.864,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.914,rest_RY_up,0.000,0.000,1.875,0.000,0 +57.964,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.015,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.065,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.115,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.166,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.216,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.266,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.316,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.366,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.416,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.466,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.516,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.566,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.617,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.667,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.717,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.768,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.818,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.868,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.918,rest_RY_up,0.000,0.000,1.875,0.000,0 +58.969,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.019,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.069,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.119,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.169,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.219,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.269,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.320,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.370,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.420,rest_RY_up,0.000,0.000,1.875,0.000,0 +59.473,RY_down,0.000,0.000,1.875,0.000,0 +59.523,RY_down,0.000,0.000,1.875,0.000,0 +59.573,RY_down,0.000,0.000,1.875,0.000,0 +59.623,RY_down,0.000,0.000,1.875,0.000,0 +59.673,RY_down,0.000,0.000,1.875,0.000,0 +59.723,RY_down,0.000,0.000,1.875,0.000,0 +59.773,RY_down,0.000,0.000,1.875,0.000,0 +59.823,RY_down,0.000,0.000,1.875,0.000,0 +59.873,RY_down,0.000,0.000,1.875,0.000,0 +59.923,RY_down,0.000,0.000,1.875,0.000,0 +59.973,RY_down,0.000,0.000,1.875,0.000,0 +60.024,RY_down,0.000,0.000,1.875,0.000,0 +60.074,RY_down,0.000,0.000,1.875,0.000,0 +60.124,RY_down,0.000,0.000,1.875,0.000,0 +60.174,RY_down,0.000,0.000,1.875,0.000,0 +60.224,RY_down,0.000,0.000,1.875,0.000,0 +60.274,RY_down,0.000,0.000,1.875,0.000,0 +60.324,RY_down,0.000,0.000,1.875,0.000,0 +60.374,RY_down,0.000,0.000,1.875,0.000,0 +60.424,RY_down,0.000,0.000,1.875,0.000,0 +60.474,RY_down,0.000,0.000,1.875,0.000,0 +60.524,RY_down,0.000,0.000,1.875,0.000,0 +60.575,RY_down,0.000,0.000,1.875,0.000,0 +60.625,RY_down,0.000,0.000,1.875,0.000,0 +60.675,RY_down,0.000,0.000,1.875,0.000,0 +60.725,RY_down,0.000,0.000,1.875,0.000,0 +60.775,RY_down,0.000,0.000,1.875,0.000,0 +60.825,RY_down,0.000,0.000,1.875,0.000,0 +60.875,RY_down,0.000,0.000,1.875,0.000,0 +60.925,RY_down,0.000,0.000,1.875,0.000,0 +60.975,RY_down,0.000,0.000,1.875,0.000,0 +61.025,RY_down,0.000,0.000,1.875,0.000,0 +61.075,RY_down,0.000,0.000,1.875,0.000,0 +61.125,RY_down,0.000,0.000,1.875,0.000,0 +61.176,RY_down,0.000,0.000,1.875,0.000,0 +61.226,RY_down,0.000,0.000,1.875,0.000,0 +61.276,RY_down,0.000,0.000,1.875,0.000,0 +61.326,RY_down,0.000,0.000,1.875,0.000,0 +61.376,RY_down,0.000,0.000,1.875,0.000,0 +61.426,RY_down,0.000,0.000,1.875,0.000,0 +61.476,RY_down,0.000,0.000,1.875,0.000,0 +61.526,RY_down,0.000,0.000,1.875,0.000,0 +61.576,RY_down,0.000,0.000,1.875,0.000,0 +61.627,RY_down,0.000,0.000,1.875,0.000,0 +61.677,RY_down,0.000,0.000,1.875,0.000,0 +61.727,RY_down,0.000,0.000,1.875,0.000,0 +61.777,RY_down,0.000,0.000,1.875,0.000,0 +61.827,RY_down,0.000,0.000,1.875,0.000,0 +61.878,RY_down,0.000,0.000,1.875,0.000,0 +61.928,RY_down,0.000,0.000,1.875,0.000,0 +61.978,RY_down,0.000,0.000,1.875,0.000,0 +62.028,RY_down,0.000,0.000,1.875,0.000,0 +62.079,RY_down,0.000,0.000,1.875,0.000,0 +62.129,RY_down,0.000,0.000,1.875,0.000,0 +62.180,RY_down,0.000,0.000,1.875,0.000,0 +62.230,RY_down,0.000,0.000,1.875,0.000,0 +62.280,RY_down,0.000,0.000,1.875,0.000,0 +62.331,RY_down,0.000,0.000,1.875,0.000,0 +62.381,RY_down,0.000,0.000,1.875,0.000,0 +62.431,RY_down,0.000,0.000,1.875,0.000,0 +62.481,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.531,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.581,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.631,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.682,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.732,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.782,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.832,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.882,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.932,rest_RY_down,0.000,0.000,1.875,0.000,0 +62.983,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.033,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.083,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.134,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.184,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.234,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.284,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.334,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.384,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.434,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.484,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.534,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.585,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.635,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.685,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.735,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.786,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.836,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.886,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.936,rest_RY_down,0.000,0.000,1.875,0.000,0 +63.986,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.036,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.087,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.137,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.187,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.237,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.287,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.338,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.388,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.438,rest_RY_down,0.000,0.000,1.875,0.000,0 +64.491,RX_right,0.000,0.000,1.875,0.000,0 +64.541,RX_right,0.000,0.000,1.875,0.000,0 +64.591,RX_right,0.000,0.000,1.875,0.000,0 +64.641,RX_right,0.000,0.000,1.875,0.000,0 +64.691,RX_right,0.000,0.000,1.875,0.000,0 +64.741,RX_right,0.000,0.000,1.875,0.000,0 +64.791,RX_right,0.000,0.000,1.875,0.000,0 +64.841,RX_right,0.000,0.000,1.875,0.000,0 +64.891,RX_right,0.000,0.000,1.875,0.000,0 +64.942,RX_right,0.000,0.000,1.875,0.000,0 +64.992,RX_right,0.000,0.000,1.875,0.000,0 +65.042,RX_right,0.000,0.000,1.875,0.000,0 +65.092,RX_right,0.000,0.000,1.875,0.000,0 +65.142,RX_right,0.000,0.000,1.875,0.000,0 +65.192,RX_right,0.000,0.000,1.875,0.000,0 +65.242,RX_right,0.000,0.000,1.875,0.000,0 +65.292,RX_right,0.000,0.000,1.875,0.000,0 +65.342,RX_right,0.000,0.000,1.875,0.000,0 +65.392,RX_right,0.000,0.000,1.875,0.000,0 +65.442,RX_right,0.000,0.000,1.875,0.000,0 +65.492,RX_right,0.000,0.000,1.875,0.000,0 +65.542,RX_right,0.000,0.000,1.875,0.000,0 +65.592,RX_right,0.000,0.000,1.875,0.000,0 +65.642,RX_right,0.000,0.000,1.875,0.000,0 +65.692,RX_right,0.000,0.000,1.875,0.000,0 +65.743,RX_right,0.000,0.000,1.875,0.000,0 +65.793,RX_right,0.000,0.000,1.875,0.000,0 +65.843,RX_right,0.000,0.000,1.875,0.000,0 +65.893,RX_right,0.000,0.000,1.875,0.000,0 +65.944,RX_right,0.000,0.000,1.875,0.000,0 +65.994,RX_right,0.000,0.000,1.875,0.000,0 +66.044,RX_right,0.000,0.000,1.875,0.000,0 +66.094,RX_right,0.000,0.000,1.875,0.000,0 +66.144,RX_right,0.000,0.000,1.875,0.000,0 +66.194,RX_right,0.000,0.000,1.875,0.000,0 +66.244,RX_right,0.000,0.000,1.875,0.000,0 +66.294,RX_right,0.000,0.000,1.875,0.000,0 +66.344,RX_right,0.000,0.000,1.875,0.000,0 +66.395,RX_right,0.000,0.000,1.875,0.000,0 +66.445,RX_right,0.000,0.000,1.875,0.000,0 +66.496,RX_right,0.000,0.000,1.875,0.000,0 +66.546,RX_right,0.000,0.000,1.875,0.000,0 +66.596,RX_right,0.000,0.000,1.875,0.000,0 +66.646,RX_right,0.000,0.000,1.875,0.000,0 +66.696,RX_right,0.000,0.000,1.875,0.000,0 +66.747,RX_right,0.000,0.000,1.875,0.000,0 +66.797,RX_right,0.000,0.000,1.875,0.000,0 +66.847,RX_right,0.000,0.000,1.875,0.000,0 +66.897,RX_right,0.000,0.000,1.875,0.000,0 +66.947,RX_right,0.000,0.000,1.875,0.000,0 +66.997,RX_right,0.000,0.000,1.875,0.000,0 +67.048,RX_right,0.000,0.000,1.875,0.000,0 +67.098,RX_right,0.000,0.000,1.875,0.000,0 +67.148,RX_right,0.000,0.000,1.875,0.000,0 +67.198,RX_right,0.000,0.000,1.875,0.000,0 +67.248,RX_right,0.000,0.000,1.875,0.000,0 +67.298,RX_right,0.000,0.000,1.875,0.000,0 +67.348,RX_right,0.000,0.000,1.875,0.000,0 +67.399,RX_right,0.000,0.000,1.875,0.000,0 +67.449,RX_right,0.000,0.000,1.875,0.000,0 +67.499,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.549,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.600,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.650,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.701,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.751,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.801,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.851,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.901,rest_RX_right,0.000,0.000,1.875,0.000,0 +67.951,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.001,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.051,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.102,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.152,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.202,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.252,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.302,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.352,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.402,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.452,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.502,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.552,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.602,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.653,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.703,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.753,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.803,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.853,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.904,rest_RX_right,0.000,0.000,1.875,0.000,0 +68.954,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.004,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.054,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.105,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.155,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.205,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.255,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.305,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.355,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.405,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.456,rest_RX_right,0.000,0.000,1.875,0.000,0 +69.509,dpad_up,0.000,0.000,1.875,0.000,0 +69.559,dpad_up,0.000,0.000,1.875,0.000,0 +69.609,dpad_up,0.000,0.000,1.875,0.000,0 +69.659,dpad_up,0.000,0.000,1.875,0.000,0 +69.709,dpad_up,0.000,0.000,1.875,0.000,0 +69.759,dpad_up,0.000,0.000,1.875,0.000,0 +69.809,dpad_up,0.000,0.000,1.875,0.000,0 +69.860,dpad_up,0.000,0.000,1.875,0.000,0 +69.910,dpad_up,0.000,0.000,1.875,0.000,0 +69.960,dpad_up,0.000,0.000,1.875,0.000,0 +70.010,dpad_up,0.000,0.000,1.875,0.000,0 +70.060,dpad_up,0.000,0.000,1.875,0.000,0 +70.110,dpad_up,0.000,0.000,1.875,0.000,0 +70.160,dpad_up,0.000,0.000,1.875,0.000,0 +70.210,dpad_up,0.000,0.000,1.875,0.000,0 +70.260,dpad_up,0.000,0.000,1.875,0.000,0 +70.310,dpad_up,0.000,0.000,1.875,0.000,0 +70.360,dpad_up,0.000,0.000,1.875,0.000,0 +70.411,dpad_up,0.000,0.000,1.875,0.000,0 +70.461,dpad_up,0.000,0.000,1.875,0.000,0 +70.511,dpad_up,0.000,0.000,1.875,0.000,0 +70.561,dpad_up,0.000,0.000,1.875,0.000,0 +70.611,dpad_up,0.000,0.000,1.875,0.000,0 +70.661,dpad_up,0.000,0.000,1.875,0.000,0 +70.711,dpad_up,0.000,0.000,1.875,0.000,0 +70.761,dpad_up,0.000,0.000,1.875,0.000,0 +70.811,dpad_up,0.000,0.000,1.875,0.000,0 +70.861,dpad_up,0.000,0.000,1.875,0.000,0 +70.911,dpad_up,0.000,0.000,1.875,0.000,0 +70.961,dpad_up,0.000,0.000,1.875,0.000,0 +71.012,dpad_up,0.000,0.000,1.875,0.000,0 +71.062,dpad_up,0.000,0.000,1.875,0.000,0 +71.112,dpad_up,0.000,0.000,1.875,0.000,0 +71.162,dpad_up,0.000,0.000,1.875,0.000,0 +71.213,dpad_up,0.000,0.000,1.875,0.000,0 +71.263,dpad_up,0.000,0.000,1.875,0.000,0 +71.313,dpad_up,0.000,0.000,1.875,0.000,0 +71.363,dpad_up,0.000,0.000,1.875,0.000,0 +71.413,dpad_up,0.000,0.000,1.875,0.000,0 +71.463,dpad_up,0.000,0.000,1.875,0.000,0 +71.513,dpad_up,0.000,0.000,1.875,0.000,0 +71.563,dpad_up,0.000,0.000,1.875,0.000,0 +71.614,dpad_up,0.000,0.000,1.875,0.000,0 +71.664,dpad_up,0.000,0.000,1.875,0.000,0 +71.714,dpad_up,0.000,0.000,1.875,0.000,0 +71.765,dpad_up,0.000,0.000,1.875,0.000,0 +71.815,dpad_up,0.000,0.000,1.875,0.000,0 +71.865,dpad_up,0.000,0.000,1.875,0.000,0 +71.915,dpad_up,0.000,0.000,1.875,0.000,0 +71.965,dpad_up,0.000,0.000,1.875,0.000,0 +72.015,dpad_up,0.000,0.000,1.875,0.000,0 +72.065,dpad_up,0.000,0.000,1.875,0.000,0 +72.115,dpad_up,0.000,0.000,1.875,0.000,0 +72.165,dpad_up,0.000,0.000,1.875,0.000,0 +72.215,dpad_up,0.000,0.000,1.875,0.000,0 +72.265,dpad_up,0.000,0.000,1.875,0.000,0 +72.316,dpad_up,0.000,0.000,1.875,0.000,0 +72.366,dpad_up,0.000,0.000,1.875,0.000,0 +72.417,dpad_up,0.000,0.000,1.875,0.000,0 +72.467,dpad_up,0.000,0.000,1.875,0.000,0 +72.517,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.567,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.617,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.667,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.717,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.768,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.818,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.868,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.918,rest_dpad_up,0.000,0.000,1.875,0.000,0 +72.968,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.018,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.068,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.118,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.169,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.219,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.269,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.319,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.370,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.420,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.470,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.520,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.570,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.621,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.671,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.721,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.771,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.821,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.872,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.922,rest_dpad_up,0.000,0.000,1.875,0.000,0 +73.972,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.022,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.072,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.122,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.172,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.223,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.273,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.323,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.373,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.423,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.474,rest_dpad_up,0.000,0.000,1.875,0.000,0 +74.527,dpad_down,0.000,0.000,1.875,0.000,0 +74.577,dpad_down,0.000,0.000,1.875,0.000,0 +74.627,dpad_down,0.000,0.000,1.875,0.000,0 +74.677,dpad_down,0.000,0.000,1.875,0.000,0 +74.728,dpad_down,0.000,0.000,1.875,0.000,0 +74.778,dpad_down,0.000,0.000,1.875,0.000,0 +74.828,dpad_down,0.000,0.000,1.875,0.000,0 +74.878,dpad_down,0.000,0.000,1.875,0.000,0 +74.928,dpad_down,0.000,0.000,1.875,0.000,0 +74.978,dpad_down,0.000,0.000,1.875,0.000,0 +75.028,dpad_down,0.000,0.000,1.875,0.000,0 +75.078,dpad_down,0.000,0.000,1.875,0.000,0 +75.128,dpad_down,0.000,0.000,1.875,0.000,0 +75.178,dpad_down,0.000,0.000,1.875,0.000,0 +75.228,dpad_down,0.000,0.000,1.875,0.000,0 +75.278,dpad_down,0.000,0.000,1.875,0.000,0 +75.328,dpad_down,0.000,0.000,1.875,0.000,0 +75.378,dpad_down,0.000,0.000,1.875,0.000,0 +75.428,dpad_down,0.000,0.000,1.875,0.000,0 +75.478,dpad_down,0.000,0.000,1.875,0.000,0 +75.529,dpad_down,0.000,0.000,1.875,0.000,0 +75.579,dpad_down,0.000,0.000,1.875,0.000,0 +75.629,dpad_down,0.000,0.000,1.875,0.000,0 +75.679,dpad_down,0.000,0.000,1.875,0.000,0 +75.729,dpad_down,0.000,0.000,1.875,0.000,0 +75.779,dpad_down,0.000,0.000,1.875,0.000,0 +75.829,dpad_down,0.000,0.000,1.875,0.000,0 +75.879,dpad_down,0.000,0.000,1.875,0.000,0 +75.929,dpad_down,0.000,0.000,1.875,0.000,0 +75.979,dpad_down,0.000,0.000,1.875,0.000,0 +76.030,dpad_down,0.000,0.000,1.875,0.000,0 +76.080,dpad_down,0.000,0.000,1.875,0.000,0 +76.130,dpad_down,0.000,0.000,1.875,0.000,0 +76.180,dpad_down,0.000,0.000,1.875,0.000,0 +76.231,dpad_down,0.000,0.000,1.875,0.000,0 +76.281,dpad_down,0.000,0.000,1.875,0.000,0 +76.331,dpad_down,0.000,0.000,1.875,0.000,0 +76.381,dpad_down,0.000,0.000,1.875,0.000,0 +76.431,dpad_down,0.000,0.000,1.875,0.000,0 +76.481,dpad_down,0.000,0.000,1.875,0.000,0 +76.531,dpad_down,0.000,0.000,1.875,0.000,0 +76.581,dpad_down,0.000,0.000,1.875,0.000,0 +76.631,dpad_down,0.000,0.000,1.875,0.000,0 +76.682,dpad_down,0.000,0.000,1.875,0.000,0 +76.732,dpad_down,0.000,0.000,1.875,0.000,0 +76.782,dpad_down,0.000,0.000,1.875,0.000,0 +76.832,dpad_down,0.000,0.000,1.875,0.000,0 +76.882,dpad_down,0.000,0.000,1.875,0.000,0 +76.933,dpad_down,0.000,0.000,1.875,0.000,0 +76.983,dpad_down,0.000,0.000,1.875,0.000,0 +77.033,dpad_down,0.000,0.000,1.875,0.000,0 +77.083,dpad_down,0.000,0.000,1.875,0.000,0 +77.133,dpad_down,0.000,0.000,1.875,0.000,0 +77.183,dpad_down,0.000,0.000,1.875,0.000,0 +77.233,dpad_down,0.000,0.000,1.875,0.000,0 +77.284,dpad_down,0.000,0.000,1.875,0.000,0 +77.334,dpad_down,0.000,0.000,1.875,0.000,0 +77.384,dpad_down,0.000,0.000,1.875,0.000,0 +77.435,dpad_down,0.000,0.000,1.875,0.000,0 +77.485,dpad_down,0.000,0.000,1.875,0.000,0 +77.535,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.585,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.635,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.685,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.735,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.785,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.836,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.886,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.936,rest_dpad_down,0.000,0.000,1.875,0.000,0 +77.986,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.036,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.086,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.136,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.186,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.237,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.288,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.338,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.388,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.438,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.489,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.539,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.589,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.639,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.689,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.739,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.789,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.839,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.890,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.940,rest_dpad_down,0.000,0.000,1.875,0.000,0 +78.990,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.040,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.090,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.141,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.191,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.241,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.291,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.341,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.392,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.442,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.492,rest_dpad_down,0.000,0.000,1.875,0.000,0 +79.546,dpad_left,0.000,0.000,1.875,0.000,0 +79.596,dpad_left,0.000,0.000,1.875,0.000,0 +79.646,dpad_left,0.000,0.000,1.875,0.000,0 +79.696,dpad_left,0.000,0.000,1.875,0.000,0 +79.746,dpad_left,0.000,0.000,1.875,0.000,0 +79.796,dpad_left,0.000,0.000,1.875,0.000,0 +79.846,dpad_left,0.000,0.000,1.875,0.000,0 +79.896,dpad_left,0.000,0.000,1.875,0.000,0 +79.946,dpad_left,0.000,0.000,1.875,0.000,0 +79.997,dpad_left,0.000,0.000,1.875,0.000,0 +80.047,dpad_left,0.000,0.000,1.875,0.000,0 +80.097,dpad_left,0.000,0.000,1.875,0.000,0 +80.147,dpad_left,0.000,0.000,1.875,0.000,0 +80.197,dpad_left,0.000,0.000,1.875,0.000,0 +80.247,dpad_left,0.000,0.000,1.875,0.000,0 +80.297,dpad_left,0.000,0.000,1.875,0.000,0 +80.347,dpad_left,0.000,0.000,1.875,0.000,0 +80.397,dpad_left,0.000,0.000,1.875,0.000,0 +80.447,dpad_left,0.000,0.000,1.875,0.000,0 +80.497,dpad_left,0.000,0.000,1.875,0.000,0 +80.547,dpad_left,0.000,0.000,1.875,0.000,0 +80.597,dpad_left,0.000,0.000,1.875,0.000,0 +80.647,dpad_left,0.000,0.000,1.875,0.000,0 +80.697,dpad_left,0.000,0.000,1.875,0.000,0 +80.747,dpad_left,0.000,0.000,1.875,0.000,0 +80.798,dpad_left,0.000,0.000,1.875,0.000,0 +80.848,dpad_left,0.000,0.000,1.875,0.000,0 +80.899,dpad_left,0.000,0.000,1.875,0.000,0 +80.949,dpad_left,0.000,0.000,1.875,0.000,0 +80.999,dpad_left,0.000,0.000,1.875,0.000,0 +81.049,dpad_left,0.000,0.000,1.875,0.000,0 +81.099,dpad_left,0.000,0.000,1.875,0.000,0 +81.149,dpad_left,0.000,0.000,1.875,0.000,0 +81.200,dpad_left,0.000,0.000,1.875,0.000,0 +81.250,dpad_left,0.000,0.000,1.875,0.000,0 +81.300,dpad_left,0.000,0.000,1.875,0.000,0 +81.350,dpad_left,0.000,0.000,1.875,0.000,0 +81.400,dpad_left,0.000,0.000,1.875,0.000,0 +81.450,dpad_left,0.000,0.000,1.875,0.000,0 +81.500,dpad_left,0.000,0.000,1.875,0.000,0 +81.550,dpad_left,0.000,0.000,1.875,0.000,0 +81.600,dpad_left,0.000,0.000,1.875,0.000,0 +81.651,dpad_left,0.000,0.000,1.875,0.000,0 +81.701,dpad_left,0.000,0.000,1.875,0.000,0 +81.751,dpad_left,0.000,0.000,1.875,0.000,0 +81.801,dpad_left,0.000,0.000,1.875,0.000,0 +81.851,dpad_left,0.000,0.000,1.875,0.000,0 +81.901,dpad_left,0.000,0.000,1.875,0.000,0 +81.951,dpad_left,0.000,0.000,1.875,0.000,0 +82.001,dpad_left,0.000,0.000,1.875,0.000,0 +82.052,dpad_left,0.000,0.000,1.875,0.000,0 +82.102,dpad_left,0.000,0.000,1.875,0.000,0 +82.152,dpad_left,0.000,0.000,1.875,0.000,0 +82.203,dpad_left,0.000,0.000,1.875,0.000,0 +82.253,dpad_left,0.000,0.000,1.875,0.000,0 +82.303,dpad_left,0.000,0.000,1.875,0.000,0 +82.353,dpad_left,0.000,0.000,1.875,0.000,0 +82.404,dpad_left,0.000,0.000,1.875,0.000,0 +82.454,dpad_left,0.000,0.000,1.875,0.000,0 +82.504,dpad_left,0.000,0.000,1.875,0.000,0 +82.554,rest_dpad_left,0.000,0.000,1.875,0.000,0 +82.604,rest_dpad_left,0.000,0.000,1.875,0.000,0 +82.655,rest_dpad_left,0.000,0.000,1.875,0.000,0 +82.705,rest_dpad_left,0.000,0.000,1.875,0.000,0 +82.755,rest_dpad_left,0.000,0.000,1.875,0.000,0 +82.805,rest_dpad_left,0.000,0.000,1.875,0.000,0 +82.855,rest_dpad_left,0.000,0.000,1.875,0.000,0 +82.905,rest_dpad_left,0.000,0.000,1.875,0.000,0 +82.956,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.006,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.056,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.106,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.156,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.206,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.257,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.307,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.357,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.407,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.457,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.507,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.557,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.607,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.658,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.708,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.758,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.808,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.858,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.909,rest_dpad_left,0.000,0.000,1.875,0.000,0 +83.959,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.009,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.059,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.109,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.160,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.210,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.260,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.310,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.360,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.411,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.461,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.511,rest_dpad_left,0.000,0.000,1.875,0.000,0 +84.565,dpad_right,0.000,0.000,1.875,0.000,0 +84.615,dpad_right,0.000,0.000,1.875,0.000,0 +84.666,dpad_right,0.000,0.000,1.875,0.000,0 +84.716,dpad_right,0.000,0.000,1.875,0.000,0 +84.770,dpad_right,0.000,0.000,1.875,0.000,0 +84.820,dpad_right,0.000,0.000,1.875,0.000,0 +84.870,dpad_right,0.000,0.000,1.875,0.000,0 +84.920,dpad_right,0.000,0.000,1.875,0.000,0 +84.971,dpad_right,0.000,0.000,1.875,0.000,0 +85.021,dpad_right,0.000,0.000,1.875,0.000,0 +85.071,dpad_right,0.000,0.000,1.875,0.000,0 +85.123,dpad_right,0.000,0.000,1.875,0.000,0 +85.173,dpad_right,0.000,0.000,1.875,0.000,0 +85.223,dpad_right,0.000,0.000,1.875,0.000,0 +85.273,dpad_right,0.000,0.000,1.875,0.000,0 +85.323,dpad_right,0.000,0.000,1.875,0.000,0 +85.373,dpad_right,0.000,0.000,1.875,0.000,0 +85.423,dpad_right,0.000,0.000,1.875,0.000,0 +85.474,dpad_right,0.000,0.000,1.875,0.000,0 +85.524,dpad_right,0.000,0.000,1.875,0.000,0 +85.575,dpad_right,0.000,0.000,1.875,0.000,0 +85.625,dpad_right,0.000,0.000,1.875,0.000,0 +85.675,dpad_right,0.000,0.000,1.875,0.000,0 +85.725,dpad_right,0.000,0.000,1.875,0.000,0 +85.776,dpad_right,0.000,0.000,1.875,0.000,0 +85.826,dpad_right,0.000,0.000,1.875,0.000,0 +85.877,dpad_right,0.000,0.000,1.875,0.000,0 +85.927,dpad_right,0.000,0.000,1.875,0.000,0 +85.978,dpad_right,0.000,0.000,1.875,0.000,0 +86.028,dpad_right,0.000,0.000,1.875,0.000,0 +86.078,dpad_right,0.000,0.000,1.875,0.000,0 +86.128,dpad_right,0.000,0.000,1.875,0.000,0 +86.178,dpad_right,0.000,0.000,1.875,0.000,0 +86.229,dpad_right,0.000,0.000,1.875,0.000,0 +86.279,dpad_right,0.000,0.000,1.875,0.000,0 +86.329,dpad_right,0.000,0.000,1.875,0.000,0 +86.379,dpad_right,0.000,0.000,1.875,0.000,0 +86.429,dpad_right,0.000,0.000,1.875,0.000,0 +86.479,dpad_right,0.000,0.000,1.875,0.000,0 +86.529,dpad_right,0.000,0.000,1.875,0.000,0 +86.580,dpad_right,0.000,0.000,1.875,0.000,0 +86.630,dpad_right,0.000,0.000,1.875,0.000,0 +86.680,dpad_right,0.000,0.000,1.875,0.000,0 +86.730,dpad_right,0.000,0.000,1.875,0.000,0 +86.780,dpad_right,0.000,0.000,1.875,0.000,0 +86.831,dpad_right,0.000,0.000,1.875,0.000,0 +86.881,dpad_right,0.000,0.000,1.875,0.000,0 +86.931,dpad_right,0.000,0.000,1.875,0.000,0 +86.981,dpad_right,0.000,0.000,1.875,0.000,0 +87.031,dpad_right,0.000,0.000,1.875,0.000,0 +87.082,dpad_right,0.000,0.000,1.875,0.000,0 +87.132,dpad_right,0.000,0.000,1.875,0.000,0 +87.182,dpad_right,0.000,0.000,1.875,0.000,0 +87.232,dpad_right,0.000,0.000,1.875,0.000,0 +87.282,dpad_right,0.000,0.000,1.875,0.000,0 +87.332,dpad_right,0.000,0.000,1.875,0.000,0 +87.383,dpad_right,0.000,0.000,1.875,0.000,0 +87.433,dpad_right,0.000,0.000,1.875,0.000,0 +87.483,dpad_right,0.000,0.000,1.875,0.000,0 +87.533,dpad_right,0.000,0.000,1.875,0.000,0 +87.584,rest_dpad_right,0.000,0.000,1.875,0.000,0 +87.634,rest_dpad_right,0.000,0.000,1.875,0.000,0 +87.684,rest_dpad_right,0.000,0.000,1.875,0.000,0 +87.734,rest_dpad_right,0.000,0.000,1.875,0.000,0 +87.784,rest_dpad_right,0.000,0.000,1.875,0.000,0 +87.834,rest_dpad_right,0.000,0.000,1.875,0.000,0 +87.884,rest_dpad_right,0.000,0.000,1.875,0.000,0 +87.934,rest_dpad_right,0.000,0.000,1.875,0.000,0 +87.984,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.035,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.085,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.135,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.185,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.235,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.285,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.335,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.385,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.435,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.486,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.536,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.586,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.636,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.686,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.737,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.787,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.837,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.887,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.938,rest_dpad_right,0.000,0.000,1.875,0.000,0 +88.988,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.039,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.089,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.139,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.189,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.239,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.289,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.340,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.390,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.440,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.490,rest_dpad_right,0.000,0.000,1.875,0.000,0 +89.541,rest_dpad_right,0.000,0.000,1.875,0.000,0 diff --git a/tools/re-capture/binq.py b/tools/re-capture/binq.py new file mode 100644 index 0000000..cb0bf5d --- /dev/null +++ b/tools/re-capture/binq.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Ask questions of a ctrl_probe.py capture: which words answer to which input? + +The capture is a window of the player entity object sampled every tick, tagged +with the pad state that produced it. That makes the interesting question +mechanical: for each 4-byte offset, does its value during phase X differ from +its value during the rest phases either side? A word that only moves while `RT` +is held is that input's state — a throttle setting, an afterburner tank, a heat +gauge — and one that moves in *every* phase is just live physics. + +Sub-commands + phases per-phase mean of every offset that moves at all + respond offsets that move during and not at rest + near [tol] offsets whose first sample is ~= value (HUD anchor) + trace [off...] full time series of specific offsets (pos-relative hex) +""" +import os +import struct +import sys + +import numpy as np + +HDR = b"SYLPHCTR" +REC = struct.Struct(" 1e-3)) + order = idx[np.argsort(-mv[idx])][:int(args[0]) if args else 25] + print("offset " + "".join(f"{p[:8]:>10}" for p in phases)) + for i in order: + print(f"{label(d, i):<10}" + "".join(f"{means[p][i]:10.2f}" for p in phases)) + + +def cmd_respond(d, args): + want = args[0] + F, ok = d["F"], finite(d) + inp = np.array([p == want for p in d["phase"]]) + rest = np.array([p.startswith("rest") or p == "base" for p in d["phase"]]) + if not inp.any(): + sys.exit(f"no phase {want!r}") + # A word answering to this input must move *while it is held* and be quiet + # at rest; a word that also moves at rest is live physics, not the input. + a = F[inp] + r = F[rest] + d_in = a.max(axis=0) - a.min(axis=0) + d_rest = r.max(axis=0) - r.min(axis=0) + score = d_in - 2.0 * d_rest + idx = np.flatnonzero(ok & (d_in > 1e-3) & (score > 0)) + for i in idx[np.argsort(-score[idx])][:20]: + print(f"{label(d, i):<10} in-phase {a[:, i].min():12.3f}..{a[:, i].max():12.3f}" + f" at-rest {r[:, i].min():12.3f}..{r[:, i].max():12.3f}") + if not len(idx): + print("(nothing moves under this input that is quiet at rest)") + + +def cmd_near(d, args): + v = float(args[0]) + tol = float(args[1]) if len(args) > 1 else max(1e-3, abs(v) * 1e-3) + F, U = d["F"], d["U"] + for i in np.flatnonzero(np.abs(F[0] - v) <= tol): + print(f"{label(d, i):<10} f32 {F[0, i]:.4f} -> {F[-1, i]:.4f}") + for i in np.flatnonzero(np.abs(U[0].astype(np.float64) - v) <= tol): + print(f"{label(d, i):<10} u32 {U[0, i]} -> {U[-1, i]}") + + +def cmd_trace(d, args): + offs = [int(a, 0) for a in args] + idx = [(o + d["back"]) // 4 for o in offs] + print("t phase speed " + " ".join(f"{o:+#07x}" for o in offs)) + for k in range(d["n"]): + print(f"{d['t'][k]:6.2f} {d['phase'][k]:<12} {d['speed'][k]:6.0f} " + + " ".join(f"{d['F'][k, i]:8.2f}" for i in idx)) + + +def main(): + d = load(sys.argv[1]) + print(f"# {d['n']} ticks, window {d['win']:#x} bytes, back {d['back']:#x}") + cmd = sys.argv[2] if len(sys.argv) > 2 else "phases" + {"phases": cmd_phases, "respond": cmd_respond, "near": cmd_near, + "trace": cmd_trace}[cmd](d, sys.argv[3:]) + + +if __name__ == "__main__": + main() diff --git a/tools/re-capture/ctrl_probe.py b/tools/re-capture/ctrl_probe.py new file mode 100644 index 0000000..f3208bc --- /dev/null +++ b/tools/re-capture/ctrl_probe.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +"""Which control is the throttle, and where does the craft keep its own state? + +Two questions, one flight. Both are answered by *consequence* rather than by +reading a field we hope is the right one: + +* **Throttle.** The craft's speed is measured from its own position — a finite + difference on the position triple in guest RAM — so no speed field has to be + found first. The probe then holds each candidate input in turn and asks which + one changes that measured speed. (`findspeed.py` failed the other way round: + it assumed `RT` was the throttle and went looking for a field that rose.) + +* **Own state.** Every tick also copies a window of the player entity object. + Afterwards, offsets whose float value tracks the measured speed are candidate + speed/throttle fields, and offsets that only ever *fall* are candidate + hull/shield/ammo — the numbers survival needs. + +Flying straight into a firefight for 90 s is how earlier runs died, so the loop +keeps the collision avoidance from navigator.py armed the whole time and marks +any sample where it had to intervene: a phase that had to dodge is not a clean +speed measurement, and says so rather than being quietly averaged in. + +Usage: ctrl_probe.py [hold_s] [settle_s] +""" +import json +import math +import os +import struct +import sys +import time + +import numpy as np + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import gmem # noqa: E402 +import navigator # noqa: E402 +from flight_probe import Pad # noqa: E402 + +# 0x800 each way: the shield lives at pos+0x430 (own_state.py), which a +# 0x400 window silently cropped out of the first capture. +WIN_BACK = 0x800 # bytes of the player object kept before the position +WIN_FWD = 0x800 # ...and after +WIN = WIN_BACK + WIN_FWD +HZ = 20.0 + +# (label, [pad commands]) — everything the pad can do that might be a throttle. +# RB is left out: it is the fire button (autopilot-memory-driven.md) and firing +# during a speed measurement only invites return fire. +CANDIDATES = [ + ("RT", [("trig", "RT", 1.0)]), + ("LT", [("trig", "LT", 1.0)]), + ("RT+LT", [("trig", "RT", 1.0), ("trig", "LT", 1.0)]), + ("A", [("press", "A")]), + ("B", [("press", "B")]), + ("X", [("press", "X")]), + ("Y", [("press", "Y")]), + ("LB", [("press", "LB")]), + ("LS", [("press", "LS")]), + ("RS", [("press", "RS")]), + ("RY_up", [("axis", "RY", -1.0)]), + ("RY_down", [("axis", "RY", 1.0)]), + ("RX_right", [("axis", "RX", 1.0)]), + ("dpad_up", [("dpad", "up")]), + ("dpad_down", [("dpad", "down")]), + ("dpad_left", [("dpad", "left")]), + ("dpad_right", [("dpad", "right")]), +] + + +def apply(pad, cmds): + for c in cmds: + if c[0] == "trig": + pad.trig(c[1], c[2]) + elif c[0] == "axis": + pad.axis(c[1], c[2]) + elif c[0] == "press": + pad.press(c[1]) + elif c[0] == "dpad": + pad.f.write(f"dpad {c[1]}\n") + + +def clear(pad): + pad.reset() + pad.f.write("dpad center\n") + + +class Run: + def __init__(self, cfg, prefix, hold, settle): + self.W = navigator.World(cfg) + self.nav = navigator.Navigator(self.W, None, dry=True) + self.prefix = prefix + self.hold = hold + self.settle = settle + self.pad = Pad() + self.rows = [] # (t, phase, pos, speed, dodged) + self.win = [] # raw window bytes per tick + ents = self.W.scan() + me = [(off, va) for off, va in ents if "Player" in self.W.defs[va]] + if not me: + sys.exit("player entity not in the scan — not in flight?") + self.me_off = me[0][0] + self.me_name = self.W.defs[me[0][1]] + print(f"# player {self.me_name} pos off {self.me_off:#x} " + f"va {gmem.primary_va(self.me_off):#x} | {len(ents)} entities", + flush=True) + + # ------------------------------------------------------------- helpers + def pos(self): + return self.W.pos(self.me_off) + + def sticks_for(self, vec): + """Stick deflections that point the nose along `vec` (escape steering).""" + M = self.W.rot(self.me_off) + if M is None: + return 0.0, 0.0 + fwd = M[self.W.fwd_row] * self.W.fwd_sign + right = M[(self.W.fwd_row + 1) % 3] + up = np.cross(fwd, right) + ez = float(np.dot(vec, fwd)) + yaw = math.atan2(float(np.dot(vec, right)), ez if abs(ez) > 1e-3 else 1e-3) + pitch = math.atan2(float(np.dot(vec, up)), ez if abs(ez) > 1e-3 else 1e-3) + return (max(-1.0, min(1.0, 2.0 * yaw)), max(-1.0, min(1.0, -2.0 * pitch))) + + # ---------------------------------------------------------------- phase + def phase(self, label, cmds, secs): + clear(self.pad) + apply(self.pad, cmds) + t_end = time.time() + secs + dodged = False + while time.time() < t_end: + t = time.time() + p = self.pos() + if p is None: + break + self.rows.append([t, label, p, 0.0, 0]) + self.win.append(os.pread(self.W.fd, WIN, self.me_off - WIN_BACK)) + + # avoidance runs at 5 Hz; a real threat overrides the phase and the + # samples from here on are flagged + if len(self.rows) % 4 == 0: + ents = self.W.sample(t) + me = [e for e in ents if e[0] == self.me_off] + if me: + _, _, mp, mv, mr = me[0] + push, worst = self.nav.avoidance(mp, mv, mr, ents, self.me_off) + if float(np.linalg.norm(push)) > 0.6: + sx, sy = self.sticks_for(navigator.norm(push)) + self.pad.axis("LX", sx) + self.pad.axis("LY", sy) + dodged = True + self.rows[-1][4] = 1 + elif dodged: + self.pad.axis("LX", 0.0) + self.pad.axis("LY", 0.0) + time.sleep(max(0.0, 1.0 / HZ - (time.time() - t))) + return dodged + + def run(self): + t0 = time.time() + self.phase("base", [], self.settle * 2) + for label, cmds in CANDIDATES: + d = self.phase(label, cmds, self.hold) + self.phase(f"rest_{label}", [], self.settle) + sp = self.phase_speed(label) + print(f"[{time.time()-t0:6.1f}] {label:<10} " + f"v0={sp[0]:7.1f} v1={sp[1]:7.1f} d={sp[1]-sp[0]:+7.1f}" + f"{' (dodged)' if d else ''}", flush=True) + clear(self.pad) + self.speeds() + self.dump() + + # ------------------------------------------------------------ analysis + def speeds(self): + """Fill in per-tick speed by central difference on position.""" + for i, r in enumerate(self.rows): + j, k = max(0, i - 2), min(len(self.rows) - 1, i + 2) + dt = self.rows[k][0] - self.rows[j][0] + if dt > 1e-3: + r[3] = float(np.linalg.norm(self.rows[k][2] - self.rows[j][2])) / dt + + def phase_speed(self, label): + """(speed early, speed late) within a phase — needs speeds() first.""" + self.speeds() + v = [r[3] for r in self.rows if r[1] == label] + if len(v) < 6: + return (0.0, 0.0) + n = max(2, len(v) // 4) + return (float(np.mean(v[:n])), float(np.mean(v[-n:]))) + + def dump(self): + with open(self.prefix + ".csv", "w") as f: + f.write("t,phase,x,y,z,speed,dodged\n") + t0 = self.rows[0][0] + for t, ph, p, sp, dg in self.rows: + f.write(f"{t-t0:.3f},{ph},{p[0]:.3f},{p[1]:.3f},{p[2]:.3f}," + f"{sp:.3f},{dg}\n") + with open(self.prefix + ".bin", "wb") as f: + f.write(b"SYLPHCTR") + f.write(struct.pack(" 1e-6)) + cor = [] + for i in cand: + c = np.corrcoef(A[:, i], sp)[0, 1] + if np.isfinite(c): + cor.append((abs(c), c, i)) + cor.sort(reverse=True) + print("\n# object words correlating with measured speed " + "(offset relative to the position triple)") + for ac, c, i in cor[:12]: + off = i * 4 - WIN_BACK + print(f" pos{off:+#07x} r={c:+.3f} " + f"range {A[:, i].min():.3f} .. {A[:, i].max():.3f}") + + print("\n# words that never rise (candidate hull / shield / ammo)") + shown = 0 + for i in cand: + col = A[:, i] + if col[-1] >= col[0] - 1e-6: + continue + if np.max(np.diff(col)) > 1e-6: + continue + off = i * 4 - WIN_BACK + print(f" pos{off:+#07x} {col[0]:.3f} -> {col[-1]:.3f}") + shown += 1 + if shown >= 12: + break + if not shown: + print(" (none — nothing in the window decreased monotonically)") + + +def main(): + cfg = json.load(open(sys.argv[1])) + prefix = sys.argv[2] + hold = float(sys.argv[3]) if len(sys.argv) > 3 else 4.0 + settle = float(sys.argv[4]) if len(sys.argv) > 4 else 2.5 + Run(cfg, prefix, hold, settle).run() + + +if __name__ == "__main__": + main() diff --git a/tools/re-capture/ctrl_session.sh b/tools/re-capture/ctrl_session.sh new file mode 100755 index 0000000..74d5a46 --- /dev/null +++ b/tools/re-capture/ctrl_session.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# One background task: bind the player's transform, then run the control probe. +# +# Everything that must not be interrupted lives in a single task here, because +# the display and the emulator both die on their own every few minutes in this +# container and nothing may depend on surviving between tool calls. +# +# Assumes the game is ALREADY in flight (launch_mission.sh). Pass --boot to +# have it get there itself. +set -u +export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 +export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages +SD="$(cd "$(dirname "$0")" && pwd)" + +CFG=/tmp/nav-live.json +PRE=/tmp/ctrl +HOLD=3.0 +SETTLE=2.0 +if [ "${1:-}" = "--boot" ]; then + shift + "$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; } +fi +[ $# -ge 1 ] && CFG="$1" +[ $# -ge 2 ] && PRE="$2" +[ $# -ge 3 ] && HOLD="$3" +[ $# -ge 4 ] && SETTLE="$4" + +python3 "$SD/entities2.py" self 0x130 "$CFG" || { echo "TRANSFORM BIND FAILED"; exit 1; } +echo "--- config: $(cat "$CFG")" +python3 "$SD/ctrl_probe.py" "$CFG" "$PRE" "$HOLD" "$SETTLE" +echo "PROBE DONE" diff --git a/tools/re-capture/entities2.py b/tools/re-capture/entities2.py index a7174e0..541ca03 100644 --- a/tools/re-capture/entities2.py +++ b/tools/re-capture/entities2.py @@ -180,24 +180,35 @@ def main(): "(" + ",".join(f"{v:+.3f}" for v in row) + ")" for row in M)) if len(sys.argv) > 3 and found: import json - # forward axis = the row closest to our direction of travel - vdir = None + # Which of the orthonormal blocks is the CRAFT's attitude? The one + # with a row along the direction of travel. Taking found[0] is what + # produced a config with rot_delta -0x764 and a nonsense forward + # axis: several blocks inside the object are orthonormal (bone or + # camera frames), and only the craft's own has a row that tracks + # where the craft is going. p0 = np.array(pos) time.sleep(0.35) p1 = np.array(struct.unpack(">3f", os.pread(fd, 12, off))) - if np.linalg.norm(p1 - p0) > 1e-3: - vdir = (p1 - p0) / np.linalg.norm(p1 - p0) - rot_delta, M, rot_stride = found[0] - row, sign = 2, 1 - if vdir is not None: + step = p1 - p0 + if np.linalg.norm(step) < 1e-3: + sys.exit("craft is not moving — cannot bind the forward axis") + vdir = step / np.linalg.norm(step) + best = None + for d, M, st in found: cs = [float(M[r] @ vdir) for r in range(3)] - row = int(np.argmax([abs(c) for c in cs])) - sign = 1 if cs[row] > 0 else -1 - print(f"# forward axis = row {row} (sign {sign:+d}), " - f"cos={cs[row]:+.3f}") + r = int(np.argmax([abs(c) for c in cs])) + if best is None or abs(cs[r]) > abs(best[3]): + best = (d, M, st, cs[r], r) + rot_delta, M, rot_stride, cos, row = best + sign = 1 if cos > 0 else -1 + print(f"# attitude block pos{rot_delta:+#07x} stride {rot_stride}: " + f"forward = row {row} (sign {sign:+d}), cos={cos:+.3f}") + if abs(cos) < 0.9: + print("# WARNING: no block tracks the flight path (|cos| < 0.9)" + " — the craft may be drifting hard; re-run while flying straight") cfg = {"def_delta": delta, "rot_delta": rot_delta, "rot_stride": rot_stride, - "fwd_row": row, "fwd_sign": sign, + "fwd_row": row, "fwd_sign": sign, "fwd_cos": round(cos, 4), "va_lo": ENT_VA_LO, "va_hi": ENT_VA_HI} json.dump(cfg, open(sys.argv[3], "w"), indent=1) print("# wrote " + sys.argv[3] + ": " + json.dumps(cfg)) diff --git a/tools/re-capture/fly_session.sh b/tools/re-capture/fly_session.sh new file mode 100755 index 0000000..75d2231 --- /dev/null +++ b/tools/re-capture/fly_session.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# One background task: boot -> mission -> bind the transform -> fly the pilot, +# with periodic screenshots so the outcome has visual evidence and not just a log. +# +# It is one task on purpose: the display and the emulator both die on their own +# every few minutes in this container, so nothing may depend on surviving +# between tool calls. +set -u +export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 +export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages +SD="$(cd "$(dirname "$0")" && pwd)" +SECS="${1:-240}" +TAG="${2:-pilot}" +SHOTS=/sylph-home/re/shots +CFG=/tmp/nav-live.json + +"$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; } +python3 "$SD/entities2.py" self 0x130 "$CFG" || { echo "BIND FAILED"; exit 1; } +echo "--- config: $(cat "$CFG")" +python3 "$SD/own_state.py" "$CFG" 3 "/tmp/$TAG-own.json" + +( for i in $(seq 1 12); do sleep 25; screenshot "$SHOTS/$TAG-$i.png" >/dev/null 2>&1; done ) & +SHOTTER=$! +python3 "$SD/pilot.py" "$CFG" "$SECS" +kill $SHOTTER 2>/dev/null +screenshot "$SHOTS/$TAG-end.png" >/dev/null 2>&1 +echo "SESSION DONE" diff --git a/tools/re-capture/launch_mission.sh b/tools/re-capture/launch_mission.sh index 4192b51..52cfd5d 100755 --- a/tools/re-capture/launch_mission.sh +++ b/tools/re-capture/launch_mission.sh @@ -55,8 +55,8 @@ fi step up # BRIEFINGS -> TAKE OFF vgamepad tap A 250 -sleep 75 # launch cinematic + stage load + objective card -shot "lm-objective.png" -vgamepad tap A 250; sleep 6 # dismiss the OBJECTIVE panel +# The launch cinematic + stage load + objective card is NOT a fixed 75 s under +# lavapipe; wait for the flight HUD itself. +"$SD/wait_flight.sh" 300 || { echo "NEVER REACHED FLIGHT"; exit 1; } shot "lm-flight.png" echo "IN FLIGHT (emulator left running)" diff --git a/tools/re-capture/navigator.py b/tools/re-capture/navigator.py new file mode 100644 index 0000000..3536421 --- /dev/null +++ b/tools/re-capture/navigator.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +"""Drift-aware navigation with collision avoidance, driven from guest memory. + +Three things the pursuit loop in autopilot3.py did not do: + +* **See everything.** It enumerated entities by looking for things that *move*, + so stations, hulls and parked structures were invisible — exactly the objects + you crash into. This scans the entity heap for words that equal a known unit + definition address and takes `position = hit - 0x130`, which finds every + entity whether it is moving or not. + +* **Know how big they are.** Each entity's definition carries `Size_Radius` + (+0x50) and `Size_X/Y/Z` (+0x30/34/38) — fields already solved in + docs/re/structures/unit-struct-runtime.md — so the avoidance radius is the + game's own number, not a guess. + +* **Account for drift.** The craft does not turn where it points: velocity lags + the nose like an aircraft with sideslip. Steering the *nose* at a target + therefore steers the *flight path* somewhere else, wide and late. This + measures the lag online (the angle between nose and velocity, and how fast + the velocity vector is actually swinging) and commands the nose *ahead* of + where the flight path should go, by that lag. + +Avoidance is closest-point-of-approach, not distance: what matters is whether +the two paths will intersect within a horizon, which is why a fast crossing +target is dangerous at 2 km and a station drifting away is not at 300 m. + +Usage: navigator.py [seconds] [--dry] +""" +import json +import math +import os +import struct +import sys +import time +from collections import Counter + +import numpy as np + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import gmem # noqa: E402 +import gworld # noqa: E402 +import entities2 # noqa: E402 +from flight_probe import Pad # noqa: E402 + +# confirmed fields of the parsed unit definition (unit-struct-runtime.md) +DEF_SIZE_X, DEF_SIZE_Y, DEF_SIZE_Z, DEF_SIZE_R = 0x30, 0x34, 0x38, 0x50 + + +def norm(v): + n = float(np.linalg.norm(v)) + return v / n if n > 1e-9 else v * 0.0 + + +def ang(a, b): + return math.acos(max(-1.0, min(1.0, float(np.dot(norm(a), norm(b)))))) + + +class World: + def __init__(self, cfg): + self.cfg = cfg + self.w = gworld.World() + self.fd, self.size = self.w.fd, self.w.size + self.delta = cfg["def_delta"] + self.rot_delta = cfg["rot_delta"] + self.rot_stride = cfg.get("rot_stride", 12) + self.fwd_row = cfg["fwd_row"] + self.fwd_sign = cfg["fwd_sign"] + self.defs = {} # def_va -> name + self.def_word = {} # 4-byte BE -> def_va + self.radius = {} # def_va -> collision radius + self.prev = {} # pos_off -> (t, pos) for velocity + self.vel = {} # pos_off -> smoothed velocity + self.ents = [] + self._load_defs() + + def _load_defs(self): + for off in self.w.scan_vtable(gworld.DEF_VTABLE): + nm = self.w.name_of(off) + if not (nm and nm.startswith("UN_")): + continue + va = gmem.primary_va(off) + if va is None: + continue + self.defs[va] = nm + self.def_word[struct.pack(">I", va)] = va + b = os.pread(self.fd, 0x60, off) + try: + sx, sy, sz = (struct.unpack_from(">f", b, o)[0] + for o in (DEF_SIZE_X, DEF_SIZE_Y, DEF_SIZE_Z)) + sr = struct.unpack_from(">f", b, DEF_SIZE_R)[0] + except struct.error: + sx = sy = sz = sr = 0.0 + vals = [v for v in (sr, sx, sy, sz) if math.isfinite(v) and 0 < v < 1e5] + self.radius[va] = max(vals) if vals else 50.0 + + # ------------------------------------------------------------- entities + def scan(self): + """Every entity in the heap — moving or not — by its definition pointer.""" + lo = gmem.va_to_off(self.cfg["va_lo"]) + hi = gmem.va_to_off(self.cfg["va_hi"]) + found = [] + # numpy, not a per-word python loop: the entity heap is 16 MB, so + # stepping it 4 bytes at a time costs seconds per scan and the control + # loop spends its whole budget scanning instead of flying. + want = np.array(sorted(self.defs.keys()), dtype=np.uint32) + for a, b in gmem.extents(self.fd, self.size): + a, b = max(a, lo), min(b, hi) + n = (b - a) // 4 * 4 + if n < 64: + continue + arr = np.frombuffer(os.pread(self.fd, n, a), dtype=">u4").astype(np.uint32) + for k4 in np.flatnonzero(np.isin(arr, want)): + k = int(k4) * 4 + va = int(arr[k4]) + poff = a + k - self.delta + if poff < 0: + continue + pb = os.pread(self.fd, 12, poff) + if len(pb) < 12: + continue + p = np.array(struct.unpack(">3f", pb)) + if not np.all(np.isfinite(p)) or np.max(np.abs(p)) > 1e7: + continue + found.append((poff, va)) + # De-duplicate by POSITION: one entity is mirrored at several + # addresses, so keying on the address keeps every copy and the scene + # looks several times more crowded than it is. + seen, uniq = {}, {} + for poff, va in found: + p = self.pos(poff) + if p is None: + continue + key = (va, tuple(np.round(p, 0))) + if key in seen: + continue + seen[key] = poff + uniq[poff] = va + self.ents = list(uniq.items()) + return self.ents + + def pos(self, off): + b = os.pread(self.fd, 12, off) + if len(b) < 12: + return None + p = np.array(struct.unpack(">3f", b)) + return p if np.all(np.isfinite(p)) else None + + def rot(self, off): + n = self.rot_stride * 2 + 12 + b = os.pread(self.fd, n, off + self.rot_delta) + if len(b) < n: + return None + M = np.array([struct.unpack_from(">3f", b, self.rot_stride * r) for r in range(3)]) + if not np.all(np.isfinite(M)) or np.max(np.abs(M @ M.T - np.eye(3))) > 5e-3: + return None + return M + + def sample(self, t): + """[(off, name, pos, vel, radius)] with velocity by finite difference.""" + out = [] + for off, va in self.ents: + p = self.pos(off) + if p is None: + continue + # A frame the emulator did not advance gives an identical position + # and a bogus zero velocity, which then reads as "stopped" and + # wrecks both the drift estimate and every closing-rate. Keep the + # last good velocity instead, and smooth it. + prev = self.prev.get(off) + v = self.vel.get(off, np.zeros(3)) + if prev is not None: + dt = t - prev[0] + moved = float(np.linalg.norm(p - prev[1])) + if dt > 0.02 and moved > 1e-4: + inst = (p - prev[1]) / dt + if float(np.linalg.norm(inst)) < 5000.0: + v = 0.5 * v + 0.5 * inst if np.any(v) else inst + self.prev[off] = (t, p) + else: + self.prev[off] = (t, p) + self.vel[off] = v + out.append((off, self.defs[va], p, v, self.radius[va])) + return out + + +def faction(nm): + b = nm[3:] + return "ADAN" if b.startswith("be") or b.startswith("e") else "TCAF" + + +class Navigator: + KP, KD = 2.2, 0.45 + FIRE_CONE = math.radians(9) + FIRE_RANGE = 6000.0 + HORIZON = 6.0 # s of look-ahead for collision checks + MARGIN_BIG = 220.0 # clearance around hulls and structures + MARGIN_SMALL = 45.0 # ...around fighters, which manoeuvre themselves + BIG_RADIUS = 120.0 # above this an entity counts as a structure + SELF_MIRROR = 25.0 # the same object is mirrored at several addresses + + def __init__(self, W, pad, dry=False, log=sys.stdout): + self.W = W + self.pad = pad + self.dry = dry + self.log = log + self.prevM = None + self.firing = False + self.tau = 0.8 # velocity-lag time constant, refined online + + # ------------------------------------------------------------ drift + def update_tau(self, fwd, vel, prev_vhat, dt): + """How long the flight path takes to catch the nose. + + The velocity vector swings toward the nose; the angle between them + divided by the rate the velocity is actually swinging is that lag, and + it is what the nose has to be commanded ahead by. + """ + if prev_vhat is None or dt <= 1e-3: + return + vh = norm(vel) + if np.linalg.norm(vh) < 1e-6: + return + swing = ang(prev_vhat, vh) / dt # rad/s the path is turning + lag = ang(fwd, vh) # rad the path is behind + if swing > 0.02 and lag > 0.02: + tau = lag / swing + if 0.05 < tau < 5.0: + self.tau = 0.9 * self.tau + 0.1 * tau + + # ------------------------------------------------- collision avoidance + def avoidance(self, me_p, me_v, me_r, ents, me_off): + """Sum of escape directions, weighted by how soon and how close.""" + push = np.zeros(3) + worst = None + for off, nm, p, v, r in ents: + if off == me_off: + continue + rel_p = p - me_p + rel_v = v - me_v + d = float(np.linalg.norm(rel_p)) + # The same entity exists at several mirrored addresses, so our own + # copy shows up as an obstacle at zero distance and pins the sticks + # at full deflection forever. Anything this close is us. + if d < self.SELF_MIRROR: + continue + # A wingman flying formation is metres away by design and steers + # itself; giving it a hull-sized margin makes the loop thrash. + big = r >= self.BIG_RADIUS + margin = self.MARGIN_BIG if big else self.MARGIN_SMALL + if not big and faction(nm) == "TCAF": + margin *= 0.5 + safe = me_r + r + margin + if d > 1e4: + continue + vv = float(np.dot(rel_v, rel_v)) + t_cpa = 0.0 if vv < 1e-6 else -float(np.dot(rel_p, rel_v)) / vv + t_cpa = max(0.0, min(self.HORIZON, t_cpa)) + cpa = rel_p + rel_v * t_cpa + miss = float(np.linalg.norm(cpa)) + if miss >= safe: + continue + urgency = (1.0 - miss / safe) * (1.0 - t_cpa / self.HORIZON) + if urgency <= 0: + continue + esc = -norm(cpa) if miss > 1e-3 else norm(np.cross(rel_p, me_v)) + if np.linalg.norm(esc) < 1e-6: + esc = norm(np.cross(rel_p, np.array([0.0, 1.0, 0.0]))) + push += esc * urgency + if worst is None or urgency > worst[0]: + worst = (urgency, nm, d, miss, t_cpa) + return push, worst + + # -------------------------------------------------------------- target + def pick(self, me_p, me_v, fwd, ents, me_off): + speed = max(float(np.linalg.norm(me_v)), 1.0) + best, bestscore = None, 1e18 + for off, nm, p, v, r in ents: + if off == me_off or faction(nm) != "ADAN": + continue + rel = p - me_p + d = float(np.linalg.norm(rel)) + if d < 1e-3: + continue + # lead: where it will be when a shot gets there + lead = p + v * (d / max(speed, 200.0)) + rel_l = lead - me_p + theta = ang(rel_l, fwd) + score = d * (1.0 + 3.0 * (theta / math.pi) ** 2) + if score < bestscore: + best, bestscore = (off, nm, lead, rel_l, d), score + return best + + # ---------------------------------------------------------------- step + def step(self, t, dt, prev_vhat): + ents = self.W.sample(t) + me = None + for e in ents: + if "Player" in e[1]: + me = e + break + if me is None: + return "no-player", prev_vhat + me_off, me_nm, me_p, me_v, me_r = me + M = self.W.rot(me_off) + if M is None: + return "no-orientation", prev_vhat + fwd = M[self.W.fwd_row] * self.W.fwd_sign + right = M[(self.W.fwd_row + 1) % 3] + up = np.cross(fwd, right) + + speed = float(np.linalg.norm(me_v)) + vhat = norm(me_v) if speed > 1.0 else fwd + self.update_tau(fwd, me_v, prev_vhat, dt) + + # body angular velocity for the damping term + w = np.zeros(3) + if self.prevM is not None and dt > 1e-3: + D = self.prevM @ M.T + w = np.array([D[2, 1] - D[1, 2], D[0, 2] - D[2, 0], D[1, 0] - D[0, 1]]) / (2 * dt) + self.prevM = M + + tgt = self.pick(me_p, me_v, fwd, ents, me_off) + goal = norm(tgt[3]) if tgt else fwd + + push, worst = self.avoidance(me_p, me_v, me_r, ents, me_off) + pn = float(np.linalg.norm(push)) + # avoidance outranks the target when it is urgent + want = norm(goal + push * (3.0 if pn > 0.6 else 1.5)) if pn > 1e-6 else goal + + # Command the NOSE ahead of where the flight path must go, by the + # measured lag -- steering the nose straight at the target makes the + # path arrive wide and late. + nose_cmd = norm(want + (want - vhat) * min(self.tau * 1.6, 2.5)) + + ex = float(np.dot(nose_cmd, right)) + ey = float(np.dot(nose_cmd, up)) + ez = float(np.dot(nose_cmd, fwd)) + yaw = math.atan2(ex, ez if abs(ez) > 1e-3 else 1e-3) + pitch = math.atan2(ey, ez if abs(ez) > 1e-3 else 1e-3) + if ez < 0: + yaw = math.copysign(math.pi / 2, ex if ex else 1.0) + + sx = max(-1.0, min(1.0, self.KP * yaw - self.KD * float(np.dot(w, up)))) + sy = max(-1.0, min(1.0, -(self.KP * pitch - self.KD * float(np.dot(w, right))))) + + # fire only when the *flight path* is clear and the nose is on target + aim_ok = tgt and abs(yaw) < self.FIRE_CONE and abs(pitch) < self.FIRE_CONE + fire = bool(aim_ok and tgt[4] < self.FIRE_RANGE and pn < 1.2) + + if not self.dry: + self.pad.axis("LX", sx) + self.pad.axis("LY", sy) + if fire != self.firing: + (self.pad.press if fire else self.pad.release)("RB") + self.firing = fire + + drift = math.degrees(ang(fwd, vhat)) + msg = (f"spd={speed:6.0f} drift={drift:5.1f}d tau={self.tau:4.2f} " + f"yaw={math.degrees(yaw):+6.1f} pit={math.degrees(pitch):+6.1f} " + f"stick=({sx:+.2f},{sy:+.2f}) fire={int(fire)}") + if tgt: + msg += f" tgt={tgt[1][3:22]:<20} d={tgt[4]:7.0f}" + if worst: + msg += (f" | AVOID {worst[1][3:20]} miss={worst[3]:6.0f} " + f"t={worst[4]:4.1f}s u={worst[0]:.2f}") + return msg, vhat + + def run(self, secs, hz=10.0): + self.W.scan() + t0 = time.time() + last, last_scan, prev_vhat = t0, 0.0, None + while time.time() - t0 < secs: + t = time.time() + if t - last_scan > 4.0: + ents = self.W.scan() + last_scan = t + c = Counter(faction(self.W.defs[va]) for _, va in ents) + print(f"[{t-t0:6.1f}] scan: {len(ents)} entities {dict(c)}", + file=self.log, flush=True) + msg, prev_vhat = self.step(t, t - last, prev_vhat) + last = t + print(f"[{t-t0:6.1f}] {msg}", file=self.log, flush=True) + time.sleep(max(0, 1.0 / hz - (time.time() - t))) + if not self.dry: + self.pad.reset() + + +def main(): + cfg = json.load(open(sys.argv[1])) + secs = float(sys.argv[2]) if len(sys.argv) > 2 else 90.0 + W = World(cfg) + Navigator(W, Pad(), dry="--dry" in sys.argv).run(secs) + + +if __name__ == "__main__": + main() diff --git a/tools/re-capture/own_state.py b/tools/re-capture/own_state.py new file mode 100644 index 0000000..f4fcc71 --- /dev/null +++ b/tools/re-capture/own_state.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Where does the craft keep its hull and shield? Anchor on the definition. + +The parsed unit definition already has solved fields (unit-struct-runtime.md): +`HP` at `+0x054`, shield `MaxValue` at `+0x238`, shield `ChargeSpeed` at +`+0x244`. A craft that has taken no damage is at full hull and full shield, so +its live entity object must *contain those very numbers*. That turns "find the +HP field" from a value-scan over 4 GB into: read two floats from the definition, +then look for them inside the entity object. + +Then watch the candidates while the craft is under fire. The live field is the +one that falls; a copy of the definition value that never moves is not it. + +Usage: own_state.py [watch_seconds] [out.json] +""" +import json +import os +import struct +import sys +import time + +import numpy as np + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import gmem # noqa: E402 +import navigator # noqa: E402 + +DEF_HP, DEF_SHIELD_MAX, DEF_SHIELD_CHG = 0x054, 0x238, 0x244 +BACK, FWD = 0x800, 0x800 + + +def near(a, v): + return np.abs(a - v) <= max(1e-3, abs(v) * 1e-4) + + +def main(): + cfg = json.load(open(sys.argv[1])) + secs = float(sys.argv[2]) if len(sys.argv) > 2 else 40.0 + out = sys.argv[3] if len(sys.argv) > 3 else None + + W = navigator.World(cfg) + ents = W.scan() + me = [(off, va) for off, va in ents if "Player" in W.defs[va]] + if not me: + sys.exit("player entity not found — not in flight?") + me_off, def_va = me[0] + print(f"# player {W.defs[def_va]} pos off {me_off:#x} def {def_va:#010x}") + + d = os.pread(W.fd, 0x300, gmem.va_to_off(def_va)) + want = {} + for nm, o in (("HP", DEF_HP), ("Shield_MaxValue", DEF_SHIELD_MAX), + ("Shield_ChargeSpeed", DEF_SHIELD_CHG)): + (v,) = struct.unpack_from(">f", d, o) + want[nm] = v + print(f"# definition {nm:<18} = {v:g}") + + blob = os.pread(W.fd, BACK + FWD, me_off - BACK) + arr = np.frombuffer(blob, dtype=">f4").astype(np.float64) + cands = [] # (label, delta_from_position) + for nm, v in want.items(): + if not np.isfinite(v) or v == 0.0: + continue + for i in np.flatnonzero(near(arr, v)): + cands.append((nm, int(i) * 4 - BACK)) + print(f"# {len(cands)} candidate offsets inside the entity object:") + for nm, dlt in cands: + print(f" pos{dlt:+#07x} == definition {nm}") + if not cands: + print("# none — the object does not carry the definition's own numbers" + " at this offset window; widen BACK/FWD or the craft is damaged") + + # ---- watch them; the live field is the one that moves + hist = {c: [] for c in cands} + t0 = time.time() + last_print = 0.0 + while time.time() - t0 < secs: + p = W.pos(me_off) + if p is None: + print("# player object gone (death / stage change?)") + break + w = os.pread(W.fd, BACK + FWD, me_off - BACK) + a = np.frombuffer(w, dtype=">f4").astype(np.float64) + for c in cands: + i = (c[1] + BACK) // 4 + hist[c].append(float(a[i])) + t = time.time() - t0 + if t - last_print > 4.0: + last_print = t + cur = " ".join(f"{c[0][:4]}{c[1]:+#x}={hist[c][-1]:.1f}" for c in cands[:6]) + print(f"[{t:6.1f}] {cur}", flush=True) + time.sleep(0.2) + + print("\n# offset anchor first last min moved") + moving = [] + for c in cands: + h = np.array(hist[c]) + if not len(h): + continue + mv = float(h.max() - h.min()) + print(f" pos{c[1]:+#07x} {c[0]:<18} {h[0]:8.1f} {h[-1]:8.1f} " + f"{h.min():8.1f} {mv:7.3f}") + if mv > 1e-3: + moving.append({"anchor": c[0], "delta": c[1], + "first": h[0], "last": float(h[-1]), + "min": float(h.min())}) + if not moving: + print("# nothing moved — the craft took no damage during the window") + if out: + json.dump({"player": W.defs[def_va], "def_va": def_va, + "definition": want, + "candidates": [{"anchor": a, "delta": b} for a, b in cands], + "moved": moving}, open(out, "w"), indent=1) + print(f"# wrote {out}") + + +if __name__ == "__main__": + main() diff --git a/tools/re-capture/pilot.py b/tools/re-capture/pilot.py new file mode 100644 index 0000000..936b955 --- /dev/null +++ b/tools/re-capture/pilot.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +"""A pilot that tries to stay alive, not just to shoot. + +Every earlier loop flew a straight pursuit and was shot down; the trace from +ctrl_probe.py shows why — 1500 hull points gone in twelve seconds while sitting +in a turret's line of fire, with no reaction of any kind. Three measured facts +make a reaction possible: + +* **Hull is `position + 0x154`** — at spawn it equals the unit definition's own + `HP` (1500 for the Delta Saber), it steps down 30/60/90 per hit, and it goes + negative at death. So damage is observable *as it happens*, not inferred. +* **`RT` accelerates and `LT` brakes**, and the setting persists: measured + ground speed went 488 → 1510 under RT, 488 → 174 under LT and then *stayed* + near 130 with the sticks neutral. (The older note "RT is not the throttle" was + drawn from a value-scan for a speed field, not from measuring the speed.) +* **`RB` fires** (autopilot-memory-driven.md). + +So the loop is a state machine on damage rather than a pure pursuit: + + ENGAGE chase and shoot the nearest hostile fighter + EVADE entered the moment the hull drops — turn away from the threats, + full throttle, jink; leave only after several quiet seconds + RETIRE hull below a floor: break for the friendly capital ship, which the + mission's own hint says is where you resupply + +Turrets are treated as threats to be *kept at a distance*, not as targets: the +objective is the invading fighters, and the turret is what killed every previous +run. + +Usage: pilot.py [seconds] [--dry] +""" +import json +import math +import os +import struct +import sys +import time +from collections import deque + +import numpy as np + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import navigator # noqa: E402 +from navigator import ang, norm # noqa: E402 +from flight_probe import Pad # noqa: E402 + +HULL_OFF = 0x154 # confirmed: == definition HP at spawn, falls when hit +SHIELD_OFF = 0x430 # candidate: == definition Shield MaxValue at spawn + + +class Pilot: + KP, KD = 2.2, 0.45 + FIRE_CONE = math.radians(9) + FIRE_RANGE = 5000.0 + TURRET_KEEPOUT = 2500.0 # ...and stay this far from things that shoot back + EVADE_QUIET = 5.0 # seconds without damage before re-engaging + RETIRE_FRAC = 0.30 # hull fraction that sends us home + HZ = 8.0 + + def __init__(self, W, pad, dry=False, log=sys.stdout): + self.W = W + self.pad = pad + self.dry = dry + self.log = log + # closest-point-of-approach avoidance is navigator.py's, reused as-is + self.av = navigator.Navigator(W, pad, dry=True, log=log) + self.prevM = None + self.firing = False + self.throttle = 0 # -1 brake, 0 coast, +1 accelerate + self.mode = "ENGAGE" + self.hp_hist = deque(maxlen=64) + self.hp0 = None + self.last_hit = -1e9 + self.threat_dir = None + + # ------------------------------------------------------------ own state + def own(self, off): + b = os.pread(self.W.fd, 8, off + HULL_OFF) + hull = struct.unpack_from(">f", b, 0)[0] if len(b) >= 4 else float("nan") + b2 = os.pread(self.W.fd, 4, off + SHIELD_OFF) + shield = struct.unpack(">f", b2)[0] if len(b2) == 4 else float("nan") + return hull, shield + + def set_throttle(self, want): + """RT / LT are a persistent setting, so only send the change.""" + if want == self.throttle or self.dry: + return + self.pad.trig("RT", 1.0 if want > 0 else 0.0) + self.pad.trig("LT", 1.0 if want < 0 else 0.0) + self.throttle = want + + # -------------------------------------------------------------- targets + def hostiles(self, ents, me_off): + out = [] + for off, nm, p, v, r in ents: + if off == me_off or navigator.faction(nm) != "ADAN": + continue + out.append((off, nm, p, v, r, "Turret" in nm or r >= navigator.Navigator.BIG_RADIUS)) + return out + + def pick(self, me_p, me_v, fwd, hos): + """Nearest *fighter*, weighted by how far off the nose it is.""" + speed = max(float(np.linalg.norm(me_v)), 1.0) + best, bestscore = None, 1e18 + for off, nm, p, v, r, hard in hos: + if hard: + continue # turrets and hulls are not the objective + rel = p - me_p + d = float(np.linalg.norm(rel)) + if d < 1e-3: + continue + lead = p + v * (d / max(speed, 300.0)) + theta = ang(lead - me_p, fwd) + score = d * (1.0 + 3.0 * (theta / math.pi) ** 2) + if score < bestscore: + best, bestscore = (off, nm, lead, lead - me_p, d), score + return best + + def threat_vector(self, me_p, hos): + """Where the danger is: inverse-square weighted direction to shooters.""" + acc = np.zeros(3) + for off, nm, p, v, r, hard in hos: + rel = p - me_p + d = float(np.linalg.norm(rel)) + if d < 1.0 or d > 8000.0: + continue + w = (1500.0 / d) ** 2 * (3.0 if hard else 1.0) + acc += norm(rel) * w + return norm(acc) if np.linalg.norm(acc) > 1e-6 else None + + def friendly_base(self, ents, me_off): + """The biggest friendly — the carrier the briefing says to resupply at.""" + best = None + for off, nm, p, v, r in ents: + if off == me_off or navigator.faction(nm) != "TCAF": + continue + if best is None or r > best[4]: + best = (off, nm, p, v, r) + return best + + # ------------------------------------------------------------ steering + def sticks(self, want, M, w): + fwd = M[self.W.fwd_row] * self.W.fwd_sign + right = M[(self.W.fwd_row + 1) % 3] + up = np.cross(fwd, right) + ex, ey, ez = (float(np.dot(want, right)), float(np.dot(want, up)), + float(np.dot(want, fwd))) + yaw = math.atan2(ex, ez if abs(ez) > 1e-3 else 1e-3) + pitch = math.atan2(ey, ez if abs(ez) > 1e-3 else 1e-3) + if ez < 0: # target behind: commit to a full turn + yaw = math.copysign(math.pi / 2, ex if ex else 1.0) + sx = max(-1.0, min(1.0, self.KP * yaw - self.KD * float(np.dot(w, up)))) + sy = max(-1.0, min(1.0, -(self.KP * pitch - self.KD * float(np.dot(w, right))))) + return sx, sy, yaw, pitch + + # ---------------------------------------------------------------- step + def step(self, t, dt): + ents = self.W.sample(t) + me = next((e for e in ents if "Player" in e[1]), None) + if me is None: + return None + me_off, me_nm, me_p, me_v, me_r = me + M = self.W.rot(me_off) + if M is None: + return "no-orientation" + fwd = M[self.W.fwd_row] * self.W.fwd_sign + right = M[(self.W.fwd_row + 1) % 3] + up = np.cross(fwd, right) + speed = float(np.linalg.norm(me_v)) + + hull, shield = self.own(me_off) + if self.hp0 is None and math.isfinite(hull) and hull > 0: + self.hp0 = hull + self.hp_hist.append((t, hull)) + # damage over the last ~2 s; the hull only ever falls, so any drop is a hit + recent = [h for (ts, h) in self.hp_hist if t - ts <= 2.0] + dmg = (max(recent) - hull) if recent else 0.0 + if dmg > 0.5: + self.last_hit = t + if hull <= 0: + return "DEAD" + + # body angular velocity, for the damping term + w = np.zeros(3) + if self.prevM is not None and dt > 1e-3: + D = self.prevM @ M.T + w = np.array([D[2, 1] - D[1, 2], D[0, 2] - D[2, 0], D[1, 0] - D[0, 1]]) / (2 * dt) + self.prevM = M + + hos = self.hostiles(ents, me_off) + self.threat_dir = self.threat_vector(me_p, hos) + frac = hull / self.hp0 if self.hp0 else 1.0 + + # ---- mode + if frac <= self.RETIRE_FRAC: + self.mode = "RETIRE" + elif t - self.last_hit < self.EVADE_QUIET: + self.mode = "EVADE" + else: + self.mode = "ENGAGE" + + tgt = self.pick(me_p, me_v, fwd, hos) + push, worst = self.av.avoidance(me_p, me_v, me_r, ents, me_off) + + if self.mode == "EVADE": + # Away from the guns, plus a jink so a straight escape line is not + # itself an easy solution for whatever is shooting. + away = -self.threat_dir if self.threat_dir is not None else fwd + jink = right * math.sin(t * 1.7) * 0.5 + up * math.cos(t * 2.3) * 0.35 + want = norm(away + jink) + self.set_throttle(+1) + fire = False + elif self.mode == "RETIRE": + base = self.friendly_base(ents, me_off) + if base is not None: + rel = base[2] - me_p + d = float(np.linalg.norm(rel)) + want = norm(rel) if d > base[4] + 400.0 else norm(np.cross(rel, up)) + else: + want = -self.threat_dir if self.threat_dir is not None else fwd + self.set_throttle(+1) + fire = False + else: + # Straight lead pursuit and fly *through*. An earlier version orbited + # once inside a standoff radius and braked while doing it: it then + # circled one attacker for 40 s at ~700 m, at 60-100 units/s, never + # inside the firing cone. Overshooting and re-acquiring is better + # than a stall in the middle of a battle; collision avoidance already + # keeps a fighter-sized margin. + want = norm(tgt[3]) if tgt else fwd + if tgt and tgt[4] > 2500.0: + self.set_throttle(+1) + elif tgt and tgt[4] < 500.0 and speed > 900.0: + self.set_throttle(-1) + else: + self.set_throttle(0) + fire = True + + # a turret inside its keep-out radius outranks the target + for off, nm, p, v, r, hard in hos: + if not hard: + continue + d = float(np.linalg.norm(p - me_p)) + if d < self.TURRET_KEEPOUT: + want = norm(want + norm(me_p - p) * (2.0 * (1.0 - d / self.TURRET_KEEPOUT))) + break + + pn = float(np.linalg.norm(push)) + if pn > 1e-6: + want = norm(want + push * (3.0 if pn > 0.6 else 1.5)) + + sx, sy, yaw, pitch = self.sticks(want, M, w) + # The firing gate has to be measured against the TARGET, not against the + # commanded direction: `want` carries the avoidance and keep-out terms, + # so gating on it means the guns stay cold exactly when the loop is + # manoeuvring — which is most of a dogfight. + aim = self.sticks(norm(tgt[3]), M, w)[2:] if tgt else (math.pi, math.pi) + aim_ok = abs(aim[0]) < self.FIRE_CONE and abs(aim[1]) < self.FIRE_CONE + fire = bool(fire and tgt and aim_ok and tgt[4] < self.FIRE_RANGE and pn < 1.2) + + if not self.dry: + self.pad.axis("LX", sx) + self.pad.axis("LY", sy) + if fire != self.firing: + (self.pad.press if fire else self.pad.release)("RB") + self.firing = fire + + msg = (f"{self.mode:<7} hull={hull:6.0f} shd={shield:6.0f} spd={speed:6.0f} " + f"thr={self.throttle:+d} yaw={math.degrees(yaw):+6.1f} " + f"pit={math.degrees(pitch):+6.1f} aim={math.degrees(aim[0]):+6.1f}" + f"/{math.degrees(aim[1]):+6.1f} fire={int(fire)}") + if dmg > 0.5: + msg += f" HIT -{dmg:.0f}" + if tgt: + msg += f" tgt={tgt[1][3:24]:<21} d={tgt[4]:6.0f}" + if worst: + msg += f" | AVOID {worst[1][3:18]} miss={worst[3]:5.0f} t={worst[4]:4.1f}" + return msg + + def run(self, secs): + self.W.scan() + t0 = time.time() + last, last_scan = t0, 0.0 + hostiles0 = None + while time.time() - t0 < secs: + t = time.time() + if t - last_scan > 5.0: + ents = self.W.scan() + last_scan = t + n_ad = sum(1 for _, va in ents + if navigator.faction(self.W.defs[va]) == "ADAN") + if hostiles0 is None: + hostiles0 = n_ad + print(f"[{t-t0:6.1f}] scan: {len(ents)} entities, {n_ad} ADAN " + f"(start {hostiles0})", file=self.log, flush=True) + msg = self.step(t, t - last) + last = t + if msg is None: + print(f"[{t-t0:6.1f}] player object gone — stopping", + file=self.log, flush=True) + break + print(f"[{t-t0:6.1f}] {msg}", file=self.log, flush=True) + if msg == "DEAD": + break + time.sleep(max(0.0, 1.0 / self.HZ - (time.time() - t))) + if not self.dry: + self.pad.reset() + print(f"# flew {time.time()-t0:.0f}s", file=self.log, flush=True) + + +def main(): + cfg = json.load(open(sys.argv[1])) + secs = float(sys.argv[2]) if len(sys.argv) > 2 else 180.0 + W = navigator.World(cfg) + Pilot(W, Pad(), dry="--dry" in sys.argv).run(secs) + + +if __name__ == "__main__": + main() diff --git a/tools/re-capture/wait_flight.sh b/tools/re-capture/wait_flight.sh new file mode 100755 index 0000000..85fd9a2 --- /dev/null +++ b/tools/re-capture/wait_flight.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Wait until the game is actually FLYING, then return — do not guess with sleeps. +# +# `launch_mission.sh` used to allow a fixed 75 s for the launch cinematic, the +# stage load and the objective card. Under lavapipe that is not a constant: one +# run needed 75 s, the next was still in Raymond's dialogue at 140 s, so the A +# meant for the OBJECTIVE card was swallowed by the cutscene and the card sat +# there forever. +# +# The in-flight HUD is unmistakable: the SHIELD bar is a solid bright green +# block at the bottom of the screen, and no cutscene or menu has anything green +# there. So poll that pixel, and tap A every few seconds until it appears (which +# dismisses the objective card whenever it happens to be up). +set -u +export HOME=/sylph-home/re +DEADLINE=$(( SECONDS + ${1:-240} )) +PX=450; PY=640 # inside the SHIELD bar of the flight HUD +last_tap=0 +while [ $SECONDS -lt $DEADLINE ]; do + screenshot /tmp/wf.png >/dev/null 2>&1 || { sleep 2; continue; } + read -r r g b < <(convert /tmp/wf.png -format \ + "%[fx:int(255*p{$PX,$PY}.r)] %[fx:int(255*p{$PX,$PY}.g)] %[fx:int(255*p{$PX,$PY}.b)]" info:) + if [ "${g:-0}" -gt 140 ] && [ $(( g - r )) -gt 60 ] && [ $(( g - b )) -gt 60 ]; then + echo "IN FLIGHT at ${SECONDS}s (HUD shield bar visible)" + exit 0 + fi + if [ $(( SECONDS - last_tap )) -ge 6 ]; then + vgamepad tap A 250 + last_tap=$SECONDS + fi + sleep 2 +done +echo "NO FLIGHT HUD within ${1:-240}s"; exit 1