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.
159 lines
4.7 KiB
JavaScript
159 lines
4.7 KiB
JavaScript
/**
|
||
* 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} 0–1
|
||
*/
|
||
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} 0–1
|
||
*/
|
||
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,
|
||
};
|
||
}
|
||
}
|