acceptance.py
python · 57 lines · Exact bytes
Version & provenance · service-gpu-c205ec8f7f77
Recorded SHA-256 c237357038199cfae2c8956840bc558373ed16ff13ee30d53017cf49b30ae456Rendered SHA-256 c237357038199cfae2c8956840bc558373ed16ff13ee30d53017cf49b30ae456
Apache-2.0 for original LittlePly code, declared by the project owner on 2026-10-28. Historical source bytes and hashes are unchanged. Third-party components retain their own licenses; Lichess data remain CC0-1.0. License notes. Line links are stable within this snapshot. Exact allowlisted source from immutable live GPU release 6133648b2926-c205ec8f7f77, verified against release manifest SHA-256 a1cdc3f5f55c96b3164bc6fd78cb37308d547b2d7f07865dd37ad1448f2f3a1e. Runtime owner confirmed GPU serving, event-stream/model warmup, BOT identity and zero restarts on 2026-10-28. Capacity is up to four casual standard games, with bullet games alone; UltraBullet, correspondence and variants are unsupported. Checkpoint remains 6133648b2926b38542ee3ee1251a1ba111d61da9a8ea69d1c5f0559b09933643. app_multi.py is the multi-game controller; app.py is its shared base, whose legacy standalone admission differs. This is a source subset; host configuration, launcher, state, secrets and private games are excluded. This is a serving change, not a new model or evaluation.
1"""Generation-bound acceptance ownership; only settled operations may reconcile."""
2from dataclasses import dataclass,field
3import time,uuid
4
5@dataclass
6class Acceptance:
7 cid:str
8 generation:str=field(default_factory=lambda:uuid.uuid4().hex)
9 state:str='queued'
10 cancelled:bool=False
11 settled:float=0.
12 negatives:int=0
13 last_negative:float=0.
14 @property
15 def key(self):return 'accept:'+self.cid+':'+self.generation
16
17class Acceptances:
18 def __init__(self):self.operations={}
19 def begin(self,cid):
20 if cid in self.operations:raise RuntimeError('duplicate acceptance reservation')
21 op=Acceptance(cid);self.operations[cid]=op;return op
22 def find(self,key):
23 parts=key.split(':');op=self.operations.get(parts[1]) if len(parts)==3 else None
24 return op if op and op.key==key else None
25 def valid(self,key):
26 op=self.find(key)
27 return bool(op and not op.cancelled and op.state in ('queued','sending'))
28 def invalidate(self,cid):
29 op=self.operations.pop(cid,None)
30 if op:op.cancelled=True
31 def cancel(self,cid):
32 op=self.operations.get(cid)
33 if not op:return False
34 op.cancelled=True
35 if op.state=='queued':self.invalidate(cid);return True
36 return False
37 def sent(self,key):
38 op=self.find(key)
39 if not op or op.cancelled or op.state!='queued':return False
40 op.state='sending';return True
41 def settle(self,key,status):
42 op=self.find(key)
43 if not op:return None
44 if status in ('rejected','rate_limited','cancelled'):
45 self.invalidate(op.cid);return op.cid
46 op.state='settled';op.settled=time.monotonic();op.negatives=0;op.last_negative=0.
47 return None
48 def reconcile(self,active,requested_at,stream_live):
49 now=time.monotonic();released=[]
50 if not stream_live:return released
51 for cid,op in list(self.operations.items()):
52 if cid in active or op.state!='settled' or requested_at<op.settled:continue
53 if op.negatives and (requested_at<=op.last_negative or now-op.last_negative<2):continue
54 op.negatives+=1;op.last_negative=now
55 if op.negatives>=2:self.invalidate(cid);released.append(cid)
56 return released
57