A traction elevator balances the car against a counterweight sized for roughly half the rated load. The unbalanced mass on the car side is
Δm = L − 0.5·L_rated
where L is the current passenger load. Whichever side is heavier is pulled down by gravity; if the car happens to be travelling in that natural direction, the hoist motor acts as a generator and brakes the descent/ascent instead of driving it — the same principle used in EV regenerative braking:
E_trip = |Δm| · g · h
Regen: motor direction opposes gravity's pull → E_regen = η · E_trip
Consumption: motor direction matches the needed push → E_draw = E_trip / η
Every regenerated trip charges a bank of second-life batteries — EV or lift-truck packs retired below the ~80% state-of-health automotive threshold but still fit for slower, shallower stationary cycling. The bank's state of charge integrates net power over time, clamped to its rated capacity:
SOC(t+dt) = clamp( SOC(t) + P_net·dt / C_bank , 0, 1 )
The bank then performs peak shaving: whenever the building's background load plus an elevator draw would exceed the threshold you set, the battery discharges to cover the excess instead of drawing it from the grid, up to its available charge. That is the direct economic case behind reusing "second-life" batteries in building/lift energy systems — it needs far less cycle life and calendar life than a first-life automotive pack, so a battery graded out of a vehicle can still do useful work for years, cutting demand charges and the need to mine and refine fresh cobalt and lithium for a new pack.
- Passenger load — heavier trips create a larger Δm and more energy per trip, in either direction.
- Trip rate — more trips per minute means more energy events to buffer, testing whether the bank can keep up.
- Battery bank size — a bigger repurposed pack absorbs bigger regen spikes and shaves peaks for longer before running flat.
- Peak-shaving threshold — lower it and the battery works harder (and discharges faster) to keep grid draw under the line.
2D note: the peak-shaving branch of the original 3D engine computed an unused `availableKW` helper with a redundant ×1000/1000 term that never fed into the discharge limit — the real cap was always maxDischargeKW = SOC·3600/dt. This 2D engine keeps only the correct term; behaviour is numerically identical, the dead code was simply dropped.