Skip to content

Compositions

The .holo format uses compositions to define complete scenes. This guide covers all composition features.

Basic Structure

holo
composition "Scene Name" {
  environment { }
  
  template "TemplateName" { }
  
  object "ObjectName" { }
  
  spatial_group "GroupName" { }
  
  logic { }
}

Every .holo file contains exactly one composition.


Environment Block

Configure the world settings:

holo
environment {
  // Skybox - preset name or URL
  skybox: "sunset"
  // skybox: "https://example.com/skybox.hdr"
  
  // Lighting
  ambient_light: 0.4          // 0.0 - 1.0
  ambient_color: "#ffffff"
  
  // Physics
  gravity: -9.81              // m/s²
  // gravity: [0, -9.81, 0]   // Vector form
  
  // Atmosphere
  fog: {
    enabled: true
    color: "#cccccc"
    density: 0.02
    // Or use linear: start: 10, end: 100
  }
  
  // Audio
  reverb: "large_hall"
  master_volume: 0.8
  
  // Rendering
  exposure: 1.0
  tone_mapping: "aces"
}

Skybox Presets

NameDescription
"clear"Clear blue sky
"sunset"Orange/pink sunset
"night"Starry night
"nebula"Space nebula
"overcast"Cloudy gray
"gradient"Simple gradient
"dark"Nearly black

Templates

Templates define reusable object types:

holo
template "Enemy" {
  // Default traits
  @physics
  @collidable
  @destructible
  
  // Default properties
  scale: 1.0
  color: "#ff0000"
  
  // State
  state {
    health: 100
    speed: 5
    is_alive: true
  }
  
  // Actions (like methods)
  action take_damage(amount) {
    state.health -= amount
    
    if (state.health <= 0) {
      this.die()
    }
  }
  
  action die() {
    state.is_alive = false
    play_sound("death.wav")
    spawn "Explosion" at this.position
    destroy this with { delay: 100ms }
  }
  
  // Lifecycle hooks
  on_spawn {
    find_player()
  }
  
  // Update loop
  every 100ms {
    if (state.is_alive) {
      move_towards(player.position, state.speed)
    }
  }
}

Using Templates

holo
// Basic usage
object "Goblin" using "Enemy" {
  position: [5, 0, 10]
}

// Override defaults
object "BossGoblin" using "Enemy" {
  position: [0, 0, 50]
  scale: 3.0
  color: "#ff00ff"
  
  state {
    health: 500      // Override
    speed: 3         // Override
  }
}

Template Inheritance

holo
template "Creature" {
  @physics
  @collidable
  
  state {
    health: 100
  }
}

template "Enemy" extends "Creature" {
  @destructible
  
  state {
    aggression: 50  // Added to Creature's state
  }
}

template "Boss" extends "Enemy" {
  state {
    health: 1000    // Override
    phase: 1
  }
}

Objects

Objects are instances in the scene:

holo
object "ObjectName" {
  // Traits
  @grabbable
  @physics
  
  // Transform
  position: [x, y, z]
  rotation: [x, y, z]       // Euler degrees
  scale: 1.0                // Uniform
  scale: [x, y, z]          // Non-uniform
  
  // Visual
  color: "#00ffff"
  model: "path/to/model.glb"
  material: "metal"
  visible: true
  
  // State
  state {
    custom_property: value
  }
  
  // Events
  on_grab: { }
  on_click: { }
  on_collision(other): { }
}

Event Handlers

holo
object "InteractiveButton" {
  @clickable
  @hoverable
  
  // Inline handler
  on_click: { 
    play_sound("click.wav")
    GameManager.toggle_door()
  }
  
  // Multi-line handler
  on_hover_enter: {
    this.color = "#00ffff"
    this.scale = 1.2
  }
  
  on_hover_exit: {
    this.color = "#ffffff"
    this.scale = 1.0
  }
}

Spatial Groups

Group objects together with shared transform:

holo
spatial_group "EnemyCamp" {
  // Group transform - all children inherit this
  position: [100, 0, 50]
  rotation: [0, 45, 0]
  
  // Children use local coordinates
  object "Tent_1" {
    position: [0, 0, 0]      // World: [100, 0, 50]
  }
  
  object "Tent_2" {
    position: [5, 0, 0]      // World: [105, 0, 50]
  }
  
  object "Campfire" {
    @glowing
    position: [2.5, 0, 2]
  }
  
  // Nested groups
  spatial_group "Guards" {
    position: [0, 0, -5]
    
    object "Guard_1" using "Enemy" { position: [-2, 0, 0] }
    object "Guard_2" using "Enemy" { position: [2, 0, 0] }
  }
}

