LittlePlyResearch notebook

acceptance.py

python · 57 lines · Exact bytes

Line 1 ↗
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