Asset Format

This guide documents the file formats used for game assets in brkrs.

Level Files

Level definitions are consolidated in assets/levels/README.md:

File Naming

  • Format: level_NNN.ron where NNN is a zero-padded number

  • Examples: level_001.ron, level_002.ron, level_999.ron

Level Definition Structure

A level file is a RON value with the LevelDefinition structure the runtime expects. Minimal example:

LevelDefinition(
  number: 1,                         // Level number (must match filename)
  gravity: Some((2.0, 0.0, 0.0)),    // Optional: custom gravity vector (x, y, z)
  description: Some("Level design notes and gameplay hints"), // Optional: level documentation
  author: Some("[Jane Smith](mailto:jane@example.com)"),      // Optional: contributor attribution
  matrix: [
    // 20 rows of 20 columns each
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    // ... 18 more rows ...
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  ],
  presentation: Some((              // Optional: per-level texture overrides
    level_number: 1,
    ground_profile: Some("ground/custom"),
    background_profile: None,
    sidewall_profile: None,
    tint: None,
    notes: Some("Custom ground texture"),
  )),
)

Fields Reference

  • number: u32 — Level index/identifier; used by the loader to find level_{:03}.ron next-level files. Must match the filename (e.g., number: 1 in level_001.ron).

  • gravity: Option<(f32, f32, f32)> — Optional gravity override for the level (X, Y, Z). If omitted, the runtime uses the global gravity configuration. During ball respawn, gravity is temporarily set to zero while the paddle grows back to normal size.

  • matrix: Vec<Vec<u8>> — The tile grid, encoded as rows of byte values. The runtime normalizes input to 20×20 using src/level_loader.rs::normalize_matrix_simple (padding/truncating rows or columns as needed).

  • description: Option<String> — Optional level design documentation. Use for design notes, gameplay hints, technical implementation details, or any other information helpful to other developers. Supports multiline strings and special characters.

  • author: Option<String> — Optional contributor attribution. Use plain text names or Markdown link format [Name](url) for email/website attribution. The runtime provides helper methods to extract display names from Markdown links.

  • presentation: Option<LevelTextureSet> — Optional per-level texture overrides for ground, background, and sidewall materials. See “Assigning Per-Level Ground Textures” below.

Grid Coordinates

The game uses a 20×20 grid with the following coordinate system:

  • Origin: Top-left corner is [0][0]

  • X-axis: Columns (left to right, 0-19)

  • Z-axis: Rows (top to bottom, 0-19)

  • Y-axis: Fixed at Y=2.0 (gameplay plane)

     0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19  (columns)
   ┌────────────────────────────────────────────────────────────┐
 0 │                                                            │
 1 │              ┌─────────────────────┐                       │
 2 │              │   BRICKS (20s)      │                       │
 3 │              └─────────────────────┘                       │
 4 │                                                            │
   │                                                            │
12 │                    ═ Paddle (2)                            │
   │                                                            │
18 │                    ○ Ball (1)                              │
19 │                                                            │
   └────────────────────────────────────────────────────────────┘
(rows)

Gravity Override Examples

The gravity field is optional and allows per-level physics customization:

// No gravity override (uses default global gravity)
gravity: None,

// Custom gravity vector
gravity: Some((2.0, 0.0, 0.0)),  // Pulls toward +X (right)
gravity: Some((-1.0, 0.0, 1.0)), // Diagonal pull
gravity: Some((0.0, -9.81, 0.0)), // Standard downward gravity

Metadata Fields (Description and Author)

Level files can include optional metadata fields for better organization and attribution:

Description Field

The description field allows level designers to document their design intent, gameplay mechanics, or technical notes:

