Files
iron-requiem/src/game/systems/HeatSystem.js
Kay Kayyali eea9c9c973
Some checks failed
Iron Requiem CI/CD / test (push) Failing after 2m53s
Iron Requiem CI/CD / deploy (push) Has been skipped
Build & Deploy / build-and-deploy (push) Has been cancelled
feat: S3/S4 wiring — HeatSystem tick, RadioSystem, IronLedger, PauseScene, Ghost Crew, ScavengeSystem, data files, CampaignMap, CutsceneScene
S3 (Crew + Morale + Radio + Heat):
- HeatSystem wired: update() called in MainGame tick, thermal multipliers applied to speed/accuracy, fuel depletion
- RadioSystem wired: message queue, ghost transmissions, enemy comms interception, debug overlay display
- IronLedger wired: kill/zone/morale logging, SaveManager integration, HUD ledger display
- PauseScene: ESC menu with Resume/Settings/Quit, system pausing (audio/spawns)

S4 (Campaign + Zones + Cutscenes + Polish):
- Data files: 3-zone definitions, 14 upgrades (4 categories), 25 dialogue nodes with 4 branching endings
- ScavengeSystem: kill yields, one-shot bonuses, wreck scavenging, post-combat tally
- Ghost Crew: top-50 runs with score formula, German naming pools, GhostCrewScene display
- CampaignMapScene, CutsceneScene, upgrades wired into main.js

Tests: 496 total, 0 regressions. All workers passed green.
2026-05-30 05:04:35 +00:00

159 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* HeatSystem — tracks tank thermal state.
*
* Heat builds from engine runtime, firing, and taking hits.
* Passive cooling brings temperature down when idle.
* Freeze-thaw: engine must reach warmupThreshold before full power.
* Overheat penalties: reduced speed and accuracy.
*
* @module src/game/systems/HeatSystem
*/
const DEFAULTS = {
maxHeat: 100,
overheatThreshold: 80,
coolingRate: 5,
engineHeatRate: 20,
fireHeat: 25,
hitHeat: 40,
warmupThreshold: 30,
coldSpeedMultiplier: 0.5,
overheatSpeedPenalty: 0.6,
overheatAccuracyPenalty: 0.7,
maxFuel: 100,
fuelDepletionRate: 5, // units per second while engine runs
};
export class HeatSystem {
/**
* @param {object} [config={}] — optional overrides for any default
*/
constructor(config = {}) {
this.maxHeat = config.maxHeat ?? DEFAULTS.maxHeat;
this.overheatThreshold = config.overheatThreshold ?? DEFAULTS.overheatThreshold;
this.coolingRate = config.coolingRate ?? DEFAULTS.coolingRate;
this.engineHeatRate = config.engineHeatRate ?? DEFAULTS.engineHeatRate;
this.fireHeat = config.fireHeat ?? DEFAULTS.fireHeat;
this.hitHeat = config.hitHeat ?? DEFAULTS.hitHeat;
this.warmupThreshold = config.warmupThreshold ?? DEFAULTS.warmupThreshold;
this.coldSpeedMultiplier = config.coldSpeedMultiplier ?? DEFAULTS.coldSpeedMultiplier;
this.overheatSpeedPenalty = config.overheatSpeedPenalty ?? DEFAULTS.overheatSpeedPenalty;
this.overheatAccuracyPenalty = config.overheatAccuracyPenalty ?? DEFAULTS.overheatAccuracyPenalty;
this.fuelDepletionRate = config.fuelDepletionRate ?? DEFAULTS.fuelDepletionRate;
this._temperature = 0;
this._engineReady = false;
this._fuel = config.maxFuel ?? DEFAULTS.maxFuel;
}
/** Current temperature (0 maxHeat). */
get temperature() {
return this._temperature;
}
/** True when temperature exceeds overheatThreshold. */
get isOverheated() {
return this._temperature > this.overheatThreshold;
}
/** True once temperature has reached warmupThreshold (persists). */
get engineReady() {
return this._engineReady;
}
/** Current fuel level (0 maxFuel). */
get fuel() {
return this._fuel;
}
/**
* Advance the thermal simulation by dt seconds.
*
* @param {number} dt — delta time in seconds
* @param {object} state
* @param {boolean} [state.engineOn] — engine is running
* @param {boolean} [state.fired] — a shot was fired this frame
* @param {boolean} [state.wasHit] — the tank took a hit this frame
* @param {number} [state.engineHeatRate_override] — temporary engine heat rate
*/
update(dt, state = {}) {
// Heat sources
if (state.engineOn) {
const rate = state.engineHeatRate_override ?? this.engineHeatRate;
this._temperature += rate * dt;
}
if (state.fired) {
this._temperature += this.fireHeat;
}
if (state.wasHit) {
this._temperature += this.hitHeat;
}
// Passive cooling — only when idle
if (!state.engineOn && !state.fired && !state.wasHit) {
this._temperature = Math.max(0, this._temperature - this.coolingRate * dt);
}
// Cap
if (this._temperature > this.maxHeat) {
this._temperature = this.maxHeat;
}
// Prevent temperature from going negative due to cooling
if (this._temperature < 0) {
this._temperature = 0;
}
// Freeze-thaw latch
if (this._temperature >= this.warmupThreshold) {
this._engineReady = true;
}
// Fuel depletion while engine is running
if (state.engineOn) {
this._fuel = Math.max(0, this._fuel - this.fuelDepletionRate * dt);
}
}
/**
* Speed multiplier, factoring in engine readiness and overheat.
* @returns {number} 01
*/
getSpeedMultiplier() {
if (this._temperature > this.overheatThreshold) {
return this.overheatSpeedPenalty;
}
if (!this._engineReady) {
return this.coldSpeedMultiplier;
}
return 1.0;
}
/**
* Accuracy multiplier — unaffected by cold engine, penalized by overheat.
* @returns {number} 01
*/
getAccuracyMultiplier() {
if (this._temperature > this.overheatThreshold) {
return this.overheatAccuracyPenalty;
}
return 1.0;
}
/**
* Snapshot of current thermal state for HUD / downstream systems.
* @returns {{ temperature: number, isOverheated: boolean, engineReady: boolean, speedMultiplier: number, accuracyMultiplier: number }}
*/
getState() {
return {
temperature: this._temperature,
isOverheated: this.isOverheated,
engineReady: this._engineReady,
speedMultiplier: this.getSpeedMultiplier(),
accuracyMultiplier: this.getAccuracyMultiplier(),
fuel: this._fuel,
};
}
}