MCCF X3D PROTO Integration Reference

 


MCCF X3D PROTO Integration Reference

Connecting the Multi-Channel Coherence Field to X3D Scenes

Version: 2.0 — April 2026
Repository: https://github.com/artistinprocess/mccf
Scene file: static/mccf_scene.x3d
Proto library: static/protos/
Loader: static/mccf_x3d_loader.html


Overview

The MCCF produces a live affective field: coherence scores between agents, emotional channel states, honor levels, zone pressures, and global tension. The X3D scene is the visual projection of that field.

The connection between them is a five-PROTO library. Each PROTO is a composable X3D object with a defined interface — a set of named fields that the MCCF API drives. The HTML loader polls the API at regular intervals and sets those fields.

MCCF Flask API
    │
    ├── /hothouse/x3d  ──► applyHotHouseData()  ──► MCCFAgent fields
    ├── /field         ──► applyFieldData()      ──► MCCFChannel fields
    │                                            ──► MCCFField fields
    ├── /scene/pressure ─► (future)              ──► MCCFZone fields
    │
    └── /voice/speak   ──► mccf_x3d_projection   ──► MCCFAgent fields
                           (custom event)

The PROTOs know nothing about the MCCF internals. They expose scalar fields that any data source can drive. The MCCF is the default driver, but any system that produces values in the correct ranges can drive the same PROTOs.


File Structure

static/
  protos/
    MCCFAgent.x3d         ← avatar with HotHouse parameter interface
    MCCFChannel.x3d       ← directed coherence relationship line
    MCCFTransition.x3d    ← smooth interpolated state transitions
    MCCFZone.x3d          ← constitutional arc waypoint marker
    MCCFField.x3d         ← global coherence field visualizer (S0)
  mccf_scene.x3d          ← scene using EXTERNPROTOs
  mccf_x3d_loader.html    ← HTML loader with polling loop
  mccf_scene_v2.x3d       ← backup of previous static scene

PROTO 1 — MCCFAgent

File: static/protos/MCCFAgent.x3d
Purpose: Composable avatar whose visual state reflects a live MCCF agent.
MCCF data source: GET /hothouse/x3d → agent name key → parameter dict

When to use

Instantiate one MCCFAgent for each registered MCCF agent. The DEF name must match the pattern Avatar_[AgentName] with spaces replaced by underscores so the JavaScript polling loop can find it by name.

Interface

Field Type Access Default Source
agentName SFString initializeOnly "Agent" Set at instantiation
position SFVec3f inputOutput 0 0 0 Set at instantiation; update for movement
bodyColor SFColor initializeOnly 0.3 0.55 0.9 Set at instantiation (cultivar color)
morphWeight_emotion SFFloat inputOutput 0.5 /hothouse/x3d → E channel
animationSpeed SFFloat inputOutput 0.5 /hothouse/x3d → B channel
gazeDirectness SFFloat inputOutput 0.5 /hothouse/x3d → P channel
socialProximity SFFloat inputOutput 0.5 /hothouse/x3d → S channel
gestureConfidence SFFloat inputOutput 0.5 /hothouse/x3dgestureConfidence
interactionOpenness SFFloat inputOutput 1.0 /hothouse/x3dinteractionOpenness
honorLevel SFFloat inputOutput 0.5 Future: /field alignment_coherence
coherenceLevel SFFloat inputOutput 0.5 /field matrix row average
behavioralMode SFString inputOutput "exploit" /field agent meta_state.mode

Channel-to-visual mapping

Field Visual Effect Range
morphWeight_emotion Body emissive glow intensity — warmer as E rises 0.0–1.0
animationSpeed Reserved for future TimeSensor rate modulation 0.0–1.0
gazeDirectness GazeIndicator sphere offset — facing scene vs. inward 0.0–1.0
socialProximity Floor ring radius — larger social field as S rises 0.0–1.0
gestureConfidence HonorMat transparency inverse — brighter when confident 0.0–1.0
interactionOpenness RingMat transparency — ring visible when open 0.0–1.0
honorLevel HonorMat gold sphere brightness 0.0–1.0
coherenceLevel Used by MCCFChannel but surfaced here for external logic 0.0–1.0

