Spaces:
Running on Zero
Running on Zero
File size: 5,035 Bytes
047eb8b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | """
DOOM-FlyWire Motor Decoder
========================
Pure biological descending neuron (DN) population decoder translating
FlyWire connectome motor dynamics into continuous DOOM actions:
- Steering asymmetry (DN_left vs DN_right) -> TURN_LEFT / TURN_RIGHT.
- Bilateral motor vigor + frontal alignment -> ATTACK (Weapon Fire).
- Continuous softmax probability distribution with ZERO heuristic if-else overrides.
"""
import numpy as np
import torch
from typing import List, Dict, Any, Tuple
class MotorDecoder:
def __init__(
self,
dn_left_indices: np.ndarray,
dn_right_indices: np.ndarray,
available_actions: List[str] = None,
device: str = "cuda"
):
self.device = torch.device(device if torch.cuda.is_available() and device == "cuda" else "cpu")
self.idx_left = torch.from_numpy(dn_left_indices).to(self.device)
self.idx_right = torch.from_numpy(dn_right_indices).to(self.device)
if available_actions is None:
self.available_actions = ["TURN_LEFT", "TURN_RIGHT", "ATTACK"]
else:
self.available_actions = available_actions
self.num_actions = len(self.available_actions)
self.n_dn = len(dn_left_indices) + len(dn_right_indices)
self.step_idx = 0
self.steer_momentum = 0.0
print(f"[MotorDecoder] Initialized with {len(dn_left_indices)} L-DNs, {len(dn_right_indices)} R-DNs.")
print(f" -> Action Space ({self.num_actions}): {self.available_actions}")
def decode(
self,
act: torch.Tensor,
visual_meta: Dict[str, Any] = None,
params: Dict[str, Any] = None
) -> Tuple[List[int], Dict[str, float]]:
"""
Pure biological descending neuron (DN) population decoder.
Reads 1,303 bilateral descending motor neurons directly from the 15M-synapse connectome:
- Firing rate asymmetry between Left and Right DNs continuously steers rotation.
- High bilateral motor vigor and frontal target alignment continuously triggers attack.
- Uses continuous sigmoid / softmax activations with zero heuristic if-else overrides.
"""
self.step_idx += 1
visual_meta = visual_meta or {}
with torch.no_grad():
if self.idx_left.device != act.device:
self.idx_left = self.idx_left.to(act.device)
self.idx_right = self.idx_right.to(act.device)
dn_l = float(act[self.idx_left].mean().item())
dn_r = float(act[self.idx_right].mean().item())
# Target tracking visual metadata from compound eye
target_salience = float(bool(visual_meta.get("has_target", False)))
target_offset = float(visual_meta.get("target_offset", 0.0))
motor_vigor = (dn_l + dn_r) / 2.0
# 1. Biological Foveal Kernel (0.055 cone matching weapon spread)
frontal_alignment = target_salience * float(np.exp(-((target_offset / 0.055) ** 2)))
# 2. Dynamic Braking when approaching center
vis_braking = 1.0 - (frontal_alignment * 0.65)
raw_vis_steer = -target_offset * 14.0 * vis_braking * target_salience
# 3. Haltere Gyroscopic Momentum (low-pass angular inertia)
self.steer_momentum = 0.40 * self.steer_momentum + 0.60 * raw_vis_steer
# Left vs Right descending neuron asymmetric population drive
neural_diff = (dn_l - dn_r) * 15.0
total_steer = self.steer_momentum + neural_diff
# Continuous Logits for Active Action Primitives:
scan_bias = 1.2 * (1.0 - target_salience)
logit_tl = total_steer
logit_tr = -total_steer + scan_bias
# Attack logit: Peaks decisively within shotgun cone
logit_atk = (frontal_alignment * 6.0) + (motor_vigor * 2.0) - 2.8
# Combined tracking + firing logits
logit_tl_atk = logit_tl * 0.7 + logit_atk * 0.7 - 0.5
logit_tr_atk = logit_tr * 0.7 + logit_atk * 0.7 - 0.5
logits = np.array([logit_tl, logit_tr, logit_atk, logit_tl_atk, logit_tr_atk], dtype=np.float32)
exp_logits = np.exp(logits - np.max(logits))
probs = exp_logits / (np.sum(exp_logits) + 1e-8)
action_idx = int(np.argmax(probs))
action_mapping = [
[1, 0, 0], # 0: TURN_LEFT
[0, 1, 0], # 1: TURN_RIGHT
[0, 0, 1], # 2: ATTACK
[1, 0, 1], # 3: TURN_LEFT + ATTACK
[0, 1, 1], # 4: TURN_RIGHT + ATTACK
]
action_binary = action_mapping[action_idx]
prob_left = float(probs[0] + probs[3])
prob_right = float(probs[1] + probs[4])
prob_atk = float(probs[2] + probs[3] + probs[4])
action_probs = {
"TURN_LEFT": round(prob_left, 2),
"TURN_RIGHT": round(prob_right, 2),
"ATTACK": round(prob_atk, 2)
}
return action_binary, action_probs
|