Dynamic Groups

holo
spatial_group "SpawnZone" {
  @trigger
  
  position: [0, 0, 0]
  
  on_player_enter: {
    spawn_enemies_in_group(5)
  }
  
  action spawn_enemies_in_group(count) {
    repeat count times {
      spawn "Enemy" in this at random_local_position()
    }
  }
}

Logic Block

Define game rules and global behaviors:

holo
logic {
  // Event-based
  on_scene_load {
    initialize_game()
  }
  
  on_player_death {
    show_game_over()
    delay 3s then restart_scene()
  }
  
  // Trigger-based
  on_player_enter("BossRoom") {
    lock_doors()
    spawn "BossEnemy" at boss_spawn_point
    play_music("boss_theme.mp3")
  }
  
  on_player_exit("SafeZone") {
    enable_enemy_spawning()
  }
  
  // Time-based
  every 30s {
    spawn_wave()
  }
  
  every frame {
    update_ui()
  }
  
  // Condition-based
  when Player.health < 20 {
    show_low_health_warning()
  }
  
  when all_enemies_dead {
    complete_wave()
    if (current_wave >= max_waves) {
      victory()
    }
  }
}

Logic Functions

holo
logic {
  // Define functions in logic block
  function spawn_wave() {
    wave_count++
    enemy_count = 5 + wave_count * 2
    
    repeat enemy_count times {
      spawn "Enemy" at random_spawn_point()
    }
  }
  
  function check_victory() {
    if (score >= target_score) {
      victory()
    }
  }
}

Control Flow

Conditionals

holo
object "DynamicDoor" {
  @if Player.has_key {
    color: "#00ff00"
    collision: false
  } @else {
    color: "#ff0000"
    collision: true
  }
}

Loops

holo
// Static generation
@for i in range(0, 10) {
  object "Pillar_${i}" {
    position: [i * 2, 0, 0]
  }
}

// With nested loops
@for x in range(-5, 5) {
  @for z in range(-5, 5) {
    object "Tile_${x}_${z}" {
      position: [x, 0, z]
    }
  }
}

Complete Example

holo
composition "Dungeon Level 1" {
  environment {
    skybox: "dark"
    ambient_light: 0.2
    fog: { color: "#111111", density: 0.05 }
  }

  template "Torch" {
    @glowing(color: "#ff6600", intensity: 2, range: 5)
    @animated(flicker: true)
    @spatial_audio
    
    audio: "fire_crackle.wav"
  }

  template "Skeleton" {
    @physics
    @collidable
    @destructible
    
    model: "skeleton.glb"
    
    state { health: 50, damage: 10 }
    
    action attack(target) {
      play_animation("attack")
      target.take_damage(state.damage)
    }
  }

  spatial_group "Entrance" {
    position: [0, 0, 0]
    
    object "Door" using "HeavyDoor" { position: [0, 0, 5] }
    object "Torch_L" using "Torch" { position: [-2, 2, 5] }
    object "Torch_R" using "Torch" { position: [2, 2, 5] }
  }

  spatial_group "MainHall" {
    position: [0, 0, 20]
    
    @for i in range(0, 6) {
      object "Pillar_${i}" {
        position: [(i % 2 == 0 ? -3 : 3), 0, i * 5]
      }
      object "Torch_${i}" using "Torch" {
        position: [(i % 2 == 0 ? -3 : 3), 2.5, i * 5]
      }
    }
    
    object "Skeleton_1" using "Skeleton" { position: [0, 0, 10] }
    object "Skeleton_2" using "Skeleton" { position: [-2, 0, 15] }
    object "Skeleton_3" using "Skeleton" { position: [2, 0, 15] }
  }

  object "Player" {
    @collidable
    position: [0, 1.6, 0]
    
    state { health: 100, keys: 0 }
  }

  logic {
    on_scene_load {
      play_music("dungeon_ambient.mp3")
    }
    
    on_all_skeletons_dead {
      open_treasure_room()
    }
  }
}

Released under the MIT License.