mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-04 13:24:13 +08:00
Guard followed route identity and expose current prepared playhead
This commit is contained in:
@@ -30,7 +30,7 @@ async function refresh(){
|
||||
document.querySelector('#state').textContent=j.state+' · '+Math.max(0,j.audio_s||0).toFixed(1)+' s · '+(j.muted?'MUTED':'system audio output');
|
||||
const follow=j.paired_controls?.following;
|
||||
document.querySelector('#scope').textContent=follow?.enabled?'Galaxy is master · phone and comma controls are mirrored to this Mac':j.paired_controls?.enabled?'Mac + comma controls enabled · each button requests the same change on both':'Mac-only controls · comma is not paired in this session';
|
||||
const followErrors={peer_session_changed:'Comma replay restarted · reconnect this Mac session',local_session_changed:'Mac replay changed · reconnect this session',peer_action_in_progress:'Waiting for the Mac request to reach Galaxy',local_replay_unready:'Waiting for the Mac replay',peer_not_ready_for_replay:'Waiting for a fresh comma replay',peer_status_stale:'Galaxy status is stale',peer_timeout:'Galaxy status timed out'};
|
||||
const followErrors={peer_session_changed:'Comma replay restarted · reconnect this Mac session',local_session_changed:'Mac replay changed · reconnect this session',peer_route_mismatch:'Comma and Mac routes do not match',peer_route_changed:'Comma route changed · reconnect this Mac session',invalid_peer_route:'Galaxy route identity unavailable',peer_action_in_progress:'Waiting for the Mac request to reach Galaxy',local_replay_unready:'Waiting for the Mac replay',peer_not_ready_for_replay:'Waiting for a fresh comma replay',peer_status_stale:'Galaxy status is stale',peer_timeout:'Galaxy status timed out'};
|
||||
document.querySelector('#following').textContent=!follow?.enabled?'':follow.status==='following'?'Mac controls match fresh Galaxy state':follow.status==='applying'?'Applying Galaxy controls on Mac':followErrors[follow.error]||'Galaxy control mirroring paused · status unavailable';
|
||||
const local=j.demo.available?j.demo.mode+' / signals '+j.demo.signal_mode:'unready';
|
||||
document.querySelector('#targets').textContent='Mac '+local+'; comma '+commaStatus(j.paired_controls);
|
||||
|
||||
@@ -70,6 +70,8 @@ def control_server(project, out, shared, port, forwarder=None, *, follow_peer=Fa
|
||||
following.update(status='pending',error='peer_action_in_progress',snapshot=None);return
|
||||
demo=operator.demo_status()
|
||||
if not demo['available']:raise ValueError('local_replay_unready')
|
||||
local_route=demo.get('route') or module.read_json(out/'status.json').get('route')
|
||||
if snapshot['route']!=local_route:raise ValueError('peer_route_mismatch')
|
||||
if local_session is None:local_session=demo['session_id']
|
||||
elif demo['session_id']!=local_session:raise ValueError('local_session_changed')
|
||||
signature=(snapshot['session_id'],local_session,snapshot['mode'],snapshot['signal_mode'],sequence)
|
||||
@@ -84,7 +86,8 @@ def control_server(project, out, shared, port, forwarder=None, *, follow_peer=Fa
|
||||
following.update(status='following' if matching else 'applying',error=None,snapshot=snapshot)
|
||||
except Exception as error:
|
||||
known={'peer_action_in_progress','peer_session_changed','local_session_changed','local_replay_unready',
|
||||
'peer_not_ready_for_replay','invalid_peer_session','forwarder_disabled'}
|
||||
'peer_not_ready_for_replay','invalid_peer_session','forwarder_disabled','invalid_peer_route',
|
||||
'peer_route_changed','peer_route_mismatch'}
|
||||
reason=str(error) if str(error) in known else 'peer_timeout' if isinstance(error,TimeoutError) else 'peer_status_unavailable'
|
||||
with control_lock:following.update(status='paused',error=reason,snapshot=None)
|
||||
def follow():
|
||||
@@ -235,6 +238,9 @@ def audio_worker(a):
|
||||
if now-last_status>=.08:
|
||||
shared.update(state='READY' if anchor else 'PREPARING',audio_s=max(0,(position or 0)/rate))
|
||||
snapshot=dict(command_wall=now,input_mode='replay',compute='prepared-core',composer='ace',profile='prism',style='Prism',readiness=shared['state'],section='PREPARED PRISM',route=a.route,buffered=max(0,(len(audio)-(position or 0))/rate),presentation_session_id=session,demo_engagement_mode=controls.mode,demo_signal_mode=controls.signal_mode,engagement_presentation={'enabled':True},generation_invoked=False)
|
||||
if anchor is not None and state['route_t'] is not None and np.isfinite(state['route_t']):
|
||||
snapshot.update(route_t=float(state['route_t']),elapsed=max(0,(position or 0)/rate),
|
||||
source_model_ns=int(sm.logMonoTime['modelV2']))
|
||||
snapshot=delay.apply(snapshot,rendered)
|
||||
# The command API must remain available during initial DAC lead-in.
|
||||
snapshot.setdefault('engagement_presentation',{'enabled':True})
|
||||
|
||||
@@ -141,6 +141,7 @@ class PairedDemoControls:
|
||||
self._active = None
|
||||
self._closed = False
|
||||
self._bound_session = None
|
||||
self._bound_route = None
|
||||
self._write_revision = 0
|
||||
|
||||
def read_status(self):
|
||||
@@ -162,12 +163,15 @@ class PairedDemoControls:
|
||||
received = time.monotonic()
|
||||
if received-started >= min(self.timeout,.75):raise TimeoutError()
|
||||
session = _session(status)
|
||||
route = status['demo'].get('route')
|
||||
if not isinstance(route,str) or not route or route=='live':raise ValueError('invalid_peer_route')
|
||||
with self._lock:
|
||||
if self._closed:raise ValueError('forwarder_disabled')
|
||||
if revision != self._write_revision or self._active is not None:raise ValueError('peer_action_in_progress')
|
||||
if self._bound_session is None:self._bound_session=session
|
||||
if self._bound_session is None:self._bound_session,self._bound_route=session,route
|
||||
elif session != self._bound_session:raise ValueError('peer_session_changed')
|
||||
return dict(session_id=session,mode=status['demo']['mode'],signal_mode=status['demo']['signal_mode'],
|
||||
elif route != self._bound_route:raise ValueError('peer_route_changed')
|
||||
return dict(session_id=session,route=route,mode=status['demo']['mode'],signal_mode=status['demo']['signal_mode'],
|
||||
request_started_wall=started,received_wall=received,peer_status=status)
|
||||
|
||||
def _result(self, status, action, value, *, acknowledged=False, applied=False,
|
||||
|
||||
@@ -191,7 +191,7 @@ class ControlTests(unittest.TestCase):
|
||||
|
||||
def following_peer(self):
|
||||
status=dict(available=True,offroad=True,state='READY',live={'enabled':False},
|
||||
demo=dict(available=True,session_id='comma-session',mode='recorded',signal_mode='recorded'))
|
||||
demo=dict(available=True,session_id='comma-session',route='fixture-route',mode='recorded',signal_mode='recorded'))
|
||||
calls=[]
|
||||
def transport(method,*args):
|
||||
calls.append(method)
|
||||
@@ -238,6 +238,13 @@ class ControlTests(unittest.TestCase):
|
||||
self.wait_for(lambda:self.request()[1]['paired_controls']['following']['error']=='local_session_changed')
|
||||
self.assertFalse((self.out/'demo_engagement.json').exists())
|
||||
|
||||
def test_different_local_route_cannot_receive_peer_controls(self):
|
||||
peer,_=self.following_peer()
|
||||
self.state['route']='other-route';self.write_state()
|
||||
peer['demo']['mode']='engaged'
|
||||
self.wait_for(lambda:self.request()[1]['paired_controls']['following']['error']=='peer_route_mismatch')
|
||||
self.assertFalse((self.out/'demo_engagement.json').exists())
|
||||
|
||||
|
||||
class RememberedConfigTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -16,7 +16,7 @@ from paired_demo_controls import GalaxyPeer, PairedDemoControls, _NoRedirect, _h
|
||||
BASE = 'https://galaxy.firestar.link/ABCDEFGHIJKLMNOP'
|
||||
COOKIE = 'ABCDEFGHIJKLMNOP%3A' + 'a' * 64
|
||||
READY = {'available': True, 'offroad': True, 'state': 'READY', 'live': {'enabled': False},
|
||||
'demo': {'available': True, 'session_id': 'independent-peer-session',
|
||||
'demo': {'available': True, 'session_id': 'independent-peer-session', 'route':'fixture-route',
|
||||
'mode': 'recorded', 'signal_mode': 'recorded'}}
|
||||
|
||||
|
||||
@@ -153,6 +153,15 @@ class PairedTests(unittest.TestCase):
|
||||
self.transport.status['demo']['readiness']='PREPARING'
|
||||
with self.assertRaisesRegex(ValueError,'peer_not_ready_for_replay'):controls.read_status()
|
||||
|
||||
def test_follower_requires_and_pins_exact_route_identity(self):
|
||||
controls=self.forwarder()
|
||||
self.transport.status['demo'].pop('route')
|
||||
with self.assertRaisesRegex(ValueError,'invalid_peer_route'):controls.read_status()
|
||||
self.transport.status['demo']['route']='fixture-route'
|
||||
self.assertEqual(controls.read_status()['route'],'fixture-route')
|
||||
self.transport.status['demo']['route']='different-route'
|
||||
with self.assertRaisesRegex(ValueError,'peer_route_changed'):controls.read_status()
|
||||
|
||||
def test_late_status_and_disabled_reader_never_return_applied_state(self):
|
||||
controls = self.forwarder()
|
||||
with patch('paired_demo_controls.time.monotonic',side_effect=[100.,101.]):
|
||||
|
||||
Reference in New Issue
Block a user