Body structure

AgentRoot (Transform)
  └── SocialRing        — Cylinder, floor disc, driven by socialProximity
  └── Body              — Cylinder, cultivar color, emissive driven by morphWeight_emotion
  └── Head              — Sphere, neutral skin tone
  └── HonorDot          — Sphere (gold), driven by gestureConfidence/honorLevel
  └── GazeIndicator     — Sphere (white), offset driven by gazeDirectness
  └── NameLabel         — Billboard Text, agentName string

Example instantiation

<MCCFAgent DEF="Avatar_Steward"
           agentName="The Steward"
           position="-4 0 15"
           bodyColor="0.30 0.55 0.90"/>

JavaScript update pattern

const node = scene.getNamedNode('Avatar_Steward');
const params = hotHouseData['The Steward'];
node.morphWeight_emotion = params.morphWeight_emotion;
node.gazeDirectness      = params.gazeDirectness;
node.socialProximity     = params.socialProximity;
node.gestureConfidence   = params.gestureConfidence;
node.interactionOpenness = params.interactionOpenness;

Extension points for character library

The bodyColor field is initializeOnly — set once at character load time. To create a plug-in character with a different mesh (H-Anim humanoid, stylized avatar, abstract shape), replace the ProtoBody cylinder/sphere structure with:

<Inline url='"../characters/MyCharacter.x3d"'/>

All interface fields remain unchanged. The character library provides the visual; the MCCF drives the behavior through the same field names.


PROTO 2 — MCCFChannel

File: static/protos/MCCFChannel.x3d
Purpose: Directed coherence relationship line between two agents.
MCCF data source: GET /fieldmatrix[fromAgent][toAgent]

When to use

Instantiate one MCCFChannel for each directed agent pair you want to visualize. The coherence matrix is asymmetric — A→B and B→A are different relationships — so you may instantiate one channel per direction or one bidirectional channel per pair depending on the scene design.

The current scene uses one channel per undirected pair (three channels for three agents), using the from→to direction as the primary measurement.

Interface

Field Type Access Default Source
fromAgent SFString initializeOnly "" Set at instantiation
toAgent SFString initializeOnly "" Set at instantiation
fromPos SFVec3f inputOutput -2 1.5 0 Agent position at chest height
toPos SFVec3f inputOutput 2 1.5 0 Agent position at chest height
coherence SFFloat inputOutput 0.0 /field matrix value
entanglement SFFloat inputOutput 0.0 /field entanglement_negativity
channelColor SFColor initializeOnly 0.6 0.55 0.8 Set at instantiation (pair identity)

Coherence-to-visual mapping

Value Visual Interpretation
0.0–0.2 Near invisible (transparency 0.85) No established relationship
0.2–0.5 Faintly visible Forming relationship
0.5–0.7 Clearly visible Established relationship
0.7–1.0 Bright and solid (transparency 0.1) Deep coherence

Transparency formula: transparency = max(0.1, 1.0 - coherence * 0.85)

Visual structure

ChannelLine  — IndexedLineSet between fromPos and toPos
ArrowHead    — Sphere near toPos indicating direction
ChannelLabel — Billboard showing coherence score (updates via JavaScript)

Color convention for the three default pairs

Pair channelColor Interpretation
Steward ↔ Archivist 0.6 0.55 0.8 Purple — duty meets memory
Steward ↔ Witness 0.35 0.7 0.85 Blue-cyan — duty meets truth
Archivist ↔ Witness 0.65 0.75 0.35 Yellow-green — memory meets observation

Example instantiation

<MCCFChannel DEF="Ch_SA"
             fromAgent="The Steward"
             toAgent="The Archivist"
             fromPos="-4 1.5 15"
             toPos="4 1.5 15"
             channelColor="0.6 0.55 0.8"/>

JavaScript update pattern

