{"_id":"@cliffy-ga/tsukoshi","_rev":"3-c3d51338b8f9141673448c089accb047","name":"@cliffy-ga/tsukoshi","dist-tags":{"latest":"0.3.1"},"versions":{"0.2.0":{"name":"@cliffy-ga/tsukoshi","version":"0.2.0","keywords":["geometric-algebra","state-management","interpolation","slerp","rotor","transforms","crdt","distributed","consensus","react-native","mobile"],"author":{"name":"Cliffy Contributors"},"license":"MIT","_id":"@cliffy-ga/tsukoshi@0.2.0","maintainers":[{"name":"justinelliottcobb","email":"justincobb76@pm.me"}],"homepage":"https://github.com/justinelliottcobb/Cliffy#readme","bugs":{"url":"https://github.com/justinelliottcobb/Cliffy/issues"},"dist":{"shasum":"89e47db2d6cf55a251f8e8a70e019d5f8736c1cd","tarball":"https://registry.npmjs.org/@cliffy-ga/tsukoshi/-/tsukoshi-0.2.0.tgz","fileCount":34,"integrity":"sha512-PjLVX0OI05hWG6KgRB0xmfNDNUSSM3eJWxczLREuZr5Eg/daEu6lJJBBJvsNZplBIOxxOTdH+Te2+mRODWtXCA==","signatures":[{"sig":"MEQCIDTUtUHyjTml+M/hS7egES7R93Y5nMM3H3hYecEOTudkAiAAvCfeQ82lleFhXykdI/S0Mrq4NDjJAqE0aKsMKRtqbg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":417078},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./protocols":{"types":"./dist/protocols/index.d.ts","import":"./dist/protocols/index.js","require":"./dist/protocols/index.cjs"}},"gitHead":"9e5e701c21ca3e824081063a819d29c2d8c39f4c","scripts":{"dev":"tsup src/index.ts src/protocols/index.ts --format esm,cjs --dts --watch","test":"vitest run","build":"tsup src/index.ts src/protocols/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"justinelliottcobb","email":"justincobb76@pm.me"},"repository":{"url":"git+https://github.com/justinelliottcobb/Cliffy.git","type":"git","directory":"cliffy-tsukoshi"},"_npmVersion":"10.9.3","description":"Minimal geometric state management with distributed protocols - pure TypeScript, zero dependencies","directories":{},"_nodeVersion":"22.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^1.0.0","typescript":"^5.3.0","@cliffy-ga/core":"file:../cliffy-wasm/pkg"},"_npmOperationalInternal":{"tmp":"tmp/tsukoshi_0.2.0_1771710814289_0.20634726354221944","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@cliffy-ga/tsukoshi","version":"0.3.0","keywords":["geometric-algebra","state-management","interpolation","slerp","rotor","transforms","crdt","distributed","consensus","react-native","mobile"],"author":{"name":"Cliffy Contributors"},"license":"MIT","_id":"@cliffy-ga/tsukoshi@0.3.0","maintainers":[{"name":"justinelliottcobb","email":"justincobb76@pm.me"}],"homepage":"https://github.com/justinelliottcobb/Cliffy#readme","bugs":{"url":"https://github.com/justinelliottcobb/Cliffy/issues"},"dist":{"shasum":"aa5bb922750d84e53ef8c72c59eeb389a6a594c7","tarball":"https://registry.npmjs.org/@cliffy-ga/tsukoshi/-/tsukoshi-0.3.0.tgz","fileCount":34,"integrity":"sha512-9YMKMQjjbY1bMzHkY6WrAdQxuVOL5AF9UnYBVZWedi8e/k4R6SA7hdBKrLCfjM2HE1cl33D6lFxpKWAD+MwIMA==","signatures":[{"sig":"MEUCIAVQA6vq9QeYsf8NpBOYFPEPpr65lCEtmF2ebY3MqWemAiEAmZ5tFTcsSx6Qw4FEMIK+Ya21eqpNKVEYxsFpiJVF+cs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":417078},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./protocols":{"types":"./dist/protocols/index.d.ts","import":"./dist/protocols/index.js","require":"./dist/protocols/index.cjs"}},"gitHead":"af980678efa5a7da43d6608a75898bb8808de6b2","scripts":{"dev":"tsup src/index.ts src/protocols/index.ts --format esm,cjs --dts --watch","test":"vitest run","build":"tsup src/index.ts src/protocols/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"justinelliottcobb","email":"justincobb76@pm.me"},"repository":{"url":"git+https://github.com/justinelliottcobb/Cliffy.git","type":"git","directory":"cliffy-tsukoshi"},"_npmVersion":"10.9.3","description":"Minimal geometric state management with distributed protocols - pure TypeScript, zero dependencies","directories":{},"_nodeVersion":"22.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^1.0.0","typescript":"^5.3.0","@cliffy-ga/core":"file:../cliffy-wasm/pkg"},"_npmOperationalInternal":{"tmp":"tmp/tsukoshi_0.3.0_1772489964349_0.9711996940307221","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@cliffy-ga/tsukoshi","version":"0.3.1","description":"Minimal geometric state management with distributed protocols - pure TypeScript, zero dependencies","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./protocols":{"types":"./dist/protocols/index.d.ts","import":"./dist/protocols/index.js","require":"./dist/protocols/index.cjs"}},"scripts":{"build":"tsup src/index.ts src/protocols/index.ts --format esm,cjs --dts --clean","dev":"tsup src/index.ts src/protocols/index.ts --format esm,cjs --dts --watch","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["geometric-algebra","state-management","interpolation","slerp","rotor","transforms","crdt","distributed","consensus","react-native","mobile"],"author":{"name":"Cliffy Contributors"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/justinelliottcobb/Cliffy.git","directory":"cliffy-tsukoshi"},"publishConfig":{"access":"public"},"devDependencies":{"@cliffy-ga/core":"file:../cliffy-wasm/pkg","tsup":"^8.0.0","typescript":"^5.3.0","vitest":"^1.0.0"},"_id":"@cliffy-ga/tsukoshi@0.3.1","gitHead":"f6d0b729951bd20dea48c6906eefc4dbbffbda3b","bugs":{"url":"https://github.com/justinelliottcobb/Cliffy/issues"},"homepage":"https://github.com/justinelliottcobb/Cliffy#readme","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-nuWWnGhc7shrq637HOGlu7vA3Q2xxtxYE8E+X23DPIHB2Yx325rgqaCHPfLBu+2ACQV5d6S2jjvshfyZhvZOeQ==","shasum":"375a9676b53422080cecf6f0497e1f55997c35bf","tarball":"https://registry.npmjs.org/@cliffy-ga/tsukoshi/-/tsukoshi-0.3.1.tgz","fileCount":34,"unpackedSize":417078,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAQZ0uebRsZugQW8l+NbjGcR9c5TzTpWtEryvIxw2KgRAiEAvEfVd8J1Dl16ii7YYMswiCRAaIA5axTCJNqhFRwxvfQ="}]},"_npmUser":{"name":"justinelliottcobb","email":"justincobb76@pm.me"},"directories":{},"maintainers":[{"name":"justinelliottcobb","email":"justincobb76@pm.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/tsukoshi_0.3.1_1772552463055_0.9883692729881326"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-21T21:53:33.912Z","modified":"2026-03-03T15:41:03.345Z","0.2.0":"2026-02-21T21:53:34.439Z","0.3.0":"2026-03-02T22:19:24.556Z","0.3.1":"2026-03-03T15:41:03.208Z"},"bugs":{"url":"https://github.com/justinelliottcobb/Cliffy/issues"},"author":{"name":"Cliffy Contributors"},"license":"MIT","homepage":"https://github.com/justinelliottcobb/Cliffy#readme","keywords":["geometric-algebra","state-management","interpolation","slerp","rotor","transforms","crdt","distributed","consensus","react-native","mobile"],"repository":{"type":"git","url":"git+https://github.com/justinelliottcobb/Cliffy.git","directory":"cliffy-tsukoshi"},"description":"Minimal geometric state management with distributed protocols - pure TypeScript, zero dependencies","maintainers":[{"name":"justinelliottcobb","email":"justincobb76@pm.me"}],"readme":"# cliffy-tsukoshi\n\nMinimal geometric state management for JavaScript/TypeScript. Pure TypeScript, zero dependencies, works everywhere including React Native.\n\n## Features\n\n- **GeometricState** - Smooth state interpolation via `.blend()`\n- **Rotor** - Rotation representation with SLERP interpolation\n- **Transform** - Combined rotation + translation\n- **Distributed Protocols** - CRDT, vector clocks, sync, consensus\n- **Zero dependencies** - Pure TypeScript math\n- **Universal** - Works in browser, Node.js, React Native, Deno\n\n## Installation\n\n```bash\nnpm install cliffy-tsukoshi\n```\n\n## Quick Start\n\n```typescript\nimport { GeometricState, Rotor, Transform } from 'cliffy-tsukoshi';\n\n// Create position state\nconst position = GeometricState.fromVector(100, 200, 0);\n\n// Smooth interpolation (the key feature!)\nconst target = GeometricState.fromVector(300, 400, 0);\nconst smoothed = position.blend(target, 0.15); // Move 15% toward target\n\n// Apply rotation (45 degrees in XY plane)\nconst rotation = Rotor.xy(Math.PI / 4);\nconst rotated = position.applyRotor(rotation);\n\n// Full transform (rotation + translation)\nconst transform = Transform.new(\n  Rotor.xz(Math.PI / 6),\n  { x: 10, y: 20, z: 0 }\n);\nconst transformed = position.applyTransform(transform);\n\n// Extract values\nconst [x, y, z] = smoothed.asVector();\nconsole.log(`Position: (${x}, ${y}, ${z})`);\n```\n\n## Core Concepts\n\n### GeometricState\n\nThe main container for position/state data. Internally uses GA3 (3D Geometric Algebra) but exposes a simple vector interface.\n\n```typescript\n// Create states\nconst pos = GeometricState.fromVector(x, y, z);\nconst pos2D = GeometricState.fromVector2D(x, y);\nconst value = GeometricState.fromScalar(42);\n\n// Smooth interpolation - the killer feature\n// In a game loop: position = position.blend(target, 0.1)\nconst smoothed = current.blend(target, t);\n\n// Arithmetic\nconst sum = a.add(b);\nconst diff = a.sub(b);\nconst scaled = a.scale(2);\n\n// Extract values\nconst [x, y, z] = state.asVector();\nconst { x, y, z } = state.asVectorObject();\nconst scalar = state.asScalar();\n```\n\n### Rotor (Rotations)\n\nRotors represent rotations without gimbal lock. They support smooth interpolation via SLERP.\n\n```typescript\n// Create rotations\nconst r1 = Rotor.xy(angle);  // Around Z axis\nconst r2 = Rotor.xz(angle);  // Around Y axis\nconst r3 = Rotor.yz(angle);  // Around X axis\nconst r4 = Rotor.fromAxisAngle(ax, ay, az, angle);\n\n// Apply to state\nconst rotated = position.applyRotor(rotation);\n\n// Compose rotations\nconst combined = r1.then(r2);  // r1 first, then r2\n\n// Interpolate (SLERP)\nconst halfway = r1.slerpTo(r2, 0.5);\n```\n\n### Transform (Rotation + Translation)\n\nCombines rotation and translation into a single operation.\n\n```typescript\n// Create transforms\nconst t = Transform.new(rotor, { x: 10, y: 20, z: 0 });\nconst tRotate = Transform.rotation(rotor);\nconst tMove = Transform.fromTranslation({ x: 10, y: 0, z: 0 });\n\n// Apply\nconst transformed = position.applyTransform(t);\n\n// Compose\nconst combined = t1.then(t2);\n\n// Interpolate\nconst halfway = t1.interpolateTo(t2, 0.5);\n```\n\n### ReactiveState\n\nA mutable container with subscriptions for reactive updates.\n\n```typescript\nimport { reactiveState, GeometricState } from 'cliffy-tsukoshi';\n\nconst position = reactiveState(GeometricState.fromVector(0, 0, 0));\n\n// Subscribe to changes\nposition.subscribe((state) => {\n  const [x, y, z] = state.asVector();\n  updateUI(x, y, z);\n});\n\n// Update (triggers subscribers)\nposition.set(GeometricState.fromVector(100, 200, 0));\n\n// Smooth update\nposition.blendTo(target, 0.1);\n```\n\n## Common Patterns\n\n### Smooth Following (Game Loop)\n\n```typescript\nfunction gameLoop() {\n  // Smooth follow: move 10% toward target each frame\n  playerPosition = playerPosition.blend(targetPosition, 0.1);\n\n  // Smooth rotation\n  playerRotation = playerRotation.slerpTo(targetRotation, 0.1);\n\n  render();\n  requestAnimationFrame(gameLoop);\n}\n```\n\n### 2D Game Movement\n\n```typescript\nconst position = GeometricState.fromVector2D(100, 100);\nconst velocity = GeometricState.fromVector2D(5, 0);\n\nfunction update() {\n  // Move\n  position = position.add(velocity);\n\n  // Rotate velocity (turn)\n  const turn = Rotor.xy(turnAngle);\n  velocity = velocity.applyRotor(turn);\n}\n```\n\n### React Native Integration\n\n```typescript\nimport { useRef, useEffect } from 'react';\nimport { Animated } from 'react-native';\nimport { GeometricState } from 'cliffy-tsukoshi';\n\nfunction useGeometricAnimation(target: GeometricState) {\n  const position = useRef(GeometricState.zero());\n  const animX = useRef(new Animated.Value(0)).current;\n  const animY = useRef(new Animated.Value(0)).current;\n\n  useEffect(() => {\n    const interval = setInterval(() => {\n      position.current = position.current.blend(target, 0.1);\n      const [x, y] = position.current.asVector2D();\n      animX.setValue(x);\n      animY.setValue(y);\n    }, 16);\n\n    return () => clearInterval(interval);\n  }, [target]);\n\n  return { x: animX, y: animY };\n}\n```\n\n## More Examples\n\n### Camera Following with Damping\n\n```typescript\nclass SmoothCamera {\n  position: GeometricState;\n  zoom: GeometricState;\n\n  constructor() {\n    this.position = GeometricState.fromVector2D(0, 0);\n    this.zoom = GeometricState.fromScalar(1);\n  }\n\n  follow(target: GeometricState, deltaTime: number) {\n    // Smooth follow with frame-rate independent damping\n    const smoothing = 1 - Math.pow(0.001, deltaTime);\n    this.position = this.position.blend(target, smoothing);\n  }\n\n  zoomTo(level: number, speed: number = 0.1) {\n    const targetZoom = GeometricState.fromScalar(level);\n    this.zoom = this.zoom.blend(targetZoom, speed);\n  }\n\n  getViewMatrix(): { x: number; y: number; scale: number } {\n    const [x, y] = this.position.asVector2D();\n    return { x: -x, y: -y, scale: this.zoom.asScalar() };\n  }\n}\n```\n\n### Touch/Joystick Input\n\n```typescript\nclass JoystickController {\n  private input = GeometricState.zero();\n  private smoothedInput = GeometricState.zero();\n\n  // Called on touch/mouse move\n  setInput(dx: number, dy: number) {\n    // Clamp to unit circle\n    const mag = Math.sqrt(dx * dx + dy * dy);\n    if (mag > 1) {\n      dx /= mag;\n      dy /= mag;\n    }\n    this.input = GeometricState.fromVector2D(dx, dy);\n  }\n\n  // Called on touch/mouse up\n  release() {\n    this.input = GeometricState.zero();\n  }\n\n  // Called every frame\n  update(): [number, number] {\n    // Smooth the input to avoid jerky movement\n    this.smoothedInput = this.smoothedInput.blend(this.input, 0.2);\n    return this.smoothedInput.asVector2D();\n  }\n}\n```\n\n### Particle System\n\n```typescript\ninterface Particle {\n  position: GeometricState;\n  velocity: GeometricState;\n  life: number;\n}\n\nclass ParticleEmitter {\n  particles: Particle[] = [];\n  origin: GeometricState;\n\n  constructor(x: number, y: number) {\n    this.origin = GeometricState.fromVector2D(x, y);\n  }\n\n  emit(count: number) {\n    for (let i = 0; i < count; i++) {\n      const angle = Math.random() * Math.PI * 2;\n      const speed = 50 + Math.random() * 100;\n\n      this.particles.push({\n        position: this.origin.clone(),\n        velocity: GeometricState.fromVector2D(\n          Math.cos(angle) * speed,\n          Math.sin(angle) * speed\n        ),\n        life: 1.0,\n      });\n    }\n  }\n\n  update(dt: number) {\n    const gravity = GeometricState.fromVector2D(0, 200);\n    const drag = 0.98;\n\n    this.particles = this.particles.filter(p => {\n      // Apply physics\n      p.velocity = p.velocity.add(gravity.scale(dt)).scale(drag);\n      p.position = p.position.add(p.velocity.scale(dt));\n      p.life -= dt;\n\n      return p.life > 0;\n    });\n  }\n}\n```\n\n### Animated Sprite with Rotation\n\n```typescript\nclass AnimatedSprite {\n  position: GeometricState;\n  rotation: Rotor;\n  targetRotation: Rotor;\n\n  constructor(x: number, y: number) {\n    this.position = GeometricState.fromVector2D(x, y);\n    this.rotation = Rotor.identity();\n    this.targetRotation = Rotor.identity();\n  }\n\n  lookAt(targetX: number, targetY: number) {\n    const [x, y] = this.position.asVector2D();\n    const angle = Math.atan2(targetY - y, targetX - x);\n    this.targetRotation = Rotor.xy(angle);\n  }\n\n  update() {\n    // Smooth rotation toward target\n    this.rotation = this.rotation.slerpTo(this.targetRotation, 0.15);\n  }\n\n  getAngle(): number {\n    return this.rotation.angle();\n  }\n}\n```\n\n### Physics Body with Velocity Damping\n\n```typescript\nclass PhysicsBody {\n  position: GeometricState;\n  velocity: GeometricState;\n\n  readonly mass: number;\n  readonly drag: number;\n\n  constructor(x: number, y: number, mass = 1, drag = 0.02) {\n    this.position = GeometricState.fromVector2D(x, y);\n    this.velocity = GeometricState.zero();\n    this.mass = mass;\n    this.drag = drag;\n  }\n\n  applyForce(fx: number, fy: number) {\n    const acceleration = GeometricState.fromVector2D(fx / this.mass, fy / this.mass);\n    this.velocity = this.velocity.add(acceleration);\n  }\n\n  applyImpulse(ix: number, iy: number) {\n    this.velocity = this.velocity.add(GeometricState.fromVector2D(ix, iy));\n  }\n\n  update(dt: number) {\n    // Apply drag\n    this.velocity = this.velocity.scale(1 - this.drag);\n\n    // Update position\n    this.position = this.position.add(this.velocity.scale(dt));\n\n    // Stop if very slow\n    if (this.velocity.magnitude() < 0.01) {\n      this.velocity = GeometricState.zero();\n    }\n  }\n}\n```\n\n### Path Following\n\n```typescript\nclass PathFollower {\n  private waypoints: GeometricState[];\n  private currentIndex = 0;\n  private position: GeometricState;\n\n  constructor(waypoints: Array<[number, number]>) {\n    this.waypoints = waypoints.map(([x, y]) => GeometricState.fromVector2D(x, y));\n    this.position = this.waypoints[0].clone();\n  }\n\n  update(speed: number = 0.05): [number, number] {\n    if (this.waypoints.length === 0) {\n      return this.position.asVector2D();\n    }\n\n    const target = this.waypoints[this.currentIndex];\n    this.position = this.position.blend(target, speed);\n\n    // Check if reached waypoint\n    if (this.position.distance(target) < 1) {\n      this.currentIndex = (this.currentIndex + 1) % this.waypoints.length;\n    }\n\n    return this.position.asVector2D();\n  }\n\n  getProgress(): number {\n    return this.currentIndex / this.waypoints.length;\n  }\n}\n```\n\n### UI Progress/Health Bar\n\n```typescript\nclass SmoothProgressBar {\n  private displayValue: GeometricState;\n  private actualValue: number;\n\n  constructor(initial: number = 1) {\n    this.actualValue = initial;\n    this.displayValue = GeometricState.fromScalar(initial);\n  }\n\n  setValue(value: number) {\n    this.actualValue = Math.max(0, Math.min(1, value));\n  }\n\n  update(): number {\n    const target = GeometricState.fromScalar(this.actualValue);\n    this.displayValue = this.displayValue.blend(target, 0.1);\n    return this.displayValue.asScalar();\n  }\n\n  // For damage flash effects\n  flash(amount: number) {\n    // Instantly show damage, then smooth back\n    this.displayValue = GeometricState.fromScalar(\n      this.displayValue.asScalar() - amount\n    );\n  }\n}\n```\n\n### Pan and Zoom Controls\n\n```typescript\nclass PanZoomController {\n  offset: GeometricState;\n  zoom: GeometricState;\n\n  private targetOffset: GeometricState;\n  private targetZoom: GeometricState;\n\n  constructor() {\n    this.offset = GeometricState.zero();\n    this.targetOffset = GeometricState.zero();\n    this.zoom = GeometricState.fromScalar(1);\n    this.targetZoom = GeometricState.fromScalar(1);\n  }\n\n  pan(dx: number, dy: number) {\n    const scale = this.zoom.asScalar();\n    this.targetOffset = this.targetOffset.add(\n      GeometricState.fromVector2D(dx / scale, dy / scale)\n    );\n  }\n\n  zoomAt(factor: number, centerX: number, centerY: number) {\n    const currentZoom = this.targetZoom.asScalar();\n    const newZoom = Math.max(0.1, Math.min(10, currentZoom * factor));\n    this.targetZoom = GeometricState.fromScalar(newZoom);\n\n    // Zoom toward cursor position\n    const [ox, oy] = this.targetOffset.asVector2D();\n    const zoomRatio = newZoom / currentZoom;\n    this.targetOffset = GeometricState.fromVector2D(\n      centerX - (centerX - ox) * zoomRatio,\n      centerY - (centerY - oy) * zoomRatio\n    );\n  }\n\n  update() {\n    this.offset = this.offset.blend(this.targetOffset, 0.15);\n    this.zoom = this.zoom.blend(this.targetZoom, 0.15);\n  }\n\n  screenToWorld(screenX: number, screenY: number): [number, number] {\n    const [ox, oy] = this.offset.asVector2D();\n    const scale = this.zoom.asScalar();\n    return [(screenX - ox) / scale, (screenY - oy) / scale];\n  }\n}\n```\n\n### React Hook for Smooth Values\n\n```typescript\nimport { useState, useEffect, useRef } from 'react';\nimport { GeometricState } from 'cliffy-tsukoshi';\n\nfunction useSmoothValue(target: number, smoothing: number = 0.1): number {\n  const [display, setDisplay] = useState(target);\n  const state = useRef(GeometricState.fromScalar(target));\n\n  useEffect(() => {\n    let animationId: number;\n    const targetState = GeometricState.fromScalar(target);\n\n    function animate() {\n      state.current = state.current.blend(targetState, smoothing);\n      setDisplay(state.current.asScalar());\n\n      if (Math.abs(state.current.asScalar() - target) > 0.001) {\n        animationId = requestAnimationFrame(animate);\n      }\n    }\n\n    animationId = requestAnimationFrame(animate);\n    return () => cancelAnimationFrame(animationId);\n  }, [target, smoothing]);\n\n  return display;\n}\n\n// Usage\nfunction Counter({ value }: { value: number }) {\n  const smoothValue = useSmoothValue(value);\n  return <span>{Math.round(smoothValue)}</span>;\n}\n```\n\n### Multiplayer State Interpolation\n\n```typescript\ninterface NetworkState {\n  position: GeometricState;\n  timestamp: number;\n}\n\nclass InterpolatedPlayer {\n  private buffer: NetworkState[] = [];\n  private renderPosition: GeometricState;\n\n  // Render 100ms behind to allow interpolation\n  private readonly INTERPOLATION_DELAY = 100;\n\n  constructor(initialX: number, initialY: number) {\n    this.renderPosition = GeometricState.fromVector2D(initialX, initialY);\n  }\n\n  // Called when network update arrives\n  receiveState(x: number, y: number, serverTime: number) {\n    this.buffer.push({\n      position: GeometricState.fromVector2D(x, y),\n      timestamp: serverTime,\n    });\n\n    // Keep only recent states\n    const cutoff = serverTime - 1000;\n    this.buffer = this.buffer.filter(s => s.timestamp > cutoff);\n  }\n\n  // Called every frame\n  update(currentTime: number): [number, number] {\n    const renderTime = currentTime - this.INTERPOLATION_DELAY;\n\n    // Find states to interpolate between\n    let before: NetworkState | null = null;\n    let after: NetworkState | null = null;\n\n    for (const state of this.buffer) {\n      if (state.timestamp <= renderTime) {\n        before = state;\n      } else if (!after) {\n        after = state;\n      }\n    }\n\n    if (before && after) {\n      // Interpolate between the two states\n      const t = (renderTime - before.timestamp) / (after.timestamp - before.timestamp);\n      this.renderPosition = before.position.blend(after.position, t);\n    } else if (before) {\n      // Extrapolate slightly\n      this.renderPosition = this.renderPosition.blend(before.position, 0.1);\n    }\n\n    return this.renderPosition.asVector2D();\n  }\n}\n```\n\n## Why Geometric Algebra?\n\nUnder the hood, cliffy-tsukoshi uses GA3 (Clifford Algebra Cl(3,0)). This provides:\n\n1. **Unified representation** - Scalars, vectors, rotations all in one type\n2. **No gimbal lock** - Rotors avoid quaternion edge cases\n3. **Composable** - Rotations compose correctly via geometric product\n4. **Interpolation** - SLERP \"just works\" for smooth rotation blending\n\nYou don't need to understand GA to use the library - the API is designed around familiar vector operations.\n\n## API Reference\n\n### GeometricState\n\n| Method | Description |\n|--------|-------------|\n| `fromVector(x, y, z)` | Create from 3D coordinates |\n| `fromVector2D(x, y)` | Create from 2D coordinates |\n| `fromScalar(n)` | Create from scalar value |\n| `blend(target, t)` | Interpolate toward target |\n| `add(other)` | Vector addition |\n| `sub(other)` | Vector subtraction |\n| `scale(factor)` | Scalar multiplication |\n| `applyRotor(r)` | Apply rotation |\n| `applyTransform(t)` | Apply rotation + translation |\n| `asVector()` | Extract as [x, y, z] |\n| `asVector2D()` | Extract as [x, y] |\n| `asScalar()` | Extract scalar value |\n| `distance(other)` | Euclidean distance |\n| `magnitude()` | Vector length |\n\n### Rotor\n\n| Method | Description |\n|--------|-------------|\n| `xy(angle)` | Rotation in XY plane (around Z) |\n| `xz(angle)` | Rotation in XZ plane (around Y) |\n| `yz(angle)` | Rotation in YZ plane (around X) |\n| `fromAxisAngle(ax, ay, az, angle)` | From axis-angle |\n| `identity()` | No rotation |\n| `transform(v)` | Apply to multivector |\n| `then(other)` | Compose rotations |\n| `inverse()` | Reverse rotation |\n| `slerp(t)` | Interpolate from identity |\n| `slerpTo(other, t)` | Interpolate to other |\n| `angle()` | Get rotation angle |\n\n### Transform\n\n| Method | Description |\n|--------|-------------|\n| `new(rotor, translation)` | Create from components |\n| `identity()` | No transformation |\n| `rotation(rotor)` | Pure rotation |\n| `fromTranslation(t)` | Pure translation |\n| `apply(v)` | Apply to multivector |\n| `then(other)` | Compose transforms |\n| `inverse()` | Reverse transform |\n| `interpolateTo(other, t)` | Interpolate to other |\n\n---\n\n## Distributed Protocols\n\ncliffy-tsukoshi includes a complete suite of distributed protocols for building collaborative applications. Import from `cliffy-tsukoshi/protocols` or directly from the main module.\n\n```typescript\nimport { GeometricCRDT, VectorClock, SyncState } from 'cliffy-tsukoshi';\n// or\nimport { GeometricCRDT, VectorClock } from 'cliffy-tsukoshi/protocols';\n```\n\n### VectorClock\n\nTrack causality in distributed systems without centralized coordination.\n\n```typescript\nimport { VectorClock } from 'cliffy-tsukoshi';\n\nconst clock1 = new VectorClock();\nconst clock2 = new VectorClock();\n\n// Each node increments its own entry\nclock1.tick('node-1');\nclock2.tick('node-2');\n\n// These events are concurrent (neither happened before the other)\nconsole.log(clock1.concurrent(clock2)); // true\n\n// After syncing, merge clocks\nclock1.update(clock2);\nclock1.tick('node-1');\n\n// Now clock1 happened after clock2\nconsole.log(clock2.happensBefore(clock1)); // true\n```\n\n### GeometricCRDT\n\nConflict-free replicated data type using geometric algebra for merge operations.\n\n```typescript\nimport { GeometricCRDT, OperationType, scalar } from 'cliffy-tsukoshi';\n\n// Create CRDTs on two different nodes\nconst nodeId1 = crypto.randomUUID();\nconst nodeId2 = crypto.randomUUID();\n\nconst crdt1 = new GeometricCRDT(nodeId1, scalar(0));\nconst crdt2 = new GeometricCRDT(nodeId2, scalar(0));\n\n// Each node makes independent updates\nconst op1 = crdt1.createOperation(scalar(5), OperationType.Addition);\ncrdt1.applyOperation(op1);\n\nconst op2 = crdt2.createOperation(scalar(3), OperationType.Addition);\ncrdt2.applyOperation(op2);\n\n// Merge - order doesn't matter, result is always consistent\nconst merged1 = crdt1.merge(crdt2);\nconst merged2 = crdt2.merge(crdt1);\n\n// Both produce the same state (8.0)\nconsole.log(merged1.state[0] === merged2.state[0]); // true\n```\n\n### Lattice Operations\n\nJoin-semilattice operations for guaranteed convergence.\n\n```typescript\nimport { GA3Lattice, ComponentLattice, latticeJoin, latticeMeet } from 'cliffy-tsukoshi';\n\n// Magnitude-based lattice (larger magnitude wins)\nconst stateA = GA3Lattice.fromScalar(3);\nconst stateB = GA3Lattice.fromScalar(7);\n\nconst joined = stateA.join(stateB);\nconsole.log(joined.dominates(stateA)); // true\nconsole.log(joined.dominates(stateB)); // true\n\n// Component-wise lattice (max of each coefficient)\nconst a = ComponentLattice.fromScalar(5);\nconst b = ComponentLattice.fromScalar(3);\n\nconst max = a.join(b);  // Takes max of each component\nconst min = a.meet(b);  // Takes min of each component\n```\n\n### Delta Synchronization\n\nEfficient state sync using minimal deltas instead of full state transfer.\n\n```typescript\nimport {\n  computeDelta,\n  applyDelta,\n  additiveDelta,\n  DeltaBatch,\n  VectorClock\n} from 'cliffy-tsukoshi';\nimport { scalar } from 'cliffy-tsukoshi';\n\n// Compute delta between states\nconst from = scalar(10);\nconst to = scalar(25);\nconst delta = computeDelta(from, to); // Results in scalar(15)\n\n// Create a delta with causal metadata\nconst fromClock = new VectorClock();\nconst toClock = new VectorClock();\ntoClock.tick('node-1');\n\nconst stateDelta = additiveDelta(delta, fromClock, toClock, 'node-1');\n\n// Apply delta to reconstruct state\nconst newState = applyDelta(from, stateDelta);\nconsole.log(newState[0]); // 25\n\n// Batch multiple deltas for efficiency\nconst batch = new DeltaBatch();\nbatch.push(stateDelta);\n// ... add more deltas\nconst finalState = batch.applyTo(from);\n```\n\n### Storage & Recovery\n\nPersist state with snapshots and operation logs.\n\n```typescript\nimport { MemoryStore, recoverState, VectorClock, additiveDelta } from 'cliffy-tsukoshi';\nimport { scalar } from 'cliffy-tsukoshi';\n\n// Create a store (in-memory for this example)\nconst store = new MemoryStore({\n  maxSnapshots: 10,\n  maxOperationsBeforeCompact: 100,\n  autoCompact: true,\n});\n\n// Save initial snapshot\nconst state = scalar(100);\nconst clock = new VectorClock();\nstore.saveSnapshot(state, clock);\n\n// Append operations\nclock.tick('node-1');\nconst delta = additiveDelta(scalar(50), new VectorClock(), clock, 'node-1');\nstore.appendOperation(delta);\n\n// Later: recover state from storage\nconst result = recoverState(store);\nif (result) {\n  console.log(result.state[0]); // 150\n  console.log(result.operationsReplayed); // 1\n}\n\n// Check stats\nconst stats = store.stats();\nconsole.log(`Snapshots: ${stats.snapshotCount}, Operations: ${stats.operationCount}`);\n```\n\n### Peer-to-Peer Sync\n\nProtocol messages for P2P state synchronization.\n\n```typescript\nimport { SyncState, PeerConnectionState, VectorClock } from 'cliffy-tsukoshi';\n\n// Create sync state for this node\nconst nodeId = crypto.randomUUID();\nconst syncState = new SyncState(nodeId, {\n  heartbeatInterval: 5000,\n  peerTimeout: 30000,\n});\n\n// When a peer connects\nconst peerId = crypto.randomUUID();\nsyncState.registerPeer(peerId, new VectorClock());\n\n// Create protocol messages\nconst hello = syncState.createHello('My App Node');\nconst heartbeat = syncState.createHeartbeat();\nconst deltaRequest = syncState.createDeltaRequest(new VectorClock());\n\n// Send messages via your transport (WebRTC, WebSocket, etc.)\nsendToPeer(peerId, JSON.stringify(hello));\n\n// Handle incoming messages\nfunction onMessageReceived(data: string) {\n  const message = JSON.parse(data);\n  const response = syncState.handleMessage(message);\n  if (response) {\n    sendToPeer(message.sender, JSON.stringify(response));\n  }\n}\n\n// Maintenance: check for stale peers\nconst stalePeers = syncState.stalePeers();\nfor (const peerId of stalePeers) {\n  // Attempt reconnection or remove\n}\n```\n\n### Distributed Consensus\n\nGeometric mean consensus for distributed agreement.\n\n```typescript\nimport { GeometricConsensus, scalar } from 'cliffy-tsukoshi';\n\nconst nodeId = crypto.randomUUID();\nconst consensus = new GeometricConsensus(nodeId, scalar(0));\n\n// Subscribe to outgoing messages\nconsensus.onMessage((message) => {\n  // Broadcast to all peers\n  broadcastToPeers(JSON.stringify(message));\n});\n\n// Propose a value\nconst round = consensus.propose(scalar(42));\n\n// Receive proposals from other nodes\nconsensus.receiveProposal('other-node-id', scalar(38), round);\nconsensus.receiveProposal('another-node-id', scalar(45), round);\n\n// Compute consensus from all proposals\nconst proposals = consensus.getProposals(round);\nconst consensusValue = consensus.geometricConsensus(proposals, 0.1);\nconsole.log('Consensus:', consensusValue[0]); // ~41.67 (geometric mean)\n\n// Vote on the consensus value\nconsensus.vote(round, true, consensusValue);\n\n// Try to commit if we have majority\nconst committed = consensus.tryCommit(round, 3); // 3 participants\nif (committed) {\n  console.log('Round committed!', committed[0]);\n}\n```\n\n### Complete Example: Collaborative Counter\n\n```typescript\nimport {\n  GeometricCRDT,\n  OperationType,\n  SyncState,\n  VectorClock,\n  MemoryStore,\n  additiveDelta,\n  scalar,\n} from 'cliffy-tsukoshi';\n\nclass CollaborativeCounter {\n  private crdt: GeometricCRDT;\n  private syncState: SyncState;\n  private store: MemoryStore;\n  private onUpdate: (value: number) => void;\n\n  constructor(nodeId: string, onUpdate: (value: number) => void) {\n    this.crdt = new GeometricCRDT(nodeId, scalar(0));\n    this.syncState = new SyncState(nodeId);\n    this.store = new MemoryStore();\n    this.onUpdate = onUpdate;\n\n    // Save initial state\n    this.store.saveSnapshot(this.crdt.state, this.crdt.vectorClock);\n  }\n\n  increment(amount: number = 1): void {\n    const op = this.crdt.createOperation(scalar(amount), OperationType.Addition);\n    this.crdt.applyOperation(op);\n    this.notifyUpdate();\n\n    // Store the operation\n    const delta = additiveDelta(\n      scalar(amount),\n      new VectorClock(),\n      this.crdt.vectorClock,\n      this.crdt.nodeId\n    );\n    this.store.appendOperation(delta);\n  }\n\n  getValue(): number {\n    return this.crdt.state[0];\n  }\n\n  // Call when receiving state from another peer\n  merge(otherCrdt: GeometricCRDT): void {\n    this.crdt = this.crdt.merge(otherCrdt);\n    this.notifyUpdate();\n  }\n\n  // Get state to send to peers\n  getState(): GeometricCRDT {\n    return this.crdt;\n  }\n\n  private notifyUpdate(): void {\n    this.onUpdate(this.getValue());\n  }\n}\n\n// Usage\nconst counter = new CollaborativeCounter('node-1', (value) => {\n  console.log('Counter updated:', value);\n});\n\ncounter.increment(5);\ncounter.increment(3);\nconsole.log(counter.getValue()); // 8\n```\n\n## Protocol Reference\n\n### VectorClock\n\n| Method | Description |\n|--------|-------------|\n| `tick(nodeId)` | Increment clock for a node |\n| `update(other)` | Merge with another clock |\n| `happensBefore(other)` | Check causal ordering |\n| `concurrent(other)` | Check if concurrent events |\n| `merge(other)` | Create merged clock |\n| `clone()` | Copy the clock |\n| `toJSON()` / `fromJSON()` | Serialization |\n\n### GeometricCRDT\n\n| Method | Description |\n|--------|-------------|\n| `createOperation(transform, type)` | Create a new operation |\n| `applyOperation(op)` | Apply an operation |\n| `merge(other)` | Merge with another CRDT |\n| `geometricJoin(other)` | Conflict resolution via magnitude |\n| `toJSON()` / `fromJSON()` | Serialization |\n\n### SyncState\n\n| Method | Description |\n|--------|-------------|\n| `registerPeer(id, clock)` | Add a peer |\n| `removePeer(id)` | Remove a peer |\n| `createHello(name?)` | Create hello message |\n| `createHeartbeat()` | Create heartbeat message |\n| `createDeltaRequest(clock)` | Request deltas |\n| `handleMessage(msg)` | Process incoming message |\n| `stalePeers()` | Get list of stale peers |\n\n### GeometricStore\n\n| Method | Description |\n|--------|-------------|\n| `saveSnapshot(state, clock)` | Persist a snapshot |\n| `loadLatestSnapshot()` | Load most recent snapshot |\n| `appendOperation(delta)` | Add operation to log |\n| `operationsSince(clock)` | Get operations after clock |\n| `compact()` | Create snapshot from operations |\n| `stats()` | Get storage statistics |\n| `clear()` | Remove all data |\n\n---\n\n## License\n\nMIT\n\n## Part of Cliffy\n\ncliffy-tsukoshi is the pure TypeScript extraction of geometric state management from [Cliffy](https://github.com/justinelliottcobb/Cliffy), a framework for building collaborative applications using geometric algebra.\n","readmeFilename":"README.md"}