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.