""" Persona as a CGA motor — a rigid screw motion on the generation manifold. M = T * R where: T = translator versor (persona's position in concept space) R = rotor (persona's characteristic rotation) Applying persona: F_voiced = M * F * reverse(M) This is a versor product. Persona application is algebraically closed. No weight overlay. No post-hoc bias. No separate correction pass. Normalization doctrine: All calls here use unitize_versor() — the construction primitive. These are all construction-time operations: building motors from raw component arrays, composing two existing motors. None of these are gate injection operations, so normalize_to_versor() is forbidden here. """ import numpy as np from algebra.versor import versor_apply, unitize_versor from algebra.cl41 import geometric_product, reverse, basis_vector, N_COMPONENTS class PersonaMotor: def __init__(self, translator: np.ndarray, rotor: np.ndarray): """ translator: a versor encoding translational bias in CGA rotor: a versor encoding rotational character Both must satisfy versor_condition < 1e-6. """ self.M = unitize_versor( geometric_product( np.asarray(translator, dtype=np.float32), np.asarray(rotor, dtype=np.float32), ) ) def apply(self, F: np.ndarray) -> np.ndarray: """Apply persona to field F. Returns M * F * reverse(M).""" return versor_apply(self.M, F) def compose(self, other: "PersonaMotor") -> "PersonaMotor": """ Compose two persona motors: M_combined = self.M * other.M Used to blend persona layers (base persona + session persona). """ result = PersonaMotor.__new__(PersonaMotor) result.M = unitize_versor(geometric_product(self.M, other.M)) return result @classmethod def identity(cls) -> "PersonaMotor": """The identity motor — applies no transformation.""" inst = cls.__new__(cls) inst.M = np.zeros(N_COMPONENTS, dtype=np.float32) inst.M[0] = 1.0 return inst @classmethod def from_concept_vector(cls, concept: np.ndarray) -> "PersonaMotor": """ Build a persona motor from a 3D concept vector in R^3. Embeds as a CGA translator: T = 1 + (1/2) * t * e_inf where e_inf = e+ + e- (the point at infinity in CGA). """ concept = np.asarray(concept, dtype=np.float32) assert len(concept) == 3 e_inf = basis_vector(3) + basis_vector(4) # e+ + e- t_blade = np.zeros(N_COMPONENTS, dtype=np.float32) for i in range(3): t_blade += concept[i] * geometric_product(basis_vector(i), e_inf) translator = np.zeros(N_COMPONENTS, dtype=np.float32) translator[0] = 1.0 translator += 0.5 * t_blade rotor = np.zeros(N_COMPONENTS, dtype=np.float32) rotor[0] = 1.0 return cls( unitize_versor(translator), unitize_versor(rotor), ) @classmethod def from_identity_manifold(cls, manifold) -> "PersonaMotor": """ Build a persona motor from a live IdentityManifold. Each value_axis carries a direction tuple in R^3. The axes are composed additively into a single concept vector, then passed to from_concept_vector() to produce the CGA translator that encodes CORE's character as a geometric displacement in concept space. The resulting motor is non-identity: it biases every field walk toward the manifold's value directions without overriding the algebraic propagation rules. Falls back to identity() if the manifold has no value_axes or if all directions are zero — preserving safe-default behavior. """ if not manifold.value_axes: return cls.identity() combined = np.zeros(3, dtype=np.float32) for axis in manifold.value_axes: direction = np.asarray(axis.direction[:3], dtype=np.float32) if np.linalg.norm(direction) > 1e-8: combined += direction if np.linalg.norm(combined) < 1e-8: return cls.identity() # Normalize so the motor magnitude is consistent regardless of # how many axes are present. combined /= np.linalg.norm(combined) return cls.from_concept_vector(combined)