Skip to content

AlexVideoFX/LavaRun.Runtime

Repository files navigation

Here's the improved README.md file, incorporating the new content while maintaining the existing structure and coherence:

Project Title

Overview

This project is designed to provide a comprehensive framework for managing and interpreting vendor level archives within the engine's architecture. The primary focus is on parsing and normalizing these archives into the engine’s LevelDefinition model, specifically within the LavaRun.Core namespace.

Scope: This document covers parsing and normalization only. Gameplay rules (hazards, scoring, completion/cooldowns) are managed by the engine (SessionEngine).

Vendor Level Format → Engine Spec

This section describes how vendor level archives (typically *.zip) are interpreted and normalized into the engine’s LevelDefinition model.

Files inside a vendor level archive

A vendor level is usually a zip file containing:

  • Level.json (required)
  • Spirits.json (optional, but commonly present)

Grid and coordinates

  • The engine's logical grid size is defined by FloorSpec.Rows × FloorSpec.Cols (currently 25×12).
  • Vendor coordinates may not match the engine's orientation. The loader applies a transform:
  1. Optional swap row/column: FloorSpec.SwapRC
  2. Optional flip rows: FloorSpec.FlipR
  3. Optional flip columns: FloorSpec.FlipC

After applying these transformations, the loader bounds-checks the transformed coordinate against the engine grid.

Lives (finite vs infinite)

Lives are stored in Level.json under option:

  • option.lifeLimit (boolean-like: can be true/false, 0/1, or string)
  • option.lifeLimitValue (integer-like)

Spec (source of truth):

  • InfiniteLives = (!lifeLimit) || (lifeLimitValue <= 0)
    • This treats 0 or -1 as infinite (vendor packs use this for “free play” / intro stages).
  • If InfiniteLives == false, then:
    • Lives = lifeLimitValue

Note: GamePackLoader.LoadLevel currently contains a compatibility fallback when lifeLimit=true but lifeLimitValue is missing/invalid (falls back to 3). Aligning perfectly to the spec is tracked as a TODO in code.

Frame timing (repeatTimes → milliseconds)

Vendor levels define animation frames in Level.json.frameList.

  • Each frame typically has repeatTimes.
  • If a frame omits repeatTimes, the loader uses frameDefaultRepeatTimes.
  • Each repeat corresponds to a base “tick” duration (the loader uses repeatTickMs if present; otherwise, it derives from fps).

Spec formula:

DurationMs = repeatTimes * TickMs

Where:

  • repeatTimes = frame.repeatTimes ?? frameDefaultRepeatTimes
  • TickMs is the base unit (commonly ~33ms), unless overridden.

Practical consequence (why blinking levels differ): large repeatTimes values produce very slow frame changes.

Matrix tiles (shape stamping)

Each frame can contain a matrix array. Each matrix item has the form:

[rowOffset, colOffset, spiritId, meta]

Where:

  • rowOffset, colOffset are base offsets.
  • spiritId is a string key used to look up shapes in Spirits.json.
  • meta is an object that includes:
    • meta.color (int)
    • meta.points (optional array of point offsets)

Color map

Vendor meta.color → engine TileColor:

  • 0 → Green
  • 1 → Blue
  • 2 → Red
  • 3 → Purple

Point expansion

A matrix entry produces one or more tiles by expanding point offsets:

  • Each point is [dr, dc]
  • Final raw coordinate:

r = rowOffset + dr c = colOffset + dc

Then:

  • Apply transform (swap/flip) via FloorSpec.
  • Bounds check against the 25×12 grid.

Overlaps / dedupe

Multiple matrix entries may stamp the same cell. The loader deduplicates by cell and applies a priority override rule; higher priority wins:

  • Yellow (runtime-only) > Green > Red > Purple > Blue

(White is not a tile color; it is a runtime overlay used by the engine.)

Spirits fallback rule

Spirits.json defines reusable shapes.

Spec:

  • If meta.points exists and is non-empty → use meta.points
  • Else → use Spirits[spiritId].points

This allows vendor packs to reference complex shapes without repeating full point lists in every frame.

Metadata stored for future use

Even if the engine does not use these fields today, the loader preserves them in LevelDefinition so features can be added later without changing parsing:

  • option.timeLimit / option.timeLimitValue
  • option.bgVoice
  • option.startGIF
  • option.integralWeights

Current code captures bgVoice, startGIF/startGif, and timeLimitValue. integralWeights is planned (see TODO(s) in loader comments).

Examples

Example 1: Level 0 infinite lives

Vendor packs often define an intro/free-play stage as “Level 0” and set:

  • option.lifeLimit = true (or sometimes false)
  • option.lifeLimitValue = 0 or -1

Per spec:

  • lifeLimitValue <= 0InfiniteLives = true

Example 2: Level 3 with repeatTimes = 5

Assume TickMs = 33ms.

  • repeatTimes = 5
  • DurationMs = 5 * 33 = 165ms

This yields a fairly fast animation.

Example 3: Level 4 with repeatTimes = 100

Assume TickMs = 33ms.

  • repeatTimes = 100
  • DurationMs = 100 * 33 = 3300ms (~3.3s)

This yields a slow blink (long hold per frame), which is why some levels appear to “blink” very slowly.

Conclusion

This document serves as a guide for developers working with vendor level archives, providing essential details on the structure, parsing, and normalization processes. For further inquiries or contributions, please refer to the project's contribution guidelines.

This revised README maintains the original structure while enhancing clarity and coherence, ensuring that all new content is seamlessly integrated.

About

No description, website, or topics provided.

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages