Atletica by Threekit SDK

Modular SDK for integrating the Atletica R8 Power Rack configurator into your application. Each module is self-contained and can be used independently.

Quick Start

1
Include the core SDK
<script src="atletica-sdk.js"></script>
2
Initialize with your configuration
const sdk = AtleticaSDK.init({ containerId: 'player' });
3
Use the SDK modules
await sdk.threekit.loadPlayer();
sdk.configurator.setAttribute('crossbar', '78');
Core SDK
atletica-sdk.js
Main entry point and orchestrator. Initializes all services, manages state, and provides the public API. Includes the Threekit player loader, configurator state machine, and event system.
atletica-sdk.js
Configuration Data
atletica-config-data.js
Product data model — all attributes, options, rack sizes, validation rules, and Threekit mappings. This is the data layer generated from the Excel file. Modify this to change the product configuration.
Usage
// Access configuration data
const config = AtleticaSDK.getConfig();

// Get all attributes
const attrs = config.attributes;

// Get rack size definitions
const rackSizes = config.rackSizes;

// Get validation rules
const rules = config.rules;

// Get Threekit mapping
const mapping = config.threekit.attributeMap;
Threekit 3D Service
atletica-threekit.js
Handles loading and controlling the Threekit 3D player. Includes attribute discovery, value resolution (text → assetId), and setConfiguration calls. Manages the full player lifecycle.
Usage
// Initialize the 3D player
await sdk.threekit.loadPlayer('#player-container');

// Get discovered attributes
const tkAttrs = sdk.threekit.getDiscoveredAttributes();

// Sync current configuration to 3D model
await sdk.threekit.syncConfiguration(currentState);

// Resolve a text value to Threekit format
const resolved = sdk.threekit.resolveValue('Uprights_Color', 'Black');
// → { assetId: "abc-123-..." }

// Take a snapshot
const imageUrl = await sdk.threekit.snapshot({ width: 1920, height: 1080 });

// Get the player instance for advanced usage
const player = sdk.threekit.getPlayer();
const configurator = sdk.threekit.getConfigurator();

// Listen for Threekit events
sdk.threekit.on('playerReady', () => console.log('Player loaded!'));
sdk.threekit.on('attributesDiscovered', (attrs) => { /* ... */ });
Configurator Engine
atletica-configurator.js
State management and business logic. Handles attribute selection, validation, rack-size-dependent logic, cable system limits, and configuration state. This is the "brain" of the configurator.
Usage
// Set an attribute value
sdk.configurator.setAttribute('crossbar', '78');

// Toggle an attribute (Yes/No, 0/1)
sdk.configurator.toggleAttribute('smith_machine');

// Step a stepper attribute (respects step size, e.g. step:2 for cable pairs)
sdk.configurator.stepAttribute('weight_stack_amount', +1);
// → jumps by 2 (0→2→4) because weight stacks come in pairs

// Get current state
const state = sdk.configurator.getState();
// → { crossbar: '78', uprights_color: 'Black', ... }

// Get current rack definition
const rackDef = sdk.configurator.getCurrentRackDef();
// → { id: '2x2', uprights: 4, crossbarPositions: [...], ... }

// Check if an attribute is available for current rack
const available = sdk.configurator.isAttributeAvailable('j_hooks_type3');
// → false (only on 2x3+)

// Validate current configuration
const errors = sdk.configurator.validate();
// → [] or [{message: '...', rule: '...'}]

// Get max cable systems for current rack size
const maxSystems = sdk.configurator.getMaxCableSystems();
// → 2 (for 2x2) or 4 (for 2x3/2x4)

// Get the text value for Threekit (resolves stepper/option → text)
const textVal = sdk.configurator.getTextValue('crossbar');
// → '78'

// inactiveValue: returns 'N/A' when equipment is off
const posVal = sdk.configurator.getTextValue('weight_stack1_position');
// → 'N/A' (when weight_stack_amount < 2)
// → 'DCL2' (when weight_stack_amount ≥ 2)

// Subscribe to state changes
sdk.configurator.on('stateChange', (newState, changedKey) => {
    console.log('State updated:', changedKey, newState[changedKey]);
});

sdk.configurator.on('validationError', (errors) => {
    errors.forEach(e => showToast(e.message));
});
Configuration Rules Reference
All rules derived from Excel and enforced at runtime
These rules are automatically derived from the Excel data and enforced by the ConfiguratorEngine. They cover position logic, limits, pairing, dependencies, and auto-switching.
Rules Summary
// ═══════════════════════════════════════════════════════
// 1. POSITION PAIRING (L↔R Mirror)
// ═══════════════════════════════════════════════════════
// When Slot 1 (left) is set, Slot 2 (right) auto-mirrors:
//   DCL2 ↔ DCR2, DCL3B ↔ DCR3B, DL2 ↔ DR2
// Applies to: Weight_Stack, Plate_Loaded
// Mirror regex: pos.replace(/([D]C?)([LR])/, swap L↔R)

// ═══════════════════════════════════════════════════════
// 2. CROSSBAR SIZE → POSITION HEIGHT FILTERING (NEW)
// ═══════════════════════════════════════════════════════
// The crossbar size controls which height levels are valid:
//   42cm  → level 2 only
//   78cm  → levels 2, 3
//   108cm → levels 2, 3, 4
// Applies to ALL position attributes (WS, PL, Smith).
// On 2x3, front and back crossbars can differ — front CB
// controls front positions, back CB controls back positions.
// Enforced in: _clampPositionsForCrossbar(), renderButtonGroup().

// ═══════════════════════════════════════════════════════
// 3. RACK-SIZE POSITION FILTERING (optionsByRack)
// ═══════════════════════════════════════════════════════
// Different rack sizes offer different positions:
//   2x2: DCL2/3/4, DCR2/3/4 (center only)
//   2x3: + DCL2B/3B/4B, DCR2B/3B/4B (adds back row)
//   2x4: DL2, DR2, DCL2, DCR2 (outer + inner)
// Smith: 2x2→DC2/3/4, 2x3→+DC2B/3B/4B, 2x4→DC3 only

// ═══════════════════════════════════════════════════════
// 4. RACK-SIZE MAX (rackSizeMax)
// ═══════════════════════════════════════════════════════
// Weight Stack: 2x2=2, 2x3=2, 2x4=4 (supports 4 units)
// Plate Loaded: 2x2=2, 2x3=2, 2x4=2 (always max 2)
// Combined cable systems: 2x2=2, 2x3=4, 2x4=4

// ═══════════════════════════════════════════════════════
// 5. STEP SIZE
// ═══════════════════════════════════════════════════════
// Cable systems increment by 2 (pairs): 0 → 2 → 4
// Other steppers increment by 1: 0 → 1 → 2 → 3 → 4

// ═══════════════════════════════════════════════════════
// 6. DEPENDENCY + INACTIVE VALUE (dependsOn)
// ═══════════════════════════════════════════════════════
// Position attributes send N/A when their parent is off:
//   weight_stack1_position → dependsOn: weight_stack_amount ≥ 2
//   smith_position         → dependsOn: smith_machine = Yes
//   spotter_arms_position  → dependsOn: spotter_arms = Yes

// ═══════════════════════════════════════════════════════
// 7. FIXED POSITIONS
// ═══════════════════════════════════════════════════════
// Spotter Arms, J-Hooks → always D1 (no user choice)
// Weight Pins, Dip Station → always N/A (no position data)

// ═══════════════════════════════════════════════════════
// 8. WEIGHT_SYSTEM AUTO-SWITCH
// ═══════════════════════════════════════════════════════
// plate_loaded_amount > 0 → Weight_System = "Plate Loaded"
// else                    → Weight_System = "95kg"

// ═══════════════════════════════════════════════════════
// 9. 2x4 FOUR-UNIT POSITION LAYOUT
// ═══════════════════════════════════════════════════════
// WS amount=2: WS1→DL2, WS2→DR2 (outer slots)
// WS amount=4: WS1→DL2, WS2→DCL2, WS3→DCR2, WS4→DR2
// All 2x4 positions are fixed (single option per slot)

// ═══════════════════════════════════════════════════════
// 10. MUTUAL EXCLUSION
// ═══════════════════════════════════════════════════════
// J-Hooks Type 1 and Type 4 cannot both be active

// ═══════════════════════════════════════════════════════
// 11. WS ↔ PL MUTUAL EXCLUSIVITY
// ═══════════════════════════════════════════════════════
// Weight Stacks and Plate Loaded are NEVER both active.
// They represent different cable system types occupying
// the same physical positions on the rack.
// Excel data: 0 rows across ALL sheets have both WS>0 AND PL>0.
// Enforced in: stepAttribute() auto-resets the other to 0.

// ═══════════════════════════════════════════════════════
// 12. SMITH ↔ CABLE POSITION CONFLICT
// ═══════════════════════════════════════════════════════
// Cable systems cannot occupy the same slot (height + bay)
// as the Smith Machine. The slot = height number + B suffix.
//   Smith=DC2 → exclude DCL2/DCR2 from cable options
//   Smith=DC3 → exclude DCL3/DCR3
//   Smith=DC2B → exclude DCL2B/DCR2B (2x3 back bay)
// Slot extraction: pos.match(/(\d+B?)$/) → "2", "3B", etc.
// Enforced in: _applyAutoRules(), validate(), UI filtering.

