Kumbatio
Guides

Migrating from Legacy Scales

Bridge a non-native discrete energy scale to the fixed five-level model with createExternalLevelCompatibility.

The package model is fixed to 100 | 75 | 50 | 25 | 0. If your existing app uses a different discrete scale - say a legacy 100 | 66 | 33 | 0 - the compatibility helpers let you migrate incrementally: keep reading old persisted values and keep your old control UX while internally applying native levels.

Build the bridge

import { createExternalLevelCompatibility } from '@kumbatio/energy-system'

const legacy = createExternalLevelCompatibility({
  levels: [100, 66, 33, 0] as const, // your scale, in cycle order
  toEnergyLevel: {
    100: 100,
    66: 50,
    33: 25,
    0: 0,
  },
  fallbackLevel: 100, // used when input is unknown
})

The options are validated at creation: levels must be unique finite numbers, every level needs a mapping, every mapping must be a valid native level, and fallbackLevel must be in levels. Bad configuration fails immediately, not on first use.

For the old 0 | 33 | 66 | 100 scale specifically: 66 → 75 and 33 → 25 or 50 are also defensible mappings, depending on what cognitive load your old levels actually meant. Pick the mapping that preserves intent, not the arithmetic midpoint.

What the bridge gives you

// Read legacy persisted values → native level
legacy.toEnergyLevel(66) // 50
legacy.toEnergyLevel(70) // 50 - unknown values snap to the nearest legacy level first

// Native level → nearest legacy value (for UIs still rendering the old scale)
legacy.fromEnergyLevel(50) // 66

// Keep the old control's cycle order
legacy.cycleExternalLevel(66) // 33
// ...while internally applying native levels
legacy.cycleMappedEnergyLevel(33) // cycles 33 → 0, then maps: 0

Read through the bridge. Wherever legacy values enter (persisted state, URL params, API), convert with legacy.toEnergyLevel(...) before touching the engine.

Write native levels. New writes persist native values (100 | 75 | 50 | 25 | 0) via the engine - never write legacy values back.

Switch UI controls to native cycling. Replace legacy.cycleMappedEnergyLevel with the package's cycleEnergyLevel (or engine.cycleLevel()).

Remove the bridge. Once persisted data is fully normalized, delete the compatibility mapping.

import { cycleEnergyLevel } from '@kumbatio/energy-system'

// End state - no compatibility layer left
const next = cycleEnergyLevel(engine.getState().level)

Lower-level helpers

The bridge is built from generic utilities you can use directly for one-off conversions:

import {
  cycleDiscreteLevel,
  mapToNearestDiscreteLevel,
  mapToNearestEnergyLevel,
} from '@kumbatio/energy-system'

cycleDiscreteLevel(66, [100, 66, 33, 0], 100) // 33 - cycle any discrete list
mapToNearestDiscreteLevel(70, [100, 66, 33, 0], 100) // 66 - snap to nearest
mapToNearestEnergyLevel(60) // 50 - snap any number to a native level

mapToNearestEnergyLevel is also useful for ingesting continuous values (a 0–100 slider, an imported metric) into the discrete model.