LevelDefinition(
  number: 42,
  description: Some(r#"
    Expert challenge level featuring moving obstacles.

    Design goals:
    - Test player precision timing
    - Introduce complex brick patterns
    - Maintain 60 FPS performance

    Technical notes:
    - Uses custom brick type 100
    - Requires texture_manifest feature
  "#),
  matrix: [
    // ... level matrix
  ],
)

Features:

  • Multiline strings using raw string literals (r#"..."#)

  • Special characters and formatting

  • Detailed design documentation

  • Technical implementation notes

Author Field

The author field credits contributors and supports both plain text and Markdown link formats:

// Plain text attribution
author: Some("Jane Smith")

// Markdown email link
author: Some("[Jane Smith](mailto:jane@example.com)")

// Markdown website link
author: Some("[Game Team](https://github.com/org/repo)")

The runtime provides extract_author_name() function and LevelDefinition::author_name() method to extract display names from Markdown links, returning “Jane Smith” or “Game Team” respectively.

Backward Compatibility

Both metadata fields are optional and default to None. Existing level files without these fields continue to work unchanged. The runtime treats empty/whitespace-only values as None for helper methods like has_description() and has_author().

Matrix Cell Values (Tile Semantics)

The runtime uses numeric tile values to determine what to spawn at each grid cell:

Matrix Cell Values (Tile Semantics)

The runtime uses numeric tile values to determine what to spawn at each grid cell:

Value

Entity

Notes

0

Empty

No entity spawned

1

Ball

First occurrence only; additional 1s are ignored. At least one recommended.

2

Paddle

First occurrence only; additional 2s are ignored. At least one recommended.

20

Standard Brick

Canonical destructible brick type (recommended for new levels)

3

Legacy Brick

Standard destructible brick (legacy; prefer 20 for new levels)

90

Indestructible Brick

Collides like a brick but does NOT count toward level completion

4-89, 91-255

Custom Brick Types

Appearance and behavior determined by texture manifest (if enabled)

Notes for Designers

  • Prefer 20 for standard destructible bricks. Value 3 is legacy and will be migrated automatically for repository assets.

  • 90 is reserved for indestructible bricks — they cannot be destroyed but still collide and participate in gameplay.

  • Only the first 2 (paddle) and 1 (ball) in the matrix are used; add at most one of each. If they are absent, the runtime spawns reasonable defaults.

  • The loader will convert input matrices to the expected 20×20 shape; but editing a properly sized matrix makes human editing and visual reasoning easier.

  • When adding custom brick types (4-89, 91-255), confirm textures are available (when texture_manifest is enabled) or the default debug material will be used.

  • When adding custom brick types (4-89, 91-255), confirm textures are available (when texture_manifest is enabled) or the default debug material will be used.

Example: Complete Level

LevelDefinition(
  number: 1,
  gravity: Some((2.0, 0.0, 0.0)),
  description: Some("Tutorial level: Learn the basics"),
  author: Some("[Tutorial Team](https://github.com/brkrs/levels)"),
  matrix: [
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,20,20,20,20,20,20,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,20,20,20,20,20,20,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,20,20,20,20,20,20,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,20,20,20,20,20,20,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  ],
  presentation: Some((
    level_number: 1,
    ground_profile: Some("ground/tutorial"),
    background_profile: None,
    sidewall_profile: None,
    tint: None,
    notes: Some("Tutorial level ground texture"),
  )),
)

Editing and Testing Workflow

Editing and Testing Workflow

  • Edit files directly using your text editor - the RON format is human-readable and Git-friendly.

  • Test a specific level by setting environment variable BK_LEVEL to the level number when running the desktop build:

    BK_LEVEL=997 cargo run --release
    
  • Hot-reload: In-game, press L to cycle through levels quickly for visual verification.

  • Unit and integration tests exercise level loading and migration tooling. See tests/ for examples.

Assigning Per-Level Ground Textures

You can assign unique ground textures to each level automatically or manually:

Manual Assignment

  1. Choose a texture from assets/textures/background/ (e.g., nsTile1044.png).

  2. Add a profile to assets/textures/manifest.ron if not already present:

    (
        id: "ground/nsTile1044",
        albedo_path: "background/nsTile1044.png",
        normal_path: None,
        roughness: 0.9,
        metallic: 0.0,
        uv_scale: (4.0, 3.0),
        uv_offset: (0.0, 0.0),
        fallback_chain: ["ground/default"],
    ),
    
  3. Edit the level file (e.g., assets/levels/level_001.ron) and add or update the presentation field:

    LevelDefinition(
      number: 1,
      ...
      presentation: Some((
        level_number: 1,
        ground_profile: Some("ground/nsTile1044"),
        background_profile: None,
        sidewall_profile: None,
        tint: None,
        notes: Some("Custom ground texture"),
      )),
    )
    
  4. Save and reload the game (or use hot-reload if supported) to see the new ground texture in-game.

See also: assets/textures/README.md for more on texture profiles and manifest editing.

Visual / Texture Mapping

If the texture_manifest feature is enabled, a texture registry maps BrickTypeIds (tile values) to visual assets. See assets/textures/README.md for texture profile names and how to add type variant mappings for new brick types.

Validation

Level Validation Rules

  1. Matrix size: Must be exactly 20 rows × 20 columns (normalized automatically by the loader)

  2. Level number: Must match the filename (e.g., number: 1 in level_001.ron)

  3. Paddle spawn: At least one cell with value 2 recommended (fallback spawn position used if absent)

  4. Ball spawn: At least one cell with value 1 recommended (fallback spawn position used if absent)

  5. Cell values: All values must be 0-255 (u8 range)

Common Errors

“Matrix must be 20x20”: Check that all rows have exactly 20 elements and there are exactly 20 rows. The loader will normalize mismatched matrices, but editing with the correct size makes debugging easier.

“Invalid cell value”: Only values 0-255 are valid (u8 range). Check for typos or negative values.

“Missing paddle/ball”: The level will still load but will use fallback spawn positions. For clarity, always include at least one 2 (paddle) and one 1 (ball) in the matrix.

“Failed to parse level”: Check RON syntax - common issues include trailing commas inside arrays, unmatched brackets, or missing commas between array elements.

Common Pitfalls

Common Pitfalls

  • Trailing commas: Avoid trailing commas inside numeric arrays as they can make RON parsing ambiguous. Follow the style of existing files in this folder.

  • Missing spawn points: Don’t rely on the runtime to always find a paddle/ball - give explicit spawn points to avoid surprising fallback behavior.

  • Custom brick types without textures: When adding custom brick types (>90), confirm textures are available (when texture_manifest is enabled) or the default debug material will be used.

  • Mismatched level number: Ensure the number field matches the filename number to avoid confusion during level transitions.

Texture Assets

Texture asset documentation is consolidated in assets/textures/README.md:

The game uses a texture manifest system (manifest.ron) to define all visual materials for gameplay objects. Textures can be customized globally or overridden per-level for unique visual themes.

Quick Start

Adding a New Texture

  1. Place your texture file in assets/textures/ or a subdirectory

    • Supported formats: PNG, KTX2

    • Recommended naming: descriptive lowercase with underscores (e.g., metal_rough.png)

  2. Add a profile entry to manifest.ron:

(
  profiles: [
    // ... existing profiles ...

    // Your new profile
    (
      id: "brick/metal",              // Unique identifier
      albedo_path: "metal_rough.png", // Base color texture
      normal_path: Some("metal_normal.png"), // Optional normal map
      roughness: 0.8,                 // 0.0 (smooth) to 1.0 (rough)
      metallic: 0.9,                  // 0.0 (non-metal) to 1.0 (metal)
      uv_scale: (1.0, 1.0),          // Texture tiling (x, y)
      uv_offset: (0.0, 0.0),         // Texture offset
      fallback_chain: [],             // Backup profiles if texture fails
    ),
  ],

  // ... rest of manifest ...
)
  1. Hot-reload: Save manifest.ron and the game will reload automatically (no restart needed)

Applying Textures to Objects

Default Object Textures

Edit the canonical profiles in manifest.ron:

  • ball/default - Ball appearance

  • paddle/default - Paddle appearance

  • brick/default - Default brick appearance

  • sidewall/default - Border walls

  • ground/default - Ground plane

  • background/default - Background plane

Type-Specific Textures (Brick/Ball Variants)

Use type_variants to map gameplay types to visual profiles:

(
  profiles: [
    // ... profiles ...
  ],

  type_variants: [
    // Map brick type 3 to metal texture
    (
      object_class: Brick,
      type_id: 3,
      profile_id: "brick/metal",
      emissive_color: None,
      animation: None,
    ),

    // Map ball type 1 to fire texture
    (
      object_class: Ball,
      type_id: 1,
      profile_id: "ball/fire",
      emissive_color: Some(Srgba(red: 1.0, green: 0.3, blue: 0.0, alpha: 1.0)),
      animation: None,
    ),
  ],

  // ... rest of manifest ...
)

New type mappings for designers

Two new brick indices are introduced for designers:

  • 20 — canonical “simple” brick type going forward (legacy index 3 is still recognized during a compatibility window)

  • 90 — indestructible brick (designer-visible; will never count toward level completion)

Add profiles for brick/type20 and brick/indestructible in manifest.ron and map them in type_variants to ensure the in-game editor and runtime render the correct visuals for these indices.

Example:

(
  // ... profiles ...
  type_variants: [
    ( object_class: Brick, type_id: 20, profile_id: "brick/type20", emissive_color: None, animation: None ),
    ( object_class: Brick, type_id: 90, profile_id: "brick/indestructible", emissive_color: None, animation: None ),
  ]
)
Per-Level Texture Overrides

Customize ground, background, and sidewall textures for specific levels:

Method 1: In manifest.ron
(
  profiles: [
    // ... profiles ...
  ],

  type_variants: [
    // ... variants ...
  ],

  level_overrides: [
    (
      level_number: 2,
      ground_profile: Some("ground/lava"),
      background_profile: Some("background/sunset"),
      sidewall_profile: Some("sidewall/marble"),
      tint: Some(Srgba(red: 1.0, green: 0.8, blue: 0.6, alpha: 1.0)),
      notes: Some("Lava-themed level with warm tint"),
    ),
  ],
)
Method 2: Inline in level file (assets/levels/level_002.ron)
LevelDefinition(
  number: 2,
  gravity: Some((-1.5, 0.0, 0.0)),
  matrix: [
    // ... matrix data ...
  ],
  presentation: Some((
    level_number: 2,
    ground_profile: Some("ground/ice"),
    background_profile: None,  // Use default
    sidewall_profile: None,     // Use default
    tint: None,
    notes: Some("Ice level"),
  )),
)

File Structure

assets/textures/
├── README.md           # This guide
├── manifest.ron        # Master texture configuration
├── fallback/          # Placeholder textures (auto-generated)
└── [your textures]    # PNG/KTX2 texture files

Texture Manifest

The manifest.ron file is the central configuration for all textures in the game. It defines visual asset profiles, maps gameplay types to textures, and configures per-level overrides.

Manifest Structure

// assets/textures/manifest.ron
(
  profiles: [
    // Visual asset profiles (materials with textures)
    (
      id: "brick/default",
      albedo_path: "debug/four_squares_64.png",
      normal_path: Some("debug/cube_normal.png"),
      orm_path: None,
      emissive_path: Some("test/test_emissive.png"),
      depth_path: Some("debug/cube_depth.png"),
      roughness: 0.7,
      metallic: 0.0,
      uv_scale: (1.0, 1.0),
      uv_offset: (0.0, 0.0),
      depth_scale: 0.2,
      fallback_chain: [],
    ),
    // ... more profiles ...
  ],

  type_variants: [
    // Map gameplay types to visual profiles
    (
      object_class: Brick,
      type_id: 3,
      profile_id: "brick/default",
      emissive_color: None,
      animation: None,
    ),
    // ... more type mappings ...
  ],

  level_overrides: [
    // Per-level texture customizations
    (
      level_number: 2,
      ground_profile: Some("ground/lava"),
      background_profile: Some("background/sunset"),
      sidewall_profile: Some("sidewall/marble"),
      tint: Some(Srgba(red: 1.0, green: 0.8, blue: 0.6, alpha: 1.0)),
      notes: Some("Lava-themed level"),
    ),
    // ... more level overrides ...
  ],
)

Manifest Loading

  • Startup: Manifest is loaded once when the game starts

  • Hot-Reload: Changes to manifest.ron are detected and applied automatically

  • Validation: Parse errors are logged with line numbers for debugging

  • Fallback Behavior: If manifest is missing or invalid, game uses hardcoded default materials

Path Resolution

All texture paths in the manifest are relative to assets/textures/:

// These are equivalent:
albedo_path: "brick_stone.png"              // → assets/textures/brick_stone.png
albedo_path: "materials/brick_stone.png"    // → assets/textures/materials/brick_stone.png

Do NOT use absolute paths or ../ navigation - they will fail in WASM builds.

Fallback Textures

The fallback/ directory contains default textures used when:

  • Custom textures are missing or fail to load

  • A profile’s fallback_chain is triggered

  • The manifest is invalid or absent

Default Fallback Files

Generated automatically if missing:

File

Purpose

Dimensions

Format

brick_base.png

Default brick texture

64×64

PNG, sRGB

paddle_base.png

Default paddle texture

64×64

PNG, sRGB

ball_base.png

Default ball texture

64×64

PNG, sRGB

ground_base.png

Floor texture

512×512

PNG, sRGB

sidewall_base.png

Wall textures

512×512

PNG, sRGB

background_base.png

Background texture

1024×1024

PNG, sRGB

Fallback Chain Mechanism

Profiles can specify a fallback_chain to gracefully degrade when textures fail:

(
  id: "brick/exotic",
  albedo_path: "exotic_unavailable.png",  // Might not exist
  // ... other fields ...
  fallback_chain: ["brick/metal", "brick/default"],
)

Resolution order:

  1. Try loading exotic_unavailable.png

  2. If that fails, try brick/metal profile

  3. If that fails, try brick/default profile

  4. If all fail, use hardcoded fallback material

Customizing Fallback Textures

You can replace the auto-generated fallbacks:

  1. Create custom textures matching the filenames above

  2. Place them in assets/textures/fallback/

  3. Restart the game (fallbacks are loaded at startup, not hot-reloaded)

Recommended: Keep fallbacks simple and low-resolution for fast loading and WASM bundle size.

Texture Formats & Color Channels

Supported File Formats

Color Channel Meanings by Texture Type

Albedo/Base Color Texture (albedo_path)
  • Format: RGB or RGBA

  • Color Space: sRGB (gamma-corrected)

  • Channels:

    • R, G, B: Diffuse surface color (what you see under white light)

    • Alpha: Transparency (1.0 = opaque, 0.0 = fully transparent)

      • For breakout game objects, typically use fully opaque (alpha = 1.0)

      • Alpha < 1.0 enables transparency but may affect rendering order

  • Best Practices:

    • Avoid pure white (255,255,255) or pure black (0,0,0) as they can look unrealistic

    • Use mid-range values (50-200) for most materials

    • Paint shadows/lighting in normal maps, not albedo

Normal Map Texture (normal_path)
  • Format: RGB (alpha channel ignored)

  • Color Space: Linear (do NOT use sRGB for normals)

  • Channels:

    • R (Red): X-axis normal component (left ↔ right surface angle)

    • G (Green): Y-axis normal component (down ↔ up surface angle)

    • B (Blue): Z-axis normal component (into ↔ out of surface)

  • Color Interpretation:

    • RGB (128, 128, 255) = flat surface pointing toward camera (normal)

    • RGB (255, 128, 128) = surface angled right

    • RGB (0, 128, 128) = surface angled left

    • RGB (128, 255, 128) = surface angled up

    • RGB (128, 0, 128) = surface angled down

  • Common Mistakes:

    • ❌ Using a bump/height map instead of a normal map

    • ❌ Saving as sRGB instead of linear color space

    • ❌ Inverting Y-axis (use OpenGL format, not DirectX)

  • Conversion: GIMP → Filters → Generic → Normal Map (from height map)

ORM Texture (orm_path)
  • Format: RGB (Occlusion/Roughness/Metallic packed)

  • Color Space: Linear

  • Channels:

    • R (Red): Ambient Occlusion

      • 255 (white) = fully lit, no occlusion

      • 0 (black) = fully shadowed (crevices, cracks)

      • Controls subtle ambient shadows in recessed areas

    • G (Green): Roughness

      • 255 (white) = completely rough/matte (1.0)

      • 0 (black) = perfectly smooth/mirror (0.0)

      • Overrides profile’s roughness scalar value

    • B (Blue): Metallic

      • 255 (white) = fully metallic (1.0)

      • 0 (black) = non-metallic/dielectric (0.0)

      • Overrides profile’s metallic scalar value

    • Alpha: Ignored

  • Optimization: Packing 3 grayscale maps into one RGB texture saves memory and texture slots

Emissive Texture (emissive_path)
  • Format: RGB or RGBA

  • Color Space: sRGB

  • Channels:

    • R, G, B: Emitted light color and intensity

      • Values > 128 (0.5 linear) emit noticeable light

      • Values > 200 (0.8 linear) emit strong light

      • Pure black (0,0,0) = no emission

    • Alpha: Can mask emission regions (0 = no emission, 255 = full emission)

  • Combination: Multiplied with emissive_color from TypeVariantDefinition if both set

  • Examples:

    • RGB (255, 200, 0) = bright yellow-orange glow

    • RGB (0, 255, 255) = cyan neon glow

    • RGB (128, 0, 0) = subtle red glow

Depth/Parallax Map Texture (depth_path)
  • Format: Grayscale (R channel used, GB ignored) or single-channel

  • Color Space: Linear

  • Channel Interpretation:

    • White (255): Raised/protruding surface areas

    • Black (0): Recessed/indented surface areas

    • Mid-gray (128): Neutral height (no displacement)

  • Usage: Creates illusion of depth on flat surfaces via parallax occlusion mapping

  • Scaling: Actual depth effect controlled by depth_scale parameter (0.0-1.0 typical)

  • Performance: Depth mapping is more expensive than normal mapping; use sparingly

Alpha/Transparency Handling

Opaque Materials (Default)
  • Set alpha = 1.0 (255) in albedo texture

  • Faster rendering (no transparency sorting needed)

  • Recommended for all breakout game objects (bricks, paddle, ball)

Transparent Materials
  • Set alpha < 1.0 in albedo texture

  • Enables alpha blending but may cause:

    • Rendering order issues (objects drawn back-to-front)

    • Performance overhead

    • Depth sorting artifacts

  • Use only when necessary (e.g., glass bricks, particle effects)

Alpha Masking (Binary Transparency)
  • Use alpha = 0.0 or 1.0 (no intermediate values)

  • Enables cutout/clip effects (e.g., chain-link fence pattern)

  • Better performance than alpha blending

  • Set alpha threshold in material if needed

Color Space Summary

Texture Type

Color Space

Why

Albedo

sRGB

Matches how artists paint and how displays show color

Normal Map

Linear

Mathematical vectors; sRGB gamma breaks calculations

ORM

Linear

Physical properties; gamma correction distorts values

Emissive

sRGB

Artist-friendly color specification

Depth

Linear

Height values; gamma correction distorts displacement

Important: Most image editors save as sRGB by default. For normal/ORM/depth maps, disable sRGB or use “Save as Linear” if available.

File Naming Conventions

Recommended naming scheme for texture sets:

material_name_albedo.png
material_name_normal.png
material_name_orm.png
material_name_emissive.png
material_name_depth.png

Example:

brick_stone_albedo.png
brick_stone_normal.png
brick_stone_orm.png

Manifest Schema Reference

VisualAssetProfile

Defines a complete material profile.

Field

Type

Default

Description

id

String

required

Unique identifier (e.g., “brick/metal”)

albedo_path

String

required

Path to base color texture

normal_path

Option<String>

None

Optional normal map for surface detail. Important: Must be a proper normal map (RGB values representing XYZ normals in tangent space), not a bump map. If you have a grayscale bump map, convert it to a normal map using image editing software (e.g., GIMP: Filters → Generic → Normal Map). Bevy does not automatically convert bump maps to normal maps.

orm_path

Option<String>

None

Optional ORM (Occlusion/Roughness/Metallic) packed texture. Red channel = occlusion, Green channel = roughness, Blue channel = metallic. When provided, overrides separate roughness and metallic scalar values. Useful for optimizing texture memory by packing multiple material properties into one texture.

emissive_path

Option<String>

None

Optional emissive texture for self-illuminating surfaces. RGB values define the emitted light color and intensity. Combined with emissive_color from TypeVariantDefinition if both are specified. Use for glowing effects, neon signs, or light-emitting game objects.

depth_path

Option<String>

None

Optional depth/parallax mapping texture (height map). Grayscale values define surface height for parallax occlusion mapping, creating an illusion of depth on flat surfaces. White = raised areas, Black = recessed areas. Requires depth_scale to be set for visible effect.

roughness

f32

0.5

Surface roughness (0.0 = mirror, 1.0 = matte). Overridden by orm_path green channel if ORM texture is provided.

metallic

f32

0.0

Metallic property (0.0 = non-metal/dielectric, 1.0 = fully metal). Controls how reflective and mirror-like a surface appears. Combined with roughness: low metallic + low roughness = shiny plastic; high metallic + low roughness = polished mirror-like metal; high metallic + high roughness = brushed/worn metal. Overridden by orm_path blue channel if ORM texture is provided.

uv_scale

(f32, f32)

(1.0, 1.0)

Texture tiling factors (x, y)

uv_offset

(f32, f32)

(0.0, 0.0)

Texture offset (x, y)

depth_scale

f32

0.1

Depth intensity multiplier for parallax mapping when depth_path is set. Higher values = more pronounced depth effect. Range typically 0.0-1.0, though values beyond can create extreme parallax. Only used if depth_path is provided.

fallback_chain

Vec<String>

[]

Backup profile IDs if texture fails to load

TypeVariantDefinition

Maps gameplay type IDs to visual profiles.

Field

Type

Default

Description

object_class

Enum

required

Ball or Brick

type_id

u8

required

Gameplay type ID (3+ for bricks)

profile_id

String

required

Reference to VisualAssetProfile.id

emissive_color

Option<Color>

None

Self-illumination color

animation

Option<AnimationDescriptor>

None

Future: animation effects

LevelTextureSet

Per-level material overrides for environmental objects.

Field

Type

Default

Description

level_number

u32

required

Level number to override

ground_profile

Option<String>

None

Ground plane texture profile

background_profile

Option<String>

None

Background plane texture profile

sidewall_profile

Option<String>

None

Border wall texture profile

tint

Option<Color>

None

RGBA color multiplier for level mood

notes

Option<String>

None

Designer notes/description

Common Workflows

Creating a Themed Level

  1. Create custom texture profiles for ground, background, sidewalls

  2. Add profiles to manifest.ron

  3. Reference them in level_overrides or inline in the level file

  4. Optionally add a tint for color mood adjustment

  5. Test in-game with hot-reload

Adding Brick Variety

  1. Create texture files for each brick type (type 3, 4, 5, etc.)

  2. Add VisualAssetProfile for each texture

  3. Add TypeVariantDefinition entries mapping type IDs to profiles

  4. Place brick types in level matrix (values 3+)

  5. Test spawn behavior

Testing Textures

  1. Hot-reload: Edit manifest.ron while game is running

    • Changes apply automatically to current level

    • No restart required

  2. Level preview: Press L to cycle through levels quickly

  3. Visual verification:

    • Check all object types render correctly

    • Verify tiling/offset adjustments

    • Confirm fallbacks work when textures missing

Troubleshooting

Texture Not Appearing

  1. Check file path: Paths in manifest are relative to assets/textures/

  2. Check format: Only PNG and KTX2 supported

  3. Check logs: Look for warnings about missing files

  4. Verify profile ID: Ensure no typos in references

Fallback Material Showing

If you see default gray/colored materials instead of textures:

  1. Check manifest.ron for parse errors (logs show line numbers)

  2. Verify profile ID matches between definition and usage

  3. Ensure texture file exists at specified path

  4. Check fallback_chain for backup options

Hot-Reload Not Working

  1. Save manifest.ron explicitly

  2. Check for syntax errors in RON format

  3. Restart game if asset server is stuck

Best Practices

Texture Creation

  • Resolution: 512x512 or 1024x1024 for most objects

    • Bricks/Balls: 512x512 sufficient

    • Ground/Background: 1024x1024 or 2048x2048 for quality

    • Normal/ORM maps: Can be half resolution of albedo (256x256 for 512x512 albedo)

  • Format:

    • PNG for development/iteration (lossless, easy editing)

    • KTX2 for production builds (50-90% size reduction, faster loading)

    • See “Texture Formats & Color Channels” section for detailed format guidance

  • Color Space:

    • Albedo/Emissive: Save as sRGB

    • Normal/ORM/Depth: Save as Linear (disable gamma correction)

    • Check your image editor’s export settings

  • Compression:

    • PNG: Keep under 1MB per file for quick loading

    • KTX2: Use BC7 compression for high quality, BC3 for legacy support

  • Alpha Channel:

    • Use fully opaque (alpha=1.0) for game objects unless transparency needed

    • Binary alpha masking (0.0 or 1.0 only) is faster than gradual transparency

  • UV Mapping:

    • Design textures to tile seamlessly for uv_scale > 1.0

    • Test tiling by setting uv_scale to (2.0, 2.0) and checking for seams

    • Power-of-2 resolutions (512, 1024, 2048) optimize GPU memory

  • Channel Usage:

    • Don’t paint lighting/shadows in albedo - use normal maps instead

    • ORM packing saves memory: combine occlusion, roughness, metallic into one RGB texture

    • Depth maps are expensive - use only when parallax effect is essential

Manifest Organization

  • Group related profiles together with comments

  • Use consistent naming conventions: object_class/variant

  • Document complex fallback chains

  • Add notes to level overrides explaining theme

Performance

  • Reuse profiles across levels when possible

  • Use texture atlases for small repeated patterns

  • Prefer lower-resolution textures for background elements

  • Monitor WASM builds for asset size (use KTX2 compression)

Advanced Features

Fallback Chains

Create resilient material loading with fallback chains:

(
  id: "brick/exotic",
  albedo_path: "exotic_unavailable.png",
  // ... other fields ...
  fallback_chain: ["brick/metal", "brick/default"],
)

If exotic_unavailable.png fails, tries brick/metal, then brick/default.

Emissive Materials

Add glow effects to bricks or balls:

(
  object_class: Brick,
  type_id: 5,
  profile_id: "brick/glowing",
  emissive_color: Some(Srgba(red: 0.0, green: 1.0, blue: 0.8, alpha: 1.0)),
  animation: None,
)

Tint Modifiers

Create color variations without new textures:

(
  level_number: 3,
  ground_profile: Some("ground/default"),
  background_profile: None,
  sidewall_profile: None,
  tint: Some(Srgba(red: 0.8, green: 0.8, blue: 1.2, alpha: 1.0)), // Blueish tint
  notes: Some("Moonlight theme"),
)

Contract API (For Tooling)

External tools can interact with the texture system via events:

Preview Asset (Temporary Override)

Send PreviewVisualAsset event with a profile to test textures without editing manifest:

PreviewVisualAsset {
    profile: VisualAssetProfile {
        id: "test/preview".to_string(),
        albedo_path: "preview_texture.png".to_string(),
        // ... other fields ...
    },
    persist: false, // Don't save to manifest
}

This enables external editors to preview textures in real-time.

Examples

Complete Manifest Example

(
  profiles: [
    // Canonical defaults
    (
      id: "ball/default",
      albedo_path: "ball_default.png",
      normal_path: None,
      roughness: 0.3,
      metallic: 0.0,
      uv_scale: (1.0, 1.0),
      uv_offset: (0.0, 0.0),
      fallback_chain: [],
    ),

    // Custom variants
    (
      id: "brick/wood",
      albedo_path: "wood_planks.png",
      normal_path: Some("wood_normal.png"),
      roughness: 0.9,
      metallic: 0.0,
      uv_scale: (2.0, 2.0),
      uv_offset: (0.0, 0.0),
      fallback_chain: ["brick/default"],
    ),
  ],

  type_variants: [
    (
      object_class: Brick,
      type_id: 3,
      profile_id: "brick/wood",
      emissive_color: None,
      animation: None,
    ),
  ],

  level_overrides: [
    (
      level_number: 2,
      ground_profile: Some("ground/grass"),
      background_profile: Some("background/sky"),
      sidewall_profile: Some("sidewall/wood"),
      tint: Some(Srgba(red: 1.0, green: 1.0, blue: 0.9, alpha: 1.0)),
      notes: Some("Outdoor garden theme"),
    ),
  ],
)

Getting Help

  • Check game logs for detailed error messages

  • Verify RON syntax with online validators

  • Review existing profiles in manifest for examples

  • Test changes incrementally with hot-reload

Audio Assets

Audio asset documentation is consolidated in assets/audio/README.md:

Overview

The game uses an audio manifest system (manifest.ron) to map gameplay events to audio files. Sound effects are triggered automatically by the audio system in response to game events.

File Structure

assets/audio/
├── README.md           # This guide
├── manifest.ron        # Audio configuration mapping events to files
└── [your audio files]  # OGG audio files

Supported Audio Format

OGG Vorbis is the only supported audio format:

  • Format: .ogg (Vorbis codec)

  • Why OGG:

    • Excellent compression (smaller than WAV, similar to MP3)

    • Open-source and royalty-free

    • Well-supported by Bevy audio system

    • Works in WASM builds

  • Recommended Settings:

    • Sample rate: 44.1 kHz or 48 kHz

    • Bit rate: 128-192 kbps for sound effects

    • Channels: Mono for most sound effects (stereo for music/ambience)

  • Conversion: Use tools like Audacity or ffmpeg to convert from WAV/MP3 to OGG

Adding Audio Files

Step 1: Prepare Your Audio File

  1. Create or obtain your audio file in a supported format (WAV, MP3, FLAC, etc.)

  2. Convert to OGG Vorbis:

    # Using ffmpeg
    ffmpeg -i input.wav -c:a libvorbis -q:a 5 output.ogg
    
    # Using Audacity: File → Export → Export as OGG Vorbis
    
  3. Use descriptive filenames: brick_destroy.ogg, level_complete.ogg, paddle_hit_metal.ogg

  4. Place the file in assets/audio/ directory

Step 2: Update the Audio Manifest

Edit manifest.ron to map the sound effect to its filename:

AudioManifest(
    sounds: {
        BrickDestroy: "brick_destroy.ogg",
        MultiHitImpact: "multi_hit_impact.ogg",
        WallBounce: "wall_bounce.ogg",
        PaddleHit: "paddle_hit.ogg",
        PaddleWallHit: "paddle_wall_hit.ogg",
        PaddleBrickHit: "paddle_brick_hit.ogg",
        LevelStart: "level_start.ogg",
        LevelComplete: "level_complete.ogg",
        UiBeep: "cheat_mode_toggle.ogg",
        // Add your new sound here:
        NewSoundType: "new_sound.ogg",
    }
)

Key Requirements:

  • The key (e.g., BrickDestroy) must match a valid SoundType enum variant in the code

  • The value is the filename of your audio file (relative to assets/audio/)

  • No duplicate keys allowed

  • Filenames are case-sensitive

Step 3: Test Your Audio

  1. Run the game: cargo run

  2. Trigger the event that should play your sound (e.g., destroy a brick for BrickDestroy)

  3. Check logs: If the sound doesn’t play, check console for warnings about missing files

Supported Sound Types

The following sound types are currently supported (defined in src/systems/audio.rs):

Sound Type

Event Trigger

Notes

BrickDestroy

Brick destroyed by ball

Standard brick destruction

MultiHitImpact

Multi-hit brick damaged

Brick hit but not destroyed

WallBounce

Ball bounces off wall

Border collision

PaddleHit

Ball bounces off paddle

Paddle collision

PaddleWallHit

Paddle collides with wall

Paddle movement limit

PaddleBrickHit

Paddle collides with brick

Rare edge case

LevelStart

New level begins

Level initialization

LevelComplete

All bricks cleared

Level completion

UiBeep

UI interaction blocked

Error/feedback sound

Adding New Sound Types: To add a new sound type, you must update the SoundType enum in src/systems/audio.rs and trigger the corresponding audio signal in the relevant game system.

Audio Manifest Reference

Structure

AudioManifest(
    sounds: {
        <SoundType>: "<filename.ogg>",
        // ... more mappings
    }
)

Fields

  • sounds: A map (dictionary) from SoundType enum variants to audio filenames

  • Keys: Must be valid Rust identifiers matching SoundType enum variants

  • Values: Filenames (strings) relative to assets/audio/ directory

Example: Complete Manifest

AudioManifest(
    sounds: {
        // Collision sounds
        BrickDestroy: "impact_brick_shatter.ogg",
        MultiHitImpact: "impact_brick_thud.ogg",
        WallBounce: "impact_wall_bounce.ogg",
        PaddleHit: "impact_paddle_hit.ogg",

        // Paddle edge cases
        PaddleWallHit: "paddle_wall_collision.ogg",
        PaddleBrickHit: "paddle_brick_collision.ogg",

        // Level events
        LevelStart: "level_start_chime.ogg",
        LevelComplete: "level_complete_fanfare.ogg",

        // UI feedback
        UiBeep: "ui_error_beep.ogg",
    }
)

Best Practices

Audio File Creation

  • Keep files short: Sound effects should be 0.5-2 seconds for most impacts

  • Normalize volume: Aim for consistent loudness across all sound effects

  • Remove silence: Trim leading/trailing silence to ensure immediate playback

  • Avoid clipping: Keep peak levels below 0 dB to prevent distortion

  • Test in-game: Audio that sounds good in isolation may not fit gameplay context

File Organization

  • Use descriptive names: brick_heavy_impact.ogg is better than sound1.ogg

  • Group related sounds: Consider prefixes like impact_, ui_, level_

  • Keep file sizes small: Compress to ~128 kbps for most effects (good quality, small size)

  • Optimize for WASM: Total audio assets affect web build loading time

Manifest Maintenance

  • Keep alphabetical: Sort sound type keys for easy reference

  • Comment variations: Add notes if using different sounds for similar events

  • Version control: Track manifest changes to understand audio design evolution

Troubleshooting

Sound Not Playing

  1. Check filename: Ensure filename in manifest matches actual file (case-sensitive)

  2. Verify format: Confirm file is valid OGG Vorbis (use file command or media player)

  3. Check logs: Look for asset loading warnings in console output

  4. Test file: Play the OGG file directly in a media player to confirm it works

  5. Verify trigger: Ensure the game event that should trigger the sound is actually occurring

Sound Plays But Is Wrong

  1. Check mapping: Verify correct sound is mapped to the event in manifest

  2. Test isolation: Temporarily map all sounds to one file to isolate the issue

  3. Volume check: Ensure audio file isn’t silent or too quiet

Manifest Parse Errors

  1. Check RON syntax: Ensure proper RON format (commas between entries, colon between key/value)

  2. Verify sound types: All keys must match exact SoundType enum variant names

  3. Check for typos: Sound type names are case-sensitive (BrickDestroy not brickdestroy)

  4. Look for duplicates: Each sound type can only appear once in the map

Common Errors

“File not found”: Filename in manifest doesn’t match actual file. Check spelling and case.

“Unsupported format”: File is not OGG Vorbis. Convert using ffmpeg or Audacity.

“Unknown sound type”: Key in manifest doesn’t match any SoundType enum variant. Check spelling and capitalization.

Audio Configuration

The game provides global audio configuration in config/audio.ron:

AudioConfig(
    master_volume: 1.0,      // Overall volume (0.0-1.0)
    effects_volume: 0.8,     // Sound effects volume multiplier
    music_volume: 0.6,       // Music volume multiplier (when implemented)
)

See config/audio.ron for current settings and documentation.