// ═══════════════════════════════════════════════════════
// All rules are enforced in ConfiguratorEngine:
//   - setAttribute()  → position pairing
//   - stepAttribute() → rackSizeMax, step, combined max, WS/PL exclusivity
//   - _applyAutoRules() → Weight_System, 2x4 positions, smith conflict, CB height
//   - _clampPositionsForCrossbar() → crossbar height filtering
//   - getTextValue()  → inactiveValue, optionsByRack
//   - validate()      → all CONFIG.rules (incl. position_conflict, crossbar_height)
// ═══════════════════════════════════════════════════════
Events System
Built into Core SDK
Pub/sub event system for decoupled communication between modules and your UI code. All SDK modules emit events that you can listen to for building reactive UIs.
Available Events
// ═══ CONFIGURATOR EVENTS ═══
sdk.on('stateChange', (state, changedKey) => { });
sdk.on('validationError', (errors) => { });
sdk.on('rackSizeChange', (newRackDef) => { });

// ═══ THREEKIT EVENTS ═══
sdk.on('playerLoading', () => { });
sdk.on('playerReady', () => { });
sdk.on('playerError', (error) => { });
sdk.on('attributesDiscovered', (attrs) => { });
sdk.on('configurationSynced', (tkConfig) => { });

// ═══ UNSUBSCRIBE ═══
const handler = (state) => console.log(state);
sdk.on('stateChange', handler);
sdk.off('stateChange', handler);
UI Rendering Helpers
atletica-ui-helpers.js
Optional helper functions for rendering configurator UI components. Use these as reference for building your own React/Vue/Angular components, or use directly in vanilla JS.
Usage
// Render a button group for an attribute
AtleticaUI.renderButtonGroup('crossbar', document.getElementById('crossbar-el'));

// Render color swatches
AtleticaUI.renderColorSwatches('uprights_color', container);

// Render a stepper control
AtleticaUI.renderStepper('weight_stack_amount', container);

// Render toggle cards
AtleticaUI.renderToggle('smith_machine', container);

// Render configuration summary
AtleticaUI.renderSummary(container);

// Render technical specs
AtleticaUI.renderSpecs(container);

// Auto-render all configurator controls
AtleticaUI.renderAll(document.getElementById('config-panel'));
ATLAS AI Agent Service
REST API
AI-powered rack design assistant exposed as a server-side service. All AI logic runs server-side — the frontend is a thin UI client. Responses are streamed via SSE for low-latency UX.
POST /api/atletica/atlas/sessions
// Create a new chat session. Returns a session_id to use in subsequent calls.

const res = await fetch('/api/atletica/atlas/sessions', { method: 'POST' });
const { session_id } = await res.json();

// Response:
// { "session_id": "atlas_m3k9x2_a7b4c1" }
POST /api/atletica/atlas/chat
// Send a user message. Returns an SSE stream with text deltas,
// tool activity events, configuration updates, and a done event.

const res = await fetch('/api/atletica/atlas/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        session_id: 'atlas_m3k9x2_a7b4c1',
        message: 'I want to build muscle',
        configurator_state: {
            rack_size: '2x2',
            crossbar: '108',
            uprights_color: 'Black',
            // ... all current attribute values
            _rackSize: '2x2',
            _crossbar: '108',
            _attributesMeta: { /* per-attribute type, options, min/max */ }
        }
    })
});

// SSE Events:
//   event: delta         → { "text": "chunk of text" }
//   event: tool_activity → { "tools": ["update_configuration"] }
//   event: updates       → [{ "attribute": "weight_stack_amount", "value": "2" }]
//   event: done          → { "session_id": "...", "message": "full text", "updates": [...] }
//   event: error         → { "error": "description" }
System Prompt Management
// GET  /api/atletica/atlas/system-prompt   — Read current prompt
// PUT  /api/atletica/atlas/system-prompt   — Update prompt { "prompt": "..." }
// DELETE /api/atletica/atlas/system-prompt — Reset to default

// Read
const { prompt, is_custom } = await (
    await fetch('/api/atletica/atlas/system-prompt')
).json();

// Update
await fetch('/api/atletica/atlas/system-prompt', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ prompt: 'You are ATLAS...' })
});

// Reset to default
await fetch('/api/atletica/atlas/system-prompt', { method: 'DELETE' });
Session Lifecycle & Health
// DELETE /api/atletica/atlas/sessions/:id  — End a session
await fetch('/api/atletica/atlas/sessions/atlas_m3k9x2_a7b4c1', { method: 'DELETE' });

// GET /api/atletica/atlas/health            — Service health check
const health = await (
    await fetch('/api/atletica/atlas/health')
).json();
// { "status": "ok", "active_sessions": 3, "api_key_configured": true }
Security: All AI logic, credentials, and implementation details are never sent to the client. The frontend only sends user messages and the current configurator state. All reasoning happens server-side.
Full Integration Example
example.html
Complete working example showing how to integrate the SDK into a standalone HTML page. This demonstrates player initialization, configuration handling, and event binding.
example.html