function updateChannelNode(scene, def, matrix, from, to) {
  const node = scene.getNamedNode(def);
  if (!node) return;
  const coh = (matrix[from] || {})[to] || 0.0;
  node.coherence = coh;
}
updateChannelNode(scene, 'Ch_SA', fieldData.matrix, 'The Steward', 'The Archivist');

Important X3D constraint

IndexedLineSet does not support per-vertex transparency. Line opacity is controlled by setting the ChannelMat Material transparency field directly from JavaScript. The PROTO exposes the coherence field; the loader is responsible for translating it to Material transparency.


PROTO 3 — MCCFTransition

File: static/protos/MCCFTransition.x3d
Purpose: Smooth interpolated visual transitions when field state changes.
MCCF data source: TimeSensor.fraction_changed (driven by scene logic)

When to use

Use MCCFTransition whenever you need smooth color or scalar interpolation driven by a time event — waypoint step transitions, coherence level changes, or collapse events. It is a utility PROTO, not directly driven by the API.

Important: Route TimeSensor.fraction_changed to MCCFTransition.fraction, not TimeSensor.cycleTime. cycleTime fires once per cycle completion (an SFTime event). fraction_changed provides a continuous 0.0→1.0 signal that drives smooth interpolation.

Interface

Field Type Access Default Purpose
fraction SFFloat inputOnly Drive from TimeSensor.fraction_changed
fromColor SFColor initializeOnly 0.5 0.5 0.5 Starting color
toColor SFColor initializeOnly 1.0 0.8 0.2 Target color
colorOut SFColor outputOnly Route to Material.diffuseColor
fromValue SFFloat initializeOnly 0.5 Starting scalar
toValue SFFloat initializeOnly 1.0 Target scalar
valueOut SFFloat outputOnly Route to any SFFloat field
transitionType SFString initializeOnly "linear" "linear" or "pulse"

transitionType values

  • "linear": Direct interpolation. Equal speed throughout. Use for data-driven transitions where the raw value is what matters.
  • "pulse": Smooth-step ease-in-out (f² × (3 - 2f)). Use for waypoint transitions and collapse events where a natural feel matters.

Example: Waypoint color transition

<TimeSensor DEF="WaypointClock" cycleInterval="2.0" loop="false"/>

<MCCFTransition DEF="T_W1_W2"
                fromColor="0.9 0.9 0.9"
                toColor="0.9 0.85 0.5"
                transitionType="pulse"/>

<ROUTE fromNode="WaypointClock" fromField="fraction_changed"
       toNode="T_W1_W2"         toField="fraction"/>

<ROUTE fromNode="T_W1_W2"       fromField="colorOut"
       toNode="Zone_W1"         toField="set_diffuseColor"/>

Example: Scalar fade

<MCCFTransition DEF="FadeOut"
                fromValue="1.0"
                toValue="0.0"
                transitionType="linear"/>

<ROUTE fromNode="FadeClock"  fromField="fraction_changed"
       toNode="FadeOut"      toField="fraction"/>

<ROUTE fromNode="FadeOut"    fromField="valueOut"
       toNode="SomeMaterial" toField="transparency"/>

Internal structure

The PROTO contains a ColorInterpolator, a ScalarInterpolator, and an ECMAScript bridge that receives fraction, applies the easing curve if pulse mode is selected, and drives both interpolators. The outputs colorOut and valueOut are exposed for external routing.


PROTO 4 — MCCFZone

File: static/protos/MCCFZone.x3d
Purpose: Constitutional arc waypoint marker that responds to live zone pressure.
MCCF data source: GET /scene/pressure and GET /zone/[name]

When to use

Instantiate one MCCFZone for each waypoint in the constitutional arc. The seven default waypoints (W1–W7) are already instantiated in mccf_scene.x3d. Additional zones can be added for custom arc sequences.

Interface

