Fit offroad tune strength and show completed-drive measurements

This commit is contained in:
firestar5683
2026-11-06 05:19:36 +00:00
parent 7f4e2ca589
commit b29a2ea4f8
12 changed files with 617 additions and 112 deletions
+6 -1
View File
@@ -520,7 +520,12 @@ def _run(
if values['active'] and not cs.steeringPressed and not values['safety_limited'] and source.calls != 1:
raise ValueError('actual selected stage hook absent or duplicated')
if effect_source is not None and values['active'] and not cs.steeringPressed and not values['curvature_limited']:
if effect_source.stage_calls != 1 or effect_source.friction_calls != 1:
from openpilot.starpilot.flm.selected_effects import bounded_profile_on_limited_frame
# Route profiles retain the real safety-limit inhibition. Zero hooks are
# required there; admitted limited-profile replay still requires both once.
expected_calls = 0 if values['safety_limited'] and not bounded_profile_on_limited_frame(controller) else 1
if effect_source.stage_calls != expected_calls or effect_source.friction_calls != expected_calls:
raise ValueError('actual selected effect hook absent or duplicated')
return float(output), log
@@ -28,6 +28,13 @@ def stable_proposal_windows(continuous, observed):
return selected, grades
def tracking_proposal_windows(continuous, observed):
# Two-second statistics use the same known-zero observations as the Stable
# reader. These children supply no missing controller/native evidence.
children = {key: [child for parent in parents for child in whole_windows(parent.points)] for key, parents in observed.items()}
return stable_proposal_windows(continuous, children)
def baseline_windows(points, timeline, metadata_for):
times = tuple(t for t, _ in timeline)
result = []
+55 -11
View File
@@ -11,9 +11,59 @@ def qualify(assessment, group, cohort, values, root, *, permitted, require_chang
roles = cohort.get('qualification_roles', cohort)
ids = tuple(roles['training']['windows']) + tuple(roles['heldout']['windows'])
original = group['qualification_original_windows'] if 'qualification_roles' in cohort else group['selected_original_windows']
if not all(3 <= len(roles[role]['windows']) <= 12 for role in ('training', 'heldout')) or len(set(ids)) != len(ids):
raise ValueError('Disjoint original training and held-out windows required')
results = _original_results(assessment, original, ids, values, root, permitted=permitted)
return {
'qualified': qualified_originals(results, roles, require_change=require_change),
'policy': 'every_preselected_window_controller_and_registered_encoder_v1',
'windows': results,
'physical_improvement_proven': False,
'current_permission_provided': False,
'requires_changed_emission': require_change,
'changed_emission_scope': 'both_preselected_roles' if require_change else 'observed_original_map',
}
def fit_training_step(assessment, group, cohort, proposal, previous, root, *, permitted):
"""Fit deterministic actuator feasibility on training only; never retry held-out."""
roles = cohort.get('qualification_roles', cohort)
train, heldout = (tuple(roles[role]['windows']) for role in ('training', 'heldout'))
if not 3 <= len(train) <= 12 or not 3 <= len(heldout) <= 12 or len(set(train + heldout)) != len(train + heldout):
raise ValueError('Original disjoint training and held-out windows required')
original = group['qualification_original_windows'] if 'qualification_roles' in cohort else group['selected_original_windows']
if set(previous) != set(proposal['values']):
raise ValueError('Exact original full-map prior required')
attempts = []
for fraction in (1.0, 0.8, 0.6, 0.4, 0.2):
values = (
dict(proposal['values']) if fraction == 1 else {name: previous[name] + fraction * (value - previous[name]) for name, value in proposal['values'].items()}
)
checked = _original_results(assessment, original, train, values, root, permitted=permitted)
feasible = len(checked) == len(train) and all(row['qualified'] for row in checked)
attempts.append({'fraction': fraction, 'feasible': feasible, 'original_training_windows': checked})
if feasible:
fitted = dict(proposal, values=values, delta=proposal['delta'] * fraction)
return fitted, {
'policy': 'training_only_native_feasibility_v1',
'training_windows': train,
'heldout_windows_not_evaluated': heldout,
'attempts': attempts,
'selected_fraction': fraction,
}
return None, {
'policy': 'training_only_native_feasibility_v1',
'training_windows': train,
'heldout_windows_not_evaluated': heldout,
'attempts': attempts,
'selected_fraction': None,
}
def _original_results(assessment, original, ids, values, root, *, permitted):
spans = {w['identity']: w for w in original if w['identity'] in ids}
if not 6 <= len(ids) <= 24 or len(set(ids)) != len(ids) or set(spans) != set(ids):
raise ValueError('Every original training and held-out window required')
if not 3 <= len(ids) <= 24 or len(set(ids)) != len(ids) or set(spans) != set(ids):
raise ValueError('Every preselected original window required')
from openpilot.starpilot.flm.learning_wire import read_control, read_applied
import hashlib
@@ -55,6 +105,8 @@ def qualify(assessment, group, cohort, values, root, *, permitted, require_chang
missing.append(int(event.logMonoTime))
if not current() or not permitted():
raise ValueError('Original input changed during checks')
# Parsed evidence owns primitives; release segment readers before replay.
del events, raw
for wid, w in sorted(spans.items()):
if w['segment'] != number:
continue
@@ -88,15 +140,7 @@ def qualify(assessment, group, cohort, values, root, *, permitted, require_chang
controls = []
outputs = []
missing = []
return {
'qualified': qualified_originals(results, roles, require_change=require_change),
'policy': 'every_preselected_window_controller_and_registered_encoder_v1',
'windows': results,
'physical_improvement_proven': False,
'current_permission_provided': False,
'requires_changed_emission': require_change,
'changed_emission_scope': 'both_preselected_roles' if require_change else 'observed_original_map',
}
return results
def qualified_originals(results, cohort, *, require_change):
@@ -60,6 +60,12 @@ class StatusReport:
details['Controller eligibility refusals'] = count('controller_fit_refusals')
details['Automatic configuration changed'] = bool(value.get('configuration_changed', False))
details['Analysis decision'] = value['reason']
details['Saved route adjustment count'] = value.get('saved_adjustments')
details['Recorded steering response'] = value.get('recorded_tracking')
checks = value.get('qualification_summary')
if checks is not None:
details['Controller and encoder check windows'] = [checks['passed'], checks['required']]
details['Automatic tune refusal reasons'] = [list(row) for row in checks['refusals']]
suggestions = value.get('suggestions', ())
details['Recorded route suggestions'] = [[s['parameter'], s['delta'], s['whole_windows'], s['qualification']] for s in suggestions[:16]]
details['Recorded suggestion count'] = value.get('suggestion_count', len(suggestions))
+99 -40
View File
@@ -26,6 +26,7 @@ class CompletedAssessment:
quality_stop_markers: tuple = ()
diagnostic_groups: tuple = ()
stable_groups: tuple = ()
recorded_tracking: dict | None = None
def report(self):
return {
@@ -38,6 +39,7 @@ class CompletedAssessment:
'quality_stop_markers': list(self.quality_stop_markers),
'diagnostic_groups': list(self.diagnostic_groups),
'stable_groups': list(self.stable_groups),
'recorded_tracking': self.recorded_tracking,
'source_role': 'Historical diagnostic alignment, not a statement of actual Controls consumption or current permission.',
}
@@ -62,6 +64,82 @@ def window_demand(window):
}
def _separate_qualification(report, continuous):
qualification = {}
for c in report['cohorts']:
native = [w for w in continuous if cohort(w) == c['cohort']]
chosen = _spread(sorted(native, key=lambda w: (w.points[0].segment, w.points[0].mono_ns)), MAX_COHORT_WINDOWS)
c['qualification_roles'] = {role: {'windows': tuple(w.identity for w in chosen[offset::2])} for role, offset in (('training', 0), ('heldout', 1))}
qualification.update((w.identity, w) for w in chosen)
report['qualification_original_windows'] = [
{
'identity': w.identity,
'segment': w.points[0].segment,
'start_ns': w.points[0].mono_ns,
'end_ns': w.points[-1].mono_ns,
'metrics': tracking_metrics(w.points),
'demand': window_demand(w),
}
for w in qualification.values()
]
def _recorded_tracking(windows):
"""Measured route summary and one input-ordered window, never a trial result."""
import math
ordered = sorted((w for rows in windows.values() for w in rows), key=lambda w: (w.points[0].segment, w.points[0].mono_ns))
if not ordered or len({w.domain for w in ordered}) != 1:
return None
samples = sum(len(w.points) for w in ordered)
squared_error = sum((p.desired - p.actual) ** 2 for w in ordered for p in w.points)
first = ordered[0]
return {
'version': 1,
'domain': first.domain,
'samples': samples,
'windows': len(ordered),
'error_rms': math.sqrt(squared_error / samples),
'route': first.points[0].route,
'segment': first.points[0].segment,
'source_start_ns': first.points[0].mono_ns,
'source_end_ns': first.points[-1].mono_ns,
'points': [[p.mono_ns, p.desired, p.actual] for p in _spread(first.points, 64)],
'sampling': 'input_ordered_original_endpoints_no_interpolation',
}
def _control_eligible(control, stamp):
return bool(
control is not None
and abs(control.control_ns - stamp) <= 30_000_000
and control.enabled
and control.active
and control.valid
and not (control.manual or control.override or control.fault or control.saturated or control.limited)
and 0 < control.state_ns <= control.control_ns <= control.state_ns + 100_000_000
and 0 < control.source_receipt_ns <= control.control_ns <= control.source_receipt_ns + 500_000_000
and (
not control.native_required
or control.native_allowed
and 0 < control.native_observed_ns <= control.control_ns <= control.native_expires_ns <= control.native_observed_ns + 200_000_000
)
)
def continuous_control_window(window, timeline, metadata_for, expected):
"""Check ALL original envelopes, including those between tracking samples."""
start, end = window.points[0].mono_ns, window.points[-1].mono_ns
rows = [(stamp, control) for stamp, control in timeline if start - 30_000_000 <= stamp <= end + 30_000_000]
return bool(
rows
and rows[0][0] <= start
and rows[-1][0] >= end
and all(b[0] - a[0] <= 50_000_000 for a, b in zip(rows, rows[1:], strict=False))
and all(_control_eligible(control, stamp) and metadata_for(control, window.domain) == expected for stamp, control in rows)
)
def analyze_latest(params, root, previous, *, permitted):
from openpilot.starpilot.flm.offroad_control_metadata import read_control_metadata as read_control
@@ -82,6 +160,7 @@ def analyze_latest(params, root, previous, *, permitted):
'original_applied_envelopes': 0,
'controller_fit_refusals': 0,
'omitted_control_metadata': 0,
'whole_control_window_refusals': 0,
}
quality_stops = []
@@ -197,21 +276,7 @@ def analyze_latest(params, root, previous, *, permitted):
candidates = tuple(i for i in (at - 1, at) if 0 <= i < len(timeline))
nearest = min(candidates, key=lambda i: abs(times[i] - point.mono_ns)) if candidates else None
control = None if nearest is None else timeline[nearest][1]
okay = bool(
control is not None
and abs(control.control_ns - point.mono_ns) <= 30_000_000
and control.enabled
and control.active
and control.valid
and not (control.manual or control.override or control.fault or control.saturated or control.limited)
and 0 < control.state_ns <= control.control_ns <= control.state_ns + 100_000_000
and 0 < control.source_receipt_ns <= control.control_ns <= control.source_receipt_ns + 500_000_000
and (
not control.native_required
or control.native_allowed
and 0 < control.native_observed_ns <= control.control_ns <= control.native_expires_ns <= control.native_observed_ns + 200_000_000
)
)
okay = _control_eligible(control, point.mono_ns)
if not okay:
counts['controller_fit_refusals'] += 1
run_context = None
@@ -231,10 +296,12 @@ def analyze_latest(params, root, previous, *, permitted):
run_context = context
for context, runs in selected.items():
for qualified in runs:
windows.setdefault(context, []).extend(whole_windows(tuple(qualified)))
# The original Stable chatter window needs genuine multi-second
# context. Keep the existing two-second path and fast gross guard.
stable_windows.setdefault(context, []).extend(whole_windows(tuple(qualified), duration_ns=6_000_000_000))
for duration, destination in ((2_000_000_000, windows), (6_000_000_000, stable_windows)):
for window in whole_windows(tuple(qualified), duration_ns=duration):
if continuous_control_window(window, timeline, metadata_for, context_metadata[context]):
destination.setdefault(context, []).append(window)
else:
counts['whole_control_window_refusals'] += 1
if sum(map(len, windows.values())) + sum(map(len, stable_windows.values())) + sum(map(len, baseline_tracking.values())) > MAX_WINDOWS:
raise ValueError('Whole-drive tracking window bound exceeded')
@@ -242,17 +309,24 @@ def analyze_latest(params, root, previous, *, permitted):
receipt = walk_completed_route(root, route, permitted=permitted, consume=consume)
if not permitted():
raise ValueError('Parked authority lost after analysis')
from openpilot.starpilot.flm.offroad_baseline_windows import stable_proposal_windows
from openpilot.starpilot.flm.offroad_baseline_windows import stable_proposal_windows, tracking_proposal_windows
proposal_windows, proposal_grades = tracking_proposal_windows(windows, baseline_tracking)
stable_windows, stable_grades = stable_proposal_windows(stable_windows, baseline_tracking)
if sum(map(len, proposal_windows.values())) + sum(map(len, stable_windows.values())) > MAX_WINDOWS:
raise ValueError('Whole-drive proposal window bound exceeded')
counts['qualified_whole_windows'] = sum(map(len, windows.values()))
counts['proposal_tracking_whole_windows'] = sum(map(len, proposal_windows.values()))
prior = previous(state['cp']) if callable(previous) else previous
amplitude_prior = {name: prior[name] for name in AMPLITUDE_NAMES}
reports = []
for key, values in sorted(windows.items()):
for key, values in sorted(proposal_windows.items()):
if not values:
continue
report = analyze(tuple(values), amplitude_prior, permitted=permitted)
if key in proposal_grades:
report['tracking_source_grade'] = proposal_grades[key]
_separate_qualification(report, windows.get(key, ()))
selected_ids = {wid for cohort_row in report['cohorts'] for role in ('training', 'heldout') for wid in cohort_row.get(role, {}).get('windows', ())}
report.update(
original_context_sha256=key,
@@ -296,25 +370,8 @@ def analyze_latest(params, root, previous, *, permitted):
report = analyze_stable_windows(tuple(values), permitted=permitted)
grade = stable_grades.get(key, 'continuous_original_control_metadata')
report['tracking_source_grade'] = grade
qualification = []
if grade == 'bookended_zero_route_tracking':
for c in report['cohorts']:
native = [w for w in windows.get(key, ()) if cohort(w) == c['cohort']]
chosen = _spread(sorted(native, key=lambda w: (w.points[0].segment, w.points[0].mono_ns)), MAX_COHORT_WINDOWS)
c['qualification_roles'] = {role: {'windows': tuple(w.identity for w in chosen[offset::2])} for role, offset in (('training', 0), ('heldout', 1))}
qualification.extend(chosen)
unique = {w.identity: w for w in qualification}
report['qualification_original_windows'] = [
{
'identity': w.identity,
'segment': w.points[0].segment,
'start_ns': w.points[0].mono_ns,
'end_ns': w.points[-1].mono_ns,
'metrics': tracking_metrics(w.points),
'demand': window_demand(w),
}
for w in unique.values()
]
_separate_qualification(report, windows.get(key, ()))
selected = {wid for c in report['cohorts'] for role in ('training', 'heldout') for wid in c.get(role, {}).get('windows', ())}
report.update(
original_context_sha256=key,
@@ -349,4 +406,6 @@ def analyze_latest(params, root, previous, *, permitted):
}
)
counts['diagnostic_whole_windows'] = sum(map(len, raw_windows.values()))
return CompletedAssessment(receipt, reports, counts, state['cp'], (), route, tuple(quality_stops), tuple(diagnostic), tuple(stable))
return CompletedAssessment(
receipt, reports, counts, state['cp'], (), route, tuple(quality_stops), tuple(diagnostic), tuple(stable), _recorded_tracking(proposal_windows)
)
+96 -55
View File
@@ -165,6 +165,23 @@ def assess_next_drive(trial, assessment, scope):
}
def qualification_summary(checks):
refusals = {}
for window in checks['windows']:
if window['qualified']:
continue
check = window.get('numerical') or {}
if check.get('qualified'):
check = window.get('registered_encoder') or {}
reason = window.get('reason') or check.get('detail') or check.get('reason') or 'original_window_not_qualified'
refusals[reason] = refusals.get(reason, 0) + 1
return {
'passed': sum(w['qualified'] for w in checks['windows']),
'required': len(checks['windows']),
'refusals': sorted(refusals.items()),
}
class OffroadWorker:
def __init__(
self, params, log_root, cache, *, permitted, assessor=analyze_latest, checker=qualify, scope_for=_current_scope, on_analysis=None, on_assessment=None
@@ -310,6 +327,8 @@ class OffroadWorker:
if metadata['route_scope'] not in ('', scope.key()):
continue
for cohort in group['cohorts']:
if 'qualification_roles' in cohort and any(len(cohort['qualification_roles'][role]['windows']) < 3 for role in ('training', 'heldout')):
continue
proposal = cohort.get('proposal')
if proposal is not None:
proposal = dict(proposal, values=dict(current, **proposal['values']))
@@ -348,64 +367,81 @@ class OffroadWorker:
if blocked:
reason = 'same_rejected_change_requires_different_evidence_or_reset'
else:
checked = self.checker(assessment, group, cohort, proposal['values'], self.root, permitted=self.permitted)
if checked['qualified']:
record = {
'kind': 'next_drive_trial',
'policy': POLICY,
'assessment': report,
'parameter': proposal['parameter'],
'delta': proposal['delta'],
'values': proposal['values'],
'previous': current,
'cohort': cohort['cohort'],
'original_metadata': group['original_metadata'],
'before': {role: cohort[role] for role in ('training', 'heldout')},
'all_original_window_checks': checked,
'confirmed_improvement': False,
'proposal_policy': proposal.get('policy', 'observed_tracking_v1'),
'stable_profile_proof': cohort.get('stable_profile_proof'),
'gross_policy': 'fixed_input_matched_2s_v1' if cohort.get('stable_profile_proof') is not None else 'original_2s_v1',
'gross_baseline': [
(w.get('gross_reference') if cohort.get('stable_profile_proof') is not None else w)
for w in group['selected_original_windows']
if w['identity'] in set(cohort['training']['windows']) | set(cohort['heldout']['windows'])
],
}
if 'qualification_roles' in cohort:
roles = cohort['qualification_roles']
ids = set(roles['training']['windows']) | set(roles['heldout']['windows'])
record.update(
gross_policy='separate_original_2s_v1',
gross_original_roles=roles,
tracking_source_grade=group['tracking_source_grade'],
gross_baseline=[w for w in group['qualification_original_windows'] if w['identity'] in ids],
)
from openpilot.starpilot.flm.offroad_quality import TrialGuard
feasible = True
if self.checker is qualify:
from openpilot.starpilot.flm.offroad_checks import fit_training_step
if TrialGuard(scope.key(), proposal['values'], record).stopped is not None:
raise ValueError('Original onroad gross-guard baseline unavailable')
digest = self.cache.put(record)
new = {
'scope': scope.document(),
'values': proposal['values'],
'previous': current,
'route_sha256s': [assessment.receipt.source_sha256],
'source_start_ns': assessment.receipt.original_start_ns,
'source_end_ns': assessment.receipt.original_end_ns,
'completed_wall_ns': assessment.receipt.completed_wall_ns,
'analysis_sha256': digest,
'sequence': 1 if row is None else row['sequence'] + 1,
'state': 'ready_for_next_drive',
}
saved = RouteStorage(self.params).save(new, expected=expected, authorized=authorized)
if not saved:
raise ValueError('Next-drive tune compare-and-swap refused')
status = 'ready_for_next_drive'
reason = 'bounded_trial_configured_for_next_drive'
fitted, fit_proof = fit_training_step(assessment, group, cohort, proposal, current, self.root, permitted=self.permitted)
report['native_training_feasibility'] = fit_proof
feasible = fitted is not None
if fitted is not None:
proposal = fitted
offered = {'values': proposal['values'], 'parameter': proposal['parameter'], 'delta': proposal['delta']}
if row is not None and row['state'] == 'rolled_back':
trial = trial_document(self.cache, row['analysis_sha256'])
feasible = not (trial['parameter'] == proposal['parameter'] and trial['delta'] == proposal['delta'])
if not feasible:
reason = 'no_new_native_feasible_training_step'
else:
checked = self.checker(assessment, group, cohort, proposal['values'], self.root, permitted=self.permitted)
report['all_original_window_checks'] = checked
reason = 'original_controller_or_registered_encoder_check_refused'
if checked['qualified']:
record = {
'kind': 'next_drive_trial',
'policy': POLICY,
'assessment': report,
'parameter': proposal['parameter'],
'delta': proposal['delta'],
'values': proposal['values'],
'previous': current,
'cohort': cohort['cohort'],
'original_metadata': group['original_metadata'],
'before': {role: cohort[role] for role in ('training', 'heldout')},
'all_original_window_checks': checked,
'confirmed_improvement': False,
'proposal_policy': proposal.get('policy', 'observed_tracking_v1'),
'stable_profile_proof': cohort.get('stable_profile_proof'),
'native_training_feasibility': report.get('native_training_feasibility'),
'gross_policy': 'fixed_input_matched_2s_v1' if cohort.get('stable_profile_proof') is not None else 'original_2s_v1',
'gross_baseline': [
(w.get('gross_reference') if cohort.get('stable_profile_proof') is not None else w)
for w in group['selected_original_windows']
if w['identity'] in set(cohort['training']['windows']) | set(cohort['heldout']['windows'])
],
}
if 'qualification_roles' in cohort:
roles = cohort['qualification_roles']
ids = set(roles['training']['windows']) | set(roles['heldout']['windows'])
record.update(
gross_policy='separate_original_2s_v1',
gross_original_roles=roles,
tracking_source_grade=group['tracking_source_grade'],
gross_baseline=[w for w in group['qualification_original_windows'] if w['identity'] in ids],
)
from openpilot.starpilot.flm.offroad_quality import TrialGuard
if TrialGuard(scope.key(), proposal['values'], record).stopped is not None:
raise ValueError('Original onroad gross-guard baseline unavailable')
digest = self.cache.put(record)
new = {
'scope': scope.document(),
'values': proposal['values'],
'previous': current,
'route_sha256s': [assessment.receipt.source_sha256],
'source_start_ns': assessment.receipt.original_start_ns,
'source_end_ns': assessment.receipt.original_end_ns,
'completed_wall_ns': assessment.receipt.completed_wall_ns,
'analysis_sha256': digest,
'sequence': 1 if row is None else row['sequence'] + 1,
'state': 'ready_for_next_drive',
}
saved = RouteStorage(self.params).save(new, expected=expected, authorized=authorized)
if not saved:
raise ValueError('Next-drive tune compare-and-swap refused')
status = 'ready_for_next_drive'
reason = 'bounded_trial_configured_for_next_drive'
else:
reason = 'original_controller_or_registered_encoder_check_refused'
diagnostics = [(g, c, c['proposal']) for g in assessment.diagnostic_groups for c in g['cohorts'] if c.get('proposal') is not None]
diagnostics.sort(key=lambda v: (-v[1]['whole_windows'], v[1]['cohort'], v[0]['original_tracking_context_sha256']))
if offered is None:
@@ -437,6 +473,7 @@ class OffroadWorker:
'assessment_sha256': self.cache.put({'kind': 'completed_assessment', 'assessment': report}),
'source_sha256': assessment.receipt.source_sha256,
'vehicle': str(assessment.cp.carFingerprint),
'recorded_tracking': report.get('recorded_tracking'),
'suggestion_count': len(diagnostics),
'suggestions': [
{'parameter': v['parameter'], 'delta': v['delta'], 'whole_windows': c['whole_windows'], 'qualification': 'controller_native_checks_not_passed'}
@@ -445,10 +482,14 @@ class OffroadWorker:
}
if offered is not None:
result['suggestion'] = offered
checks = report.get('all_original_window_checks')
if checks is not None:
result['qualification_summary'] = qualification_summary(checks)
current_raw, readable = read_saved(self.params, KEY, MAX_BYTES)
if not readable:
raise ValueError('Saved route configuration unreadable after assessment')
configured = decode(current_raw).get(NAMESPACE, {}).get(scope.key())
result['saved_adjustments'] = 0 if configured is None else sum(value != 0 for value in configured['values'].values())
if configured is not None:
result['configured_values'] = dict(configured['values'])
result['configuration_state'] = configured['state']
@@ -135,6 +135,67 @@ class TestOffroadCache(unittest.TestCase):
class TestOffroadSurface(unittest.TestCase):
def test_real_controller_route_replay_preserves_limiter_and_requires_exact_hooks(self):
from types import SimpleNamespace
from opendbc.car import structs
from opendbc.car.car_helpers import interfaces
from opendbc.car.hyundai.values import CAR
from openpilot.starpilot.flm.controller_replay import SelectedControllerFactory, capture_snapshot, restore_snapshot, frame_input, _run
from openpilot.starpilot.flm.offroad_surface import replay_source
from openpilot.starpilot.flm.torque_adapter import Objective
cp = interfaces[CAR.HYUNDAI_IONIQ_6].get_non_essential_params(CAR.HYUNDAI_IONIQ_6).as_reader()
controller, vm = SelectedControllerFactory(cp, 'starpilot').create()
cs = structs.CarState.new_message(vEgo=25.0, steeringAngleDeg=0.2, gearShifter='drive')
snapshot = capture_snapshot(controller, vm)
objective = Objective('feedforward', 'high', 'steady')
neutral = (0.0,) * len(objective.parameters)
original_update = controller.update
for limited in (False, True):
with self.subTest(limited=limited):
inputs = frame_input(
cs,
vm,
SimpleNamespace(angleOffsetDeg=0.0, roll=0.0),
active=True,
safety_limited=limited,
desired_curvature=0.0001,
curvature_limited=False,
lat_delay=0.1,
) + (('control_frame', 1.0),)
restore_snapshot(controller, vm, snapshot)
baseline, _ = _run(controller, vm, inputs, objective, neutral)
restore_snapshot(controller, vm, snapshot)
source = replay_source(values(0.005), 'b' * 64)
candidate, _ = _run(controller, vm, inputs, objective, neutral, effect_trial=source)
self.assertEqual((source.stage_calls, source.friction_calls), (0, 0) if limited else (1, 1))
if limited:
self.assertEqual(candidate, baseline, 'The actual limiter inhibits all route effects')
else:
def omit_effect(*args):
actual = controller.automatic_flm_effects
controller.automatic_flm_effects = None
try:
return original_update(*args)
finally:
controller.automatic_flm_effects = actual
restore_snapshot(controller, vm, snapshot)
with patch.object(controller, 'update', side_effect=omit_effect), self.assertRaisesRegex(ValueError, 'selected effect hook'):
_run(controller, vm, inputs, objective, neutral, effect_trial=replay_source(values(), 'b' * 64))
def duplicate_effect(*args):
result = original_update(*args)
effect = controller.automatic_flm_effects.source
effect.stage_calls += 1
effect.friction_calls += 1
return result
restore_snapshot(controller, vm, snapshot)
with patch.object(controller, 'update', side_effect=duplicate_effect), self.assertRaisesRegex(ValueError, 'selected effect hook'):
_run(controller, vm, inputs, objective, neutral, effect_trial=replay_source(values(), 'b' * 64))
def test_zero_curves_preserve_canonical_and_interpolation_is_relative(self):
for speed, expected in ((0.0, 0.0), (2.5, 0.005), (5.0, 0.01), (10.0, 0.02), (20.0, 0.035), (30.0, 0.04)):
with self.subTest(speed=speed):
@@ -218,12 +279,40 @@ class TestOffroadStore(unittest.TestCase):
class TestOffroadReport(unittest.TestCase):
def test_completed_drive_reports_every_candidate_without_claiming_application(self):
from openpilot.starpilot.flm.training_report import read_report
from openpilot.starpilot.flm.offroad_worker import qualification_summary
checks = {
'windows': [{'qualified': True}] * 22
+ [
{'qualified': False, 'numerical': {'qualified': False, 'reason': 'original_scored_frame_not_eligible'}},
{
'qualified': False,
'numerical': {'qualified': True},
'registered_encoder': {'qualified': False, 'detail': 'candidate changed an actual limiter/support'},
},
]
}
summary = qualification_summary(checks)
self.assertEqual((summary['passed'], summary['required']), (22, 24))
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'report.json'
report = StatusReport(path=path, clock_ns=lambda: 10_000_000_000)
names = ['highway_center_taper_max', 'turn_in_boost_left', 'unwind_taper_left', 'turn_in_boost_right', 'unwind_taper_right']
suggestions = [{'parameter': name, 'delta': 0.005, 'whole_windows': 9, 'qualification': 'controller_native_checks_not_passed'} for name in names]
recorded = {
'version': 1,
'domain': 'lateral_acceleration',
'samples': 2000,
'windows': 10,
'error_rms': 0.12,
'route': 'declared-completed-drive',
'segment': 0,
'source_start_ns': 1_000_000_000,
'source_end_ns': 2_900_000_000,
'points': [[1_000_000_000 + i * 100_000_000, 0.2, 0.08] for i in range(20)],
'sampling': 'input_ordered_original_endpoints_no_interpolation',
}
report.set(
{
'status': 'collecting',
@@ -233,6 +322,9 @@ class TestOffroadReport(unittest.TestCase):
'suggestions': suggestions,
'suggestion_count': len(suggestions),
'configuration_changed': False,
'recorded_tracking': recorded,
'saved_adjustments': 0,
'qualification_summary': summary,
}
)
report.publish()
@@ -244,6 +336,12 @@ class TestOffroadReport(unittest.TestCase):
self.assertFalse(current['details']['Automatic configuration changed'])
self.assertFalse(current['application']['applied'])
self.assertFalse(current['details']['Physical road improvement proven'])
self.assertEqual(current['details']['Recorded steering response'], recorded)
self.assertEqual(current['details']['Saved route adjustment count'], 0)
self.assertEqual(current['details']['Controller and encoder check windows'], [22, 24])
self.assertEqual(
current['details']['Automatic tune refusal reasons'], [['candidate changed an actual limiter/support', 1], ['original_scored_frame_not_eligible', 1]]
)
def test_typed_current_application_and_completed_analysis_remain_distinct(self):
with tempfile.TemporaryDirectory() as directory:
@@ -300,6 +398,50 @@ class TestOffroadReport(unittest.TestCase):
class TestOffroadRouteCoverage(unittest.TestCase):
def test_native_windows_veto_ineligible_envelopes_between_tracking_samples(self):
from types import SimpleNamespace
from openpilot.starpilot.flm.offroad_processor import continuous_control_window
window = TestOffroadTrackingPolicy.windows('declared-envelope-coverage', 1.0)[0]
start, end = window.points[0].mono_ns, window.points[-1].mono_ns
metadata = {'scope': 'declared-source'}
timeline = []
for stamp in range(start - 30_000_000, end + 30_000_001, 5_000_000):
control = SimpleNamespace(
control_ns=stamp,
state_ns=stamp,
source_receipt_ns=stamp,
enabled=True,
active=True,
valid=True,
manual=False,
override=False,
fault=False,
saturated=False,
limited=False,
native_required=True,
native_allowed=True,
native_observed_ns=stamp - 1_000_000,
native_expires_ns=stamp + 100_000_000,
metadata=metadata,
)
timeline.append((stamp, control))
def reader(control, domain):
return control.metadata
self.assertTrue(continuous_control_window(window, timeline, reader, metadata))
index = next(i for i, (stamp, _) in enumerate(timeline) if stamp == start + 5_000_000)
control = timeline[index][1]
for changes in ({'limited': True}, {'native_allowed': False}, {'metadata': {'scope': 'changed'}}):
with self.subTest(changes=changes):
changed = list(timeline)
changed[index] = (timeline[index][0], SimpleNamespace(**(vars(control) | changes)))
self.assertFalse(continuous_control_window(window, changed, reader, metadata))
absent: list[tuple[int, SimpleNamespace | None]] = list(timeline)
absent[index] = (timeline[index][0], None)
self.assertFalse(continuous_control_window(window, absent, reader, metadata))
def test_actual_aggregation_keeps_classifier_and_separate_qualification_windows(self):
import math
from types import SimpleNamespace
@@ -365,8 +507,42 @@ class TestOffroadRouteCoverage(unittest.TestCase):
self.assertEqual(len(roles['training']['windows']), 3)
self.assertEqual(len(roles['heldout']['windows']), 3)
self.assertFalse(set(roles['training']['windows']) & set(roles['heldout']['windows']))
primary = assessment.groups[0]
self.assertEqual(primary['tracking_source_grade'], 'bookended_zero_route_tracking')
self.assertEqual(primary['cohorts'][0]['whole_windows'], 18)
self.assertEqual({w['identity'] for w in primary['qualification_original_windows']}, {w.identity for w in continuous})
self.assertEqual(assessment.counts['qualified_whole_windows'], 6)
self.assertEqual(assessment.counts['proposal_tracking_whole_windows'], 18)
recorded = assessment.recorded_tracking
self.assertIsNotNone(recorded)
assert recorded is not None
self.assertEqual((recorded['samples'], recorded['windows']), (3600, 18))
self.assertEqual(len(recorded['points']), 64)
self.assertEqual(recorded['points'][0], [observed[0].points[0].mono_ns, observed[0].points[0].desired, observed[0].points[0].actual])
self.assertEqual(recorded['source_end_ns'], observed[0].points[199].mono_ns)
self.assertEqual(recorded['sampling'], 'input_ordered_original_endpoints_no_interpolation')
self.assertFalse(assessment.report()['automatically_saved'])
def test_primary_statistics_keep_original_children_and_never_supply_qualification(self):
from openpilot.starpilot.flm.offroad_baseline_windows import tracking_proposal_windows
from openpilot.starpilot.flm.offroad_evidence import RouteWindow, whole_windows, window_identity
original = TestOffroadTrackingPolicy.windows('declared-source', 0.9)[0]
short = replace(original, points=original.points[:200], identity='')
short = replace(short, identity=window_identity(short.points))
first = short.points[0].mono_ns
points = tuple(replace(p, mono_ns=p.mono_ns + block * 2_000_000_000) for block in range(3) for p in short.points)
parent = RouteWindow(window_identity(points), points)
self.assertEqual(parent.points[0].mono_ns, first)
expected = whole_windows(parent.points)
continuous = {'nonzero': (short,)}
selected, grades = tracking_proposal_windows(continuous, {'known-zero': (parent,)})
self.assertEqual(selected['known-zero'], list(expected))
self.assertEqual(tuple(p for w in selected['known-zero'] for p in w.points), points)
self.assertEqual(selected['nonzero'], (short,))
self.assertEqual(continuous, {'nonzero': (short,)})
self.assertEqual(grades, {'known-zero': 'bookended_zero_route_tracking'})
def test_continuous_center_does_not_hide_other_baseline_maneuvers(self):
from openpilot.starpilot.flm.offroad_baseline_windows import stable_proposal_windows
@@ -431,6 +607,118 @@ class TestOffroadRouteCoverage(unittest.TestCase):
class TestOffroadTrackingPolicy(unittest.TestCase):
def test_saved_unit_configuration_reports_successful_original_checks(self):
from types import SimpleNamespace
from openpilot.starpilot.flm.offroad_worker import OffroadWorker
from openpilot.starpilot.flm.offroad_processor import CompletedAssessment
from openpilot.starpilot.flm.offroad_route import RouteReceipt
declared = trial()
roles = declared['before']
baseline = declared['gross_baseline']
proposed = {'values': values(0.005), 'parameter': 'center_taper_max', 'delta': 0.005}
cohort = dict(roles, cohort='declared-positive-report', whole_windows=6, proposal=proposed, qualification_roles=roles)
metadata = {'route_values': tuple(values().items()), 'route_revision': REVISION, 'controller_mode': 'starpilot', 'route_scope': scope().key()}
group = {
'original_metadata': metadata,
'original_context_sha256': 'f' * 64,
'cohorts': [cohort],
'qualification_original_windows': baseline,
'selected_original_windows': baseline,
'tracking_source_grade': 'declared-unit-only',
}
cp = SimpleNamespace(carFingerprint='declared-torque-unit')
receipt = RouteReceipt('declared-positive-report', 'd' * 64, (), 'a' * 64, 1, 2, 3, ())
inventory = {'routeId': receipt.route_id}
assessment = CompletedAssessment(receipt, (group,), {}, cp, (), inventory)
def assessed(params, root, previous, *, permitted):
previous(cp)
return assessment
def declared_checks(actual, original, ids, trial_values, root, *, permitted):
# Declared protocol responses only, not a native qualification claim.
return [
{
'window': wid,
'qualified': True,
'numerical': {'qualified': True, 'changed_frames': 1},
'registered_encoder': {'qualified': True, 'candidate_changed_emissions': 1},
}
for wid in ids
]
with tempfile.TemporaryDirectory() as directory:
params = Params(directory)
with (
patch('openpilot.starpilot.flm.offroad_worker.latest_route', return_value=inventory),
patch('openpilot.starpilot.flm.offroad_checks._original_results', new=declared_checks),
):
worker = OffroadWorker(params, directory, Path(directory) / 'cache', permitted=lambda: True, assessor=assessed, scope_for=lambda cp, params: scope())
result = worker.run_once()
self.assertTrue(result['configuration_changed'])
self.assertEqual(result['qualification_summary'], {'passed': 6, 'required': 6, 'refusals': []})
self.assertEqual(result['saved_adjustments'], 1)
self.assertFalse(result['actual_application_proven'])
def test_training_feasibility_is_bounded_and_heldout_refusal_never_retries(self):
from types import SimpleNamespace
from openpilot.starpilot.flm.offroad_checks import fit_training_step
from openpilot.starpilot.flm.offroad_processor import CompletedAssessment
from openpilot.starpilot.flm.offroad_route import RouteReceipt
from openpilot.starpilot.flm.offroad_worker import OffroadWorker
train, heldout = tuple(str(i) * 64 for i in range(1, 4)), tuple(str(i) * 64 for i in range(4, 7))
roles = {'training': {'windows': train}, 'heldout': {'windows': heldout}}
proposed = values(0.005)
proposal = {'values': proposed, 'parameter': 'center_taper_max', 'delta': 0.005}
cohort = {'cohort': 'declared-training', 'whole_windows': 6, 'proposal': proposal, 'qualification_roles': roles}
metadata = {'route_values': tuple(values().items()), 'route_revision': REVISION, 'controller_mode': 'starpilot', 'route_scope': scope().key()}
group = {'original_metadata': metadata, 'original_context_sha256': 'f' * 64, 'cohorts': [cohort], 'qualification_original_windows': []}
cp = SimpleNamespace(carFingerprint='declared-torque-unit')
receipt = RouteReceipt('declared-training-drive', 'd' * 64, (), 'a' * 64, 1, 2, 3, ())
inventory = {'routeId': 'declared-training-drive'}
assessment = CompletedAssessment(receipt, (group,), {}, cp, (), inventory)
calls = []
def original_checks(actual, original, ids, trial_values, root, *, permitted):
self.assertTrue(permitted())
self.assertIs(actual, assessment)
amount = trial_values['center_taper_max']
calls.append((tuple(ids), amount))
self.assertTrue(set(ids) in (set(train), set(train + heldout)))
return [{'window': wid, 'qualified': amount <= 0.004 and wid not in heldout} for wid in ids]
with tempfile.TemporaryDirectory() as directory:
params = Params(directory)
def assessed(params, root, previous, *, permitted):
previous(cp)
return assessment
with (
patch('openpilot.starpilot.flm.offroad_worker.latest_route', return_value=inventory),
patch('openpilot.starpilot.flm.offroad_checks._original_results', new=original_checks),
):
worker = OffroadWorker(params, directory, Path(directory) / 'cache', permitted=lambda: True, assessor=assessed, scope_for=lambda cp, params: scope())
result = worker.run_once()
self.assertEqual(calls, [(train, 0.005), (train, 0.004), (train + heldout, 0.004)])
self.assertFalse(result['configuration_changed'])
self.assertEqual(result['reason'], 'original_controller_or_registered_encoder_check_refused')
self.assertEqual(read_saved(params, KEY, MAX_BYTES), (None, True))
calls.clear()
def infeasible(actual, original, ids, trial_values, root, *, permitted):
calls.append(tuple(ids))
return [{'window': wid, 'qualified': False} for wid in ids]
with patch('openpilot.starpilot.flm.offroad_checks._original_results', new=infeasible):
fitted, proof = fit_training_step(assessment, group, cohort, proposal, values(), '.', permitted=lambda: True)
self.assertIsNone(fitted)
self.assertEqual(calls, [train] * 5)
self.assertIsNone(proof['selected_fraction'])
@staticmethod
def windows(prefix, gain, amplitude=1.0):
import math
@@ -241,6 +241,31 @@ for(const label of ['Highway center taper','Left turn-in boost','Left unwind tap
assert(!offroadText.includes('Waiting for driving samples'))
assert(!offroadText.includes('Real measurements go here'))
assert.equal(ctx.currentApplied,false)
const measuredWindow={version:1,domain:'lateral_acceleration',samples:2000,windows:10,error_rms:.12,route:'declared-drive',segment:0,
source_start_ns:1e9,source_end_ns:2.9e9,points:Array.from({length:20},(_,i)=>[1e9+i*1e8,.2,.08]),
sampling:'input_ordered_original_endpoints_no_interpolation'}
ctx.report.current.details['Recorded steering response']=measuredWindow
ctx.report.current.details['Saved route adjustment count']=0
ctx.report.current.details['Controller and encoder check windows']=[22,24]
ctx.report.current.details['Automatic tune refusal reasons']=[['original_scored_frame_not_eligible',1],['candidate changed an actual limiter/support',1]]
assert.equal(ctx.offroad.observed.errorRms,.12)
assert.equal(ctx.recordedTrackingChart.valid,true)
assert(ctx.recordedTrackingChart.paths.every(path=>path.length===1),'Both original series render')
const measuredText=text(render.call(ctx,ctx,[]))
assert(measuredText.includes('Recorded baseline') && measuredText.includes(measurementText(.12)))
assert(measuredText.includes('Saved adjustments') && measuredText.includes('Awaiting next-drive measurement'))
assert(measuredText.includes('No interpolation or delay alignment'))
assert(measuredText.includes('22 / 24 original controller and encoder windows passed'))
assert(measuredText.includes('candidate triggered an encoder limiter'))
assert.equal(ctx.currentApplied,false)
for(const changed of [{points:measuredWindow.points.toReversed()},{error_rms:NaN},{domain:'torque'},
{source_end_ns:measuredWindow.source_end_ns+1}]){
ctx.report.current.details['Recorded steering response']={...measuredWindow,...changed}
assert.equal(ctx.offroad.observed,null,'Malformed recorded data cannot populate the graph')
}
ctx.report.current.details['Recorded steering response']={...measuredWindow,domain:'steering_angle'}
assert.equal(ctx.offroad.observed.units,'degrees','PID observations retain their angle units')
ctx.report.current.details['Recorded steering response']=measuredWindow
ctx.report.current.details['Saved route configuration state']='ready_for_next_drive'
assert.equal(ctx.state.title,'Tune saved for the next drive')
assert.equal(ctx.currentApplied,false,'Next-drive saving never fabricates live application')
@@ -160,7 +160,11 @@ const REASONS = {
analyzing_last_completed_drive: "Analyzing the last completed drive while parked.",
checking_recorded_steering_recommendations: "Checking the recorded-drive recommendations against the original controller and steering outputs.",
recorded_tracking_suggestion_not_yet_qualified_for_automatic_application: "Recommendations were found, but none has passed the controller and steering-output checks required for automatic saving.",
original_controller_or_registered_encoder_check_refused: "A tune was suggested, but at least one original controller or encoder check refused it. Your saved tune was kept.",
original_scored_frame_not_eligible: "An original frame was inactive, limited or otherwise ineligible.",
"candidate changed an actual limiter/support": "The candidate triggered an encoder limiter that the recorded request did not.",
original_controller_or_registered_encoder_check_refused: "The recommendation failed an original controller or steering-output check. The saved tune was kept.",
no_new_native_feasible_training_step: "No bounded training step passed the original steering-output checks. The saved tune was kept.",
no_consistent_adjustment_in_completed_drive: "The independent parts of this drive did not support the same adjustment. The saved tune was kept.",
more_independent_eligible_tracking_required: "The drive was analyzed. More complete independent steering windows are needed before saving a tune.",
bounded_trial_configured_for_next_drive: "A bounded, reversible tune was saved automatically for the next drive. Its road improvement is not yet confirmed.",
@@ -207,13 +211,37 @@ export function offroadView(report) {
if (!Array.isArray(rows) || rows.length > 16 || rows.some(row => !Array.isArray(row) || row.length !== 4 ||
!text(row[0], 500) || typeof row[1] !== 'number' || !Number.isFinite(row[1]) || Math.abs(row[1]) > .25 ||
!stamp(row[2]) || row[3] !== 'controller_native_checks_not_passed')) return null
const observed = recordedSteeringView(details['Recorded steering response'])
const savedCount = details['Saved route adjustment count']
const checked = details['Controller and encoder check windows'], refused = details['Automatic tune refusal reasons']
const checks = Array.isArray(checked) && checked.length === 2 && checked.every(stamp) && checked[0] <= checked[1] && checked[1] <= 24 &&
Array.isArray(refused) && refused.length <= 24 && refused.every(r => Array.isArray(r) && r.length === 2 && text(r[0]) && stamp(r[1])) ?
{ passed: checked[0], required: checked[1], refusals: refused.map(([reason, count]) => ({ reason, count })) } : null
return { route: details['Completed drive analyzed'], samples: details['Recorded steering observations'],
reason: details['Analysis decision'], saved: details['Automatic configuration changed'] === true,
configurationState: details['Saved route configuration state'] ?? null,
observed, checks, savedCount: stamp(savedCount) && savedCount <= 35 ? savedCount : null,
suggestions: rows.map(([parameter, delta, windows, qualification]) => ({ parameter, delta, windows, qualification })),
moreSuggestions: details['More recorded suggestions omitted'] === true }
}
export function recordedSteeringView(value) {
if (!record(value) || value.version !== 1 || !['lateral_acceleration', 'steering_angle'].includes(value.domain) ||
!stamp(value.samples) || value.samples < 20 || !stamp(value.windows) || value.windows < 1 ||
!Number.isFinite(value.error_rms) || value.error_rms < 0 || !text(value.route, 180) ||
!stamp(value.segment) || !stamp(value.source_start_ns) || !stamp(value.source_end_ns) ||
value.source_start_ns <= 0 || value.source_end_ns <= value.source_start_ns ||
value.sampling !== 'input_ordered_original_endpoints_no_interpolation' ||
!Array.isArray(value.points) || value.points.length < 20 || value.points.length > 64 ||
value.points.some((r, i) => !Array.isArray(r) || r.length !== 3 || !stamp(r[0]) ||
r[0] < value.source_start_ns || r[0] > value.source_end_ns || i > 0 && r[0] <= value.points[i - 1][0] ||
!r.slice(1).every(v => Number.isFinite(v) && Math.abs(v) <= 10000)) ||
value.points[0][0] !== value.source_start_ns || value.points.at(-1)[0] !== value.source_end_ns) return null
return { errorRms: value.error_rms, samples: value.samples, windows: value.windows, route: value.route,
segment: value.segment, units: value.domain === 'lateral_acceleration' ? 'm/s²' : 'degrees',
points: value.points.map(([ns, desired, actual]) => ({ at: (ns - value.source_start_ns) / 1e6, desired, actual })) }
}
export function tunerStatus(report, live = false, choiceLive = false, error = "", reportFresh = live) {
const preference = choiceLive && validLearningPreference(report?.preference) ? report.preference : null
const current = report?.current
+7 -4
View File
@@ -74,12 +74,13 @@ export const FlmPage = {
measurements() { return this.tracking ? this.trackingProgress?.capturedPoses ?? null : this.current?.progress?.samples ?? null },
trackingPlot() { return trackingPlotView(this.report, this.now - this.receivedAt) },
offroad() { return offroadView(this.report) },
recordedTrackingChart() { return chartGeometry(this.offroad?.observed?.points || [], ['desired', 'actual']) },
pairedTrackingChart() { return chartGeometry(this.trackingPlot?.points || [], ["desiredForceAccel", "measuredForceAccel"]) },
pairedErrorChart() { return chartGeometry(this.trackingPlot?.points || [], ["errorForceAccel"]) },
details() { return Object.entries(this.current?.details || {}) },
reportAge() { return this.current ? this.current.ageSeconds + Math.max(0, this.now - this.receivedAt) / 1000 : null },
appliedCount() { return this.currentApplied && this.rows.every(row => row.applied !== null) ? this.rows.filter(row => row.applied !== 0).length : null },
acceptedCount() { return this.trackingValues ? this.rows.filter(row => row.accepted !== null && row.accepted !== 0).length : null },
acceptedCount() { return this.offroad ? this.offroad.savedCount : this.trackingValues ? this.rows.filter(row => row.accepted !== null && row.accepted !== 0).length : null },
groupRows() { return this.rows.filter(row => this.selectedGroup.names.includes(row.name)) },
selectedGroup() { return this.groups.find(group => group.id === this.group) || this.groups[0] },
historyRows() { return this.showAllHistory ? this.report?.history || [] : (this.report?.history || []).slice(0, 6) },
@@ -180,17 +181,19 @@ export const FlmPage = {
</nav>
<section v-if="view === 'overview'" id="tuner-panel-overview" role="tabpanel" aria-labelledby="tuner-tab-overview" class="gx-tuner__panel">
<div class="gx-tuner__metrics"><div class="gx-card gx-tuner__metric"><span>Applied parameters</span><strong>{{ amount(appliedCount) }}<small v-if="appliedCount !== null"> / 30</small></strong><p>{{ appliedCount === null ? 'Requires current controller evidence' : 'Nonzero corrections acknowledged now' }}</p></div>
<div class="gx-card gx-tuner__metric"><span>Accepted corrections</span><strong>{{ amount(acceptedCount) }}</strong><p>{{ acceptedCount === null ? 'No accepted map reported' : 'Reported accepted map · saving is separate' }}</p></div>
<div class="gx-card gx-tuner__metric"><span>{{ offroad ? 'Saved adjustments' : 'Accepted corrections' }}</span><strong>{{ amount(acceptedCount) }}</strong><p>{{ offroad ? 'Saved for the next drive · current application is separate' : acceptedCount === null ? 'No accepted map reported' : 'Reported accepted map · saving is separate' }}</p></div>
<div class="gx-card gx-tuner__metric"><span>Steering measurements</span><strong>{{ amount(measurements) }}</strong><p>{{ trackingProgress ? 'Retained observations, before quality checks' : tracking ? 'No capture count reported' : 'Samples retained in the learner report' }}</p></div></div>
<div class="gx-tuner__overview-grid"><section class="gx-card gx-tuner__section gx-tuner__response"><div class="gx-tuner__section-heading"><div><p class="gx-tuner__eyebrow">STEERING RESPONSE</p><h3>Requested meets measured</h3></div><span class="gx-tuner__pill">{{ trackingPlot ? 'Completed window' : offroad ? 'Recorded drive' : 'Waiting for samples' }}</span></div>
<template v-if="trackingPlot"><PlotGraph :chart="pairedTrackingChart" title="Requested and measured steering acceleration, paired using the learner's measured delay" /><div class="gx-tuner__legend"><span class="gx-tuner__requested">Requested</span><span class="gx-tuner__measured">Measured</span><small>m/s² · delay aligned</small></div><p class="gx-tuner__caption">{{ trackingPlot.totalEndpoints }} measured endpoints · {{ trackingPlot.age.toFixed(1) }}s since the last pose</p></template>
<div v-else-if="offroad" class="gx-tuner__empty"><i class="bi bi-activity" aria-hidden="true"></i><h4>{{ amount(offroad.samples) }} recorded steering observations</h4><p>{{ learningReason(offroad.reason) }}</p><p class="gx-tuner__caption">The recorded-drive analysis is separate from the live, delay-paired response graph.</p></div>
<template v-else-if="offroad?.observed"><PlotGraph :chart="recordedTrackingChart" title="Recorded requested and measured steering" /><div class="gx-tuner__legend"><span class="gx-tuner__requested">Requested</span><span class="gx-tuner__measured">Measured</span><small>{{ offroad.observed.units }} · original source times</small></div><p class="gx-tuner__caption">Drive {{ offroad.observed.route }} · segment {{ offroad.observed.segment }} · one input-ordered recorded window. No interpolation or delay alignment. Baseline statistics use {{ amount(offroad.observed.windows) }} windows.</p></template>
<div v-else-if="offroad" class="gx-tuner__empty"><i class="bi bi-activity" aria-hidden="true"></i><h4>{{ amount(offroad.samples) }} recorded steering observations</h4><p>{{ learningReason(offroad.reason) }}</p><p class="gx-tuner__caption">No recorded response series was included in this report.</p></div>
<div v-else class="gx-tuner__empty"><i class="bi bi-activity" aria-hidden="true"></i><h4>Real measurements go here</h4><p>A completed steering window will show the requested response beside what the car actually did.</p></div>
</section>
<section class="gx-card gx-tuner__section"><div class="gx-tuner__section-heading"><div><p class="gx-tuner__eyebrow">{{ offroad ? 'RECORDED-DRIVE ANALYSIS' : live ? 'THE CURRENT EXPERIMENT' : 'LAST REPORTED EXPERIMENT' }}</p><h3>{{ offroad ? 'Next-drive tuning' : tracking?.acknowledged && tracking.status === 'trial_active' ? 'Trial in progress' : 'Trial results' }}</h3></div></div>
<p v-if="tracking">{{ learningReason(tracking.reason) }}</p><p v-else-if="offroad">{{ learningReason(offroad.reason) }}</p><p v-else>Waiting for driving samples. Auto Learn compares steering error before and during a trial.</p>
<template v-if="offroad?.checks"><p>{{ offroad.checks.passed }} / {{ offroad.checks.required }} original controller and encoder windows passed.</p><p v-for="(refusal, index) in offroad.checks.refusals" :key="index">{{ refusal.count }} window(s): {{ learningReason(refusal.reason) }}</p></template>
<template v-if="offroad"><ul v-if="offroad.suggestions.length"><li v-for="(suggestion, index) in offroad.suggestions" :key="index"><strong>{{ parameterLabel(suggestion.parameter) }} {{ coordinateText(suggestion.delta) }}</strong> · {{ amount(suggestion.windows) }} recorded windows · not approved for automatic saving</li></ul><p v-if="offroad.moreSuggestions" class="gx-tuner__caption">Additional candidates are retained in the device's full assessment.</p><p class="gx-tuner__caption">{{ offroad.configurationState === 'ready_for_next_drive' ? 'A separate qualified tune is saved for the next drive. Current application still requires controller evidence.' : 'Your saved tune is unchanged unless the controller and steering-output checks pass.' }}</p></template>
<div class="gx-tuner__comparison"><div><span>Before</span><strong>{{ measurementText(tracking?.baselineErrorRms ?? null) }}</strong></div><i class="bi bi-arrow-right" aria-hidden="true"></i><div><span>Trial</span><strong>{{ measurementText(tracking?.futureErrorRms ?? null) }}</strong></div></div><p class="gx-tuner__caption">Tracking error RMS · m/s² · lower is better</p>
<div class="gx-tuner__comparison"><div><span>{{ offroad ? 'Recorded baseline' : 'Before' }}</span><strong>{{ measurementText(offroad?.observed?.errorRms ?? tracking?.baselineErrorRms ?? null) }}</strong></div><i class="bi bi-arrow-right" aria-hidden="true"></i><div><span>Trial</span><strong>{{ offroad ? 'Awaiting next-drive measurement' : measurementText(tracking?.futureErrorRms ?? null) }}</strong></div></div><p class="gx-tuner__caption">Tracking error RMS · {{ offroad?.observed?.units ?? 'm/s²' }} · lower is better</p>
<p v-if="improvement !== null" class="gx-tuner__observed">{{ Math.abs(improvement).toFixed(1) }}% {{ improvement >= 0 ? 'lower' : 'higher' }} observed error<small>{{ state.key === 'confirmed' ? 'Comparison passed; controller acknowledged' : 'Observation so far, not a confirmed improvement' }}</small></p>
<div v-if="quota" class="gx-tuner__quota"><div><span>Comparable trial windows</span><strong>{{ quota.completed }} / {{ quota.required }}</strong></div><div class="gx-tuner__progress" role="progressbar" aria-label="Comparable trial windows" :aria-valuenow="quota.completed" :aria-valuemax="quota.required" aria-valuemin="0"><span :style="{ width: quota.percent + '%' }"></span></div></div>
<p class="gx-tuner__caption">Changes that make steering worse are rolled back. Accepted and saved values are shown separately.</p>
View File
-1
View File
@@ -1 +0,0 @@
{"protected_inventory_sha256": "634294450d5ee27ffd4369f4241cd61083c4c4244909ba9daff78b215b9049cb", "schema_version": 1}