Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ampere-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ kotlin {
val jvmMain by getting {
dependencies {
implementation(project(":ampere-core"))
implementation("link.socket:phosphor-core:0.3.0")
implementation("link.socket:phosphor-core:0.4.0")

// CLI argument parsing
implementation("com.github.ajalt.clikt:clikt:4.4.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import link.socket.phosphor.timeline.PlaybackState
import link.socket.phosphor.timeline.TimelineController
import link.socket.phosphor.timeline.TimelineEvent
import link.socket.phosphor.timeline.WaveformDemoTimeline
import link.socket.ampere.cli.render.AmperePhosphorBridge
import link.socket.ampere.cli.render.WaveformPaneRenderer
import link.socket.ampere.repl.TerminalFactory

Expand Down Expand Up @@ -68,14 +67,11 @@ class WaveformDemoSubcommand : CliktCommand(
val agents = AgentLayer(width, contentHeight, AgentLayoutOrientation.CIRCULAR)
val flow = FlowLayer(width, contentHeight)
val emitters = EmitterManager()
val emitterBridge = AmperePhosphorBridge(emitters)
val substrate = SubstrateState.create(width, contentHeight, baseDensity = 0.2f)

// Create waveform renderer
val waveformPane = WaveformPaneRenderer(
agentLayer = agents,
emitterManager = emitters,
amperePhosphorBridge = emitterBridge
agentLayer = agents
)

// Build timeline and controller
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package link.socket.ampere.cli.animation.render

import link.socket.phosphor.color.AnsiColorAdapter
import link.socket.phosphor.color.CognitiveColorModel
import link.socket.phosphor.color.FlowColorState
import link.socket.phosphor.color.ParticleColorKind
import link.socket.phosphor.choreography.AgentLayer
import link.socket.phosphor.choreography.AgentLayerRenderer
import link.socket.phosphor.field.FlowLayer
Expand All @@ -14,36 +18,40 @@ import link.socket.phosphor.field.SubstrateState
import link.socket.phosphor.math.Vector2
import link.socket.phosphor.render.RenderCell
import link.socket.phosphor.render.RenderLayer
import link.socket.phosphor.signal.AgentActivityState
import kotlin.math.roundToInt

/**
* Color palette for Ampere TUI.
*/
object AmperePalette {
// Substrate colors (256-color ANSI codes)
const val SUBSTRATE_DIM = "\u001B[38;5;239m" // Dark blue-gray
const val SUBSTRATE_MID = "\u001B[38;5;73m" // Teal
const val SUBSTRATE_BRIGHT = "\u001B[38;5;117m" // Cyan
private val colorModel = CognitiveColorModel
private val ansiAdapter = AnsiColorAdapter()

// Substrate colors sampled from Phosphor's shared flow-intensity ramp.
val SUBSTRATE_DIM = ansiAdapter.foreground(colorModel.flowIntensityRamp.sample(0.2f))
val SUBSTRATE_MID = ansiAdapter.foreground(colorModel.flowIntensityRamp.sample(0.55f))
val SUBSTRATE_BRIGHT = ansiAdapter.foreground(colorModel.flowIntensityRamp.sample(0.9f))

// Agent colors
const val AGENT_IDLE = "\u001B[38;5;244m" // Gray
const val AGENT_ACTIVE = "\u001B[38;5;220m" // Gold
const val AGENT_PROCESSING = "\u001B[38;5;208m" // Orange
const val AGENT_COMPLETE = "\u001B[38;5;46m" // Green
val AGENT_IDLE = ansiAdapter.foreground(colorModel.agentActivityColors.getValue(AgentActivityState.IDLE))
val AGENT_ACTIVE = ansiAdapter.foreground(colorModel.agentActivityColors.getValue(AgentActivityState.ACTIVE))
val AGENT_PROCESSING = ansiAdapter.foreground(colorModel.agentActivityColors.getValue(AgentActivityState.PROCESSING))
val AGENT_COMPLETE = ansiAdapter.foreground(colorModel.agentActivityColors.getValue(AgentActivityState.COMPLETE))

// Flow colors
const val FLOW_DORMANT = "\u001B[38;5;239m" // Dark
const val FLOW_ACTIVE = "\u001B[38;5;135m" // Purple
const val FLOW_TOKEN = "\u001B[38;5;226m" // Yellow
val FLOW_DORMANT = ansiAdapter.foreground(colorModel.flowStateColors.getValue(FlowColorState.DORMANT))
val FLOW_ACTIVE = ansiAdapter.foreground(colorModel.flowStateColors.getValue(FlowColorState.ACTIVATING))
val FLOW_TOKEN = ansiAdapter.foreground(colorModel.particleColors.getValue(ParticleColorKind.TRAIL))

// Accent colors
const val SPARK_ACCENT = "\u001B[38;5;203m" // Coral
const val SUCCESS_GREEN = "\u001B[38;5;83m" // Green
const val LOGO_BOLT = "\u001B[38;5;226m" // Bright yellow
const val LOGO_TEXT = "\u001B[38;5;45m" // Cyan
val SPARK_ACCENT = ansiAdapter.foreground(colorModel.particleColors.getValue(ParticleColorKind.SPARK))
val SUCCESS_GREEN = ansiAdapter.foreground(colorModel.agentActivityColors.getValue(AgentActivityState.COMPLETE))
val LOGO_BOLT = ansiAdapter.foreground(colorModel.roleColorFor("reasoning"))
val LOGO_TEXT = ansiAdapter.foreground(colorModel.roleColorFor("coordinator"))

// Reset
const val RESET = "\u001B[0m"
val RESET = AnsiColorAdapter.RESET

/**
* Get substrate color for density value.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package link.socket.ampere.cli.hybrid

import com.github.ajalt.mordant.terminal.Terminal
import link.socket.phosphor.emitter.EmitterManager
import link.socket.phosphor.field.ParticleSystem
import link.socket.ampere.cli.animation.render.AmperePalette
import link.socket.ampere.cli.animation.render.CompositeRenderer
import link.socket.ampere.cli.render.AmperePhosphorBridge
import link.socket.phosphor.field.SubstrateAnimator
import link.socket.phosphor.field.SubstrateGlyphs
import link.socket.phosphor.field.SubstrateState
Expand Down Expand Up @@ -66,9 +64,6 @@ class HybridDashboardRenderer(
private lateinit var bridge: WatchStateAnimationBridge

// 3D waveform pipeline
private lateinit var emitterManager: EmitterManager
private lateinit var amperePhosphorBridge: AmperePhosphorBridge

/**
* The waveform pane renderer for the middle pane. Callers can pass this
* as the `middlePane` parameter to [render] or [renderToBuffer] to display
Expand Down Expand Up @@ -147,25 +142,15 @@ class HybridDashboardRenderer(
maxParticles = config.particleMaxCount
)

// Initialize waveform pipeline
emitterManager = EmitterManager()
amperePhosphorBridge = AmperePhosphorBridge(emitterManager)

if (config.enableWaveform) {
val wfPane = WaveformPaneRenderer(
agentLayer = bridge.agentLayer,
emitterManager = emitterManager,
amperePhosphorBridge = amperePhosphorBridge
agentLayer = bridge.agentLayer
)
waveformPane = wfPane

// Wire cognitive events from bridge to emitter bridge
bridge.onCognitiveEvent = { event, position ->
amperePhosphorBridge.onCognitiveEvent(event, position)
}
bridge.onProviderTelemetry = { telemetry, position ->
amperePhosphorBridge.onProviderCallCompleted(telemetry, position)
}
// Route watch events into the waveform pane's runtime-backed emitter bridge.
bridge.onCognitiveEvent = wfPane::onCognitiveEvent
bridge.onProviderTelemetry = wfPane::onProviderCallCompleted
}

initialized = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package link.socket.ampere.cli.render

import link.socket.phosphor.choreography.AgentLayer
import link.socket.phosphor.choreography.AgentLayoutOrientation
import link.socket.phosphor.coordinate.CoordinateSpace
import link.socket.phosphor.emitter.EmitterManager
import link.socket.phosphor.field.FlowLayer
import link.socket.phosphor.field.FlowState
import link.socket.phosphor.math.Vector2
import link.socket.phosphor.render.Camera
import link.socket.phosphor.render.CognitiveWaveform
import link.socket.phosphor.runtime.CameraOrbitConfiguration
import link.socket.phosphor.runtime.CognitiveSceneRuntime
import link.socket.phosphor.runtime.SceneConfiguration
import link.socket.phosphor.runtime.SceneSnapshot
import link.socket.phosphor.runtime.WaveformConfiguration

/**
* Bridges Ampere's existing watch-state driven layers onto Phosphor's unified
* scene runtime so rendering consumes a single runtime update contract.
*/
class CognitiveSceneRuntimeAdapter {
private var cachedWidth = 0
private var cachedHeight = 0
private var cachedCoordinateSpace: CoordinateSpace? = null
private var runtime: CognitiveSceneRuntime? = null
private var latestSnapshot: SceneSnapshot? = null

val emitterManager: EmitterManager?
get() = runtime?.emitters

val waveform: CognitiveWaveform?
get() = runtime?.waveform

val agents: AgentLayer?
get() = runtime?.agents

fun update(
width: Int,
height: Int,
deltaSeconds: Float,
sourceAgents: AgentLayer,
sourceFlow: FlowLayer?
): SceneSnapshot {
val activeRuntime = ensureRuntime(width, height, sourceAgents.coordinateSpace)
syncAgents(sourceAgents, activeRuntime.agents)
activeRuntime.flow?.let { syncFlow(sourceFlow, it) }
return activeRuntime.update(deltaSeconds).also { latestSnapshot = it }
}

fun currentCamera(): Camera? = runtime?.cameraOrbit?.currentCamera()

fun snapshot(): SceneSnapshot? = latestSnapshot ?: runtime?.snapshot()

private fun ensureRuntime(
width: Int,
height: Int,
coordinateSpace: CoordinateSpace
): CognitiveSceneRuntime {
val needsRebuild = runtime == null ||
width != cachedWidth ||
height != cachedHeight ||
coordinateSpace != cachedCoordinateSpace

if (needsRebuild) {
cachedWidth = width
cachedHeight = height
cachedCoordinateSpace = coordinateSpace
runtime = createRuntime(width, height, coordinateSpace)
latestSnapshot = runtime?.snapshot()
}

return requireNotNull(runtime)
}

private fun createRuntime(
width: Int,
height: Int,
coordinateSpace: CoordinateSpace
): CognitiveSceneRuntime {
val config = SceneConfiguration(
width = width,
height = height,
agents = emptyList(),
initialConnections = emptyList(),
enableWaveform = true,
enableParticles = false,
enableFlow = true,
enableEmitters = true,
enableCamera = true,
coordinateSpace = coordinateSpace,
seed = 0xA63E0L,
agentLayout = AgentLayoutOrientation.CUSTOM,
waveform = WaveformConfiguration(
gridWidth = width.coerceAtMost(60),
gridDepth = height.coerceAtMost(40),
worldWidth = 20f,
worldDepth = 15f
),
cameraOrbit = CameraOrbitConfiguration(
radius = 15f,
height = 8f,
orbitSpeed = 0.08f,
wobbleAmplitude = 0.3f,
wobbleFrequency = 0.2f
)
)
return CognitiveSceneRuntime(config)
}

private fun syncAgents(source: AgentLayer, target: AgentLayer) {
val sourceById = source.allAgents.associateBy { it.id }
val targetIds = target.allAgents.map { it.id }.toSet()

for (id in targetIds - sourceById.keys) {
target.removeAgent(id)
}

for ((id, agent) in sourceById) {
if (target.getAgent(id) == null) {
target.addAgent(agent.copy())
continue
}

target.setAgentPosition(id, agent.position)
target.setAgentPosition3D(id, agent.position3D)
target.updateAgentState(id, agent.state)
target.updateAgentStatus(id, agent.statusText)
target.updateAgentCognitivePhase(id, agent.cognitivePhase, agent.phaseProgress)
}
}

private fun syncFlow(source: FlowLayer?, target: FlowLayer) {
if (source == null) {
if (target.connectionCount > 0) {
target.clear()
}
return
}

val sourceById = source.allConnections.associateBy { it.id }
val targetConnections = target.allConnections

for (connection in targetConnections) {
if (connection.id !in sourceById) {
target.removeConnection(connection.id)
}
}

for (connection in source.allConnections) {
val sourcePos = connection.path.firstOrNull() ?: Vector2.ZERO
val targetPos = connection.path.lastOrNull() ?: sourcePos
val existing = target.getConnection(connection.id)
val resolvedId = existing?.id ?: target.createConnection(
sourceAgentId = connection.sourceAgentId,
targetAgentId = connection.targetAgentId,
sourcePosition = sourcePos,
targetPosition = targetPos
)

target.updateConnectionPath(resolvedId, sourcePos, targetPos)

if (connection.state == FlowState.TRANSMITTING) {
target.startHandoff(resolvedId)
}
}
}
}
Loading