Field Type Access Default Source
waypointId SFString initializeOnly "W0" Set at instantiation
position SFVec3f inputOutput 0 0 0 Set at instantiation
pressure SFFloat inputOutput 0.0 /scene/pressure or /zone/[name]
active SFBool inputOutput false Constitutional arc current waypoint
baseColor SFColor initializeOnly 0.9 0.9 0.9 Set at instantiation (waypoint identity)
zoneType SFString initializeOnly "stable" "stable", "transitional", "integration"

Pressure-to-visual mapping

Pressure Visual Effect
0.0 Marker sphere at base opacity; pressure disc minimal
0.0–0.3 Low glow; pressure disc small
0.3–0.6 Moderate glow; pressure disc expands
0.6–1.0 Intense glow; full disc visible

The pressure disc radius scales via JavaScript: scale = 0.5 + pressure × 1.5

When active = true, the ActiveRingMat transparency drops from 0.85 to 0.3, making the activation ring visible. This indicates the current waypoint in the constitutional arc.

Visual structure

ZoneRoot (Transform)
  └── PressureDisc    — Cylinder, radius driven by pressure
  └── MarkerSphere    — Sphere at waypoint position
  └── ActiveRing      — Cylinder ring, visible when active=true
  └── WaypointLabel   — Billboard showing waypointId
  └── PressureLabel   — Billboard showing live pressure value

Constitutional arc color scheme

Waypoint baseColor Zone character
W1 0.9 0.9 0.9 White — comfort, low pressure
W2 0.9 0.85 0.5 Pale yellow — gentle friction
W3 0.4 0.9 0.95 Cyan — mirror, self-reflection
W4 0.95 0.55 0.2 Orange — pushback, mid pressure
W5 0.9 0.25 0.25 Red — rupture, maximum pressure
W6 0.7 0.4 0.95 Violet — recognition, release
W7 1.0 0.85 0.2 Gold — integration, completion

Example instantiation

<MCCFZone DEF="Zone_W4"
          waypointId="W4"
          position="0 0 20"
          baseColor="0.95 0.55 0.2"
          zoneType="transitional"/>

JavaScript activation pattern

// When constitutional arc steps to W4:
const zone = scene.getNamedNode('Zone_W4');
zone.active   = true;
zone.pressure = 0.55;  // from /zone/W4_PUSHBACK preset

// Deactivate previous waypoint:
const prev = scene.getNamedNode('Zone_W3');
prev.active = false;

PROTO 5 — MCCFField

File: static/protos/MCCFField.x3d
Purpose: Global coherence field visualizer at the S0 reference origin.
MCCF data source: GET /field — alignment_coherence, echo_chamber_risks, episode_count

When to use

Instantiate once per scene at the S0 Field Origin position (Z=20 in the default arc, the center of maximum tension corresponding to W4 Pushback). This PROTO represents the governing field itself — not an agent or a zone — and gives the viewer a continuous readout of the field's global state.

Interface

Field Type Access Default Source
position SFVec3f inputOutput 0 0.05 20 Set at instantiation
globalCoherence SFFloat inputOutput 0.5 /field alignment_coherence.global_coherence
tensionLevel SFFloat inputOutput 0.0 Computed: (1-coh)×0.6 + echo×0.4
echoRisk SFFloat inputOutput 0.0 /field echo_chamber_risks (max)
collapseImminent SFBool inputOutput false Future: arbitration engine threshold
episodeCount SFInt32 inputOutput 0 /field episode_count

Visual structure

FieldRoot (Transform)
  └── InnerRing        — Cylinder, brightness = globalCoherence
  └── OuterRing        — Cylinder, opacity = echoRisk (echo chamber indicator)
  └── TensionDisc      — Cylinder, color shifts cool→red with tensionLevel
  └── CollapseFlash    — Cylinder, invisible normally, red flash on collapse
  └── FieldLabel       — Billboard "S0 Field Origin"
  └── CoherenceReadout — Billboard showing live coherence value
  └── TensionReadout   — Billboard showing live tension value

Tension color scheme

The TensionDisc color is the most immediately readable field health indicator:

Tension Color Interpretation
0.0–0.3 Cool blue 0.3 0.5 0.8 Coherent, low stress
0.3–0.6 Cyan 0.3 0.7 0.7 Building, moderate
0.6–0.8 Orange 0.8 0.5 0.2 High stress, watch for drift
0.8–1.0 Red 0.9 0.2 0.2 Critical tension, arbitration may trigger

Tension formula

The tensionLevel is not directly from the API — it is computed from two API values:

const tension = Math.min(1.0, (1.0 - globalCoherence) * 0.6 + echoRisk * 0.4);

This compound metric rises when:

  • Coherence is low (agents are not tracking each other)
  • Echo risk is high (agents are over-synchronized and rigid)

Both conditions are danger states. The formula weights coherence loss more heavily (0.6) than echo risk (0.4) because coherence collapse is the faster failure mode.

Example instantiation

<MCCFField DEF="GlobalField" position="0 0.05 20"/>

JavaScript update pattern

const fn = scene.getNamedNode('GlobalField');
fn.globalCoherence = coh;
fn.echoRisk        = echo;
fn.tensionLevel    = (1.0 - coh) * 0.6 + echo * 0.4;
fn.episodeCount    = fieldData.episode_count;
// collapseImminent: set true when V2 ArbitrationEngine fires

EXTERNPROTO Registry

The scene file (mccf_scene.x3d) declares all five PROTOs at the top:

<ExternProtoDeclare name="MCCFAgent"
  url='"protos/MCCFAgent.x3d#MCCFAgent"'/>

<ExternProtoDeclare name="MCCFChannel"
  url='"protos/MCCFChannel.x3d#MCCFChannel"'/>

<ExternProtoDeclare name="MCCFTransition"
  url='"protos/MCCFTransition.x3d#MCCFTransition"'/>

<ExternProtoDeclare name="MCCFZone"
  url='"protos/MCCFZone.x3d#MCCFZone"'/>

<ExternProtoDeclare name="MCCFField"
  url='"protos/MCCFField.x3d#MCCFField"'/>

The URLs are relative to the scene file location, so protos/ must be a sibling directory of mccf_scene.x3d. Both are served from static/ by Flask, so the paths resolve correctly when the loader fetches the scene via http://localhost:5000/static/mccf_scene.x3d.


The Polling Loop Architecture

The loader (mccf_x3d_loader.html) runs two independent polling loops:

Loop 1 — Field polling (/field, every 750ms)

Drives: MCCFField, MCCFChannel

/field response
  │
  ├── alignment_coherence.global_coherence → MCCFField.globalCoherence
  ├── echo_chamber_risks (max)             → MCCFField.echoRisk
  ├── episode_count                        → MCCFField.episodeCount
  ├── (computed tension)                   → MCCFField.tensionLevel
  └── matrix[A][B]                         → MCCFChannel.coherence (per pair)

Loop 2 — HotHouse polling (/hothouse/x3d, every 1000ms)

Drives: MCCFAgent

/hothouse/x3d response  (keyed by agent name)
  │
  ├── morphWeight_emotion  → MCCFAgent.morphWeight_emotion
  ├── animationSpeed       → MCCFAgent.animationSpeed
  ├── gazeDirectness       → MCCFAgent.gazeDirectness
  ├── socialProximity      → MCCFAgent.socialProximity
  ├── gestureConfidence    → MCCFAgent.gestureConfidence
  └── interactionOpenness  → MCCFAgent.interactionOpenness

Event-driven update (voice interface)

When the voice interface completes an LLM response, it dispatches:

window.dispatchEvent(new CustomEvent('mccf_x3d_projection',
  { detail: hotHouseProjection }));

The loader listens for this event and calls applyHotHouseData() immediately, without waiting for the next poll interval. This means a voice conversation visually updates the avatars in near-real-time.

MCCFZone update (not yet automated)

Zone pressure and active state are not yet polled automatically. They are set manually during constitutional arc navigation. Adding /scene/pressure polling to the loader is the next integration step for V2.1.


Adding a New Character

To add a fourth cultivar avatar to the scene:

1. Register the agent in the MCCF field:

POST /agent
{"name": "The Arbiter", "weights": {"E":0.2,"B":0.3,"P":0.3,"S":0.2}}

2. Add MCCFAgent instance to mccf_scene.x3d:

<MCCFAgent DEF="Avatar_Arbiter"
           agentName="The Arbiter"
           position="8 0 20"
           bodyColor="0.8 0.3 0.5"/>

3. Add MCCFChannel instances for relationships with existing agents:

<MCCFChannel DEF="Ch_ArS"
             fromAgent="The Arbiter"
             toAgent="The Steward"
             fromPos="8 1.5 20"
             toPos="-4 1.5 15"
             channelColor="0.8 0.4 0.4"/>

4. No JavaScript changes needed. The polling loops use Object.entries(agentDefs) keyed to agent name. Add the new agent to the agentDefs mapping in the loader:

const defs = {
  'The Steward':   'Avatar_Steward',
  'The Archivist': 'Avatar_Archivist',
  'The Witness':   'Avatar_Witness',
  'The Arbiter':   'Avatar_Arbiter'   // ← add this
};

Creating a Custom Character PROTO

The MCCFAgent ProtoBody uses a cylinder body and sphere head as placeholders. To substitute a full H-Anim humanoid or custom mesh:

1. Create a character file at static/characters/MyCharacter.x3d that exports a named Transform CharRoot with your geometry inside it.

2. Create a character PROTO that exposes the same field names as MCCFAgent but with your geometry in the ProtoBody:

<ProtoDeclare name="MyCharacter">
  <ProtoInterface>
    <!-- Same fields as MCCFAgent -->
    <field name="morphWeight_emotion" accessType="inputOutput"
           type="SFFloat" value="0.5"/>
    <!-- ... all other MCCFAgent fields ... -->
  </ProtoInterface>
  <ProtoBody>
    <!-- Your geometry here -->
    <Inline url='"../characters/MyCharacter.x3d"'/>
    <!-- Internal ROUTEs mapping fields to morph targets, bone rotations, etc. -->
  </ProtoBody>
</ProtoDeclare>

3. Register it as an EXTERNPROTO alongside the standard ones in the scene:

<ExternProtoDeclare name="MyCharacter"
  url='"protos/MyCharacter.x3d#MyCharacter"'/>

4. Use it in the scene exactly as you would MCCFAgent — same field names, same JavaScript update pattern, same API data source.

The MCCF does not care what the character looks like. It drives the interface. The character library defines how the interface manifests visually.


PROTO Responsibility Summary

PROTO Driven by Updates Frequency
MCCFAgent /hothouse/x3d + voice events Avatar channels → visual state 1000ms + on-demand
MCCFChannel /field matrix Coherence → line opacity 750ms
MCCFTransition TimeSensor Color/scalar interpolation Continuous (event-driven)
MCCFZone Manual / future /scene/pressure Waypoint pressure + active Arc step events
MCCFField /field coherence + echo Global field health display 750ms

Relationship to V2 Architecture

The five PROTOs map directly onto the V2 theoretical layers:

V2 Concept PROTO Implementation
Character state vector ψᵢ MCCFAgent fields (morphWeight_emotion through socialProximity)
Coherence R_ij MCCFChannel.coherence
Semantic collapse event MCCFTransition (triggered on collapse pipeline completion)
Zone pressure operators MCCFZone.pressure
Mother Goddess / ArbitrationEngine MCCFField.tensionLevel + collapseImminent

When the V2 ArbitrationEngine is implemented, it will set MCCFField.collapseImminent = true when tension exceeds the threshold θ, triggering the CollapseFlash visual and signaling the LLM layer to complete the irreversible state deformation.


MCCF X3D PROTO Integration Reference v2.0 — April 2026
Len Bullard / Claude Sonnet 4.6

Comments

Popular posts from this blog

To Hear The Mockingbird Sing: Why Artists Must Engage AI

Schenkerian Analysis, HumanML and Affective Computing

On Integrating A Meta Context Layer to the Federated Dialog Model