Rebalance Doomsday Clock: late-game stalemate-breaker (10min grace + wave squeeze), slower troop drain, gentler-but-steeper warship attrition (#4518)

Resolves #<add your approved & assigned issue number>

## Description:

Rebalances the Doomsday Clock so it acts as a **late-game
stalemate-breaker** rather than an early-game culler, softens how fast
it removes troops and warships, and makes the HUD countdown clearer.

**Clock schedule (all presets):**
- A flat **10-minute grace** at 0% required share — the early game is
decided by combat, not the clock.
- Then a 6-wave squeeze at accelerating levels (4 / 9 / 16 / 26 / 40 /
55%) with short pauses, reaching the final 55% at each preset's cap:
**45 / 35 / 25 / 15 min** for slow / normal / fast / veryfast.
- `WaveSchedule` `rampSeconds`/`pauseSeconds` are now **per-wave
arrays**, so the curve can be shaped (gentle early, steeper late)
instead of one uniform ramp. `requiredBasisPoints` and the HUD companion
`doomsdayClockWaveState` walk the per-wave segments in lockstep.

**Troop drain:** warn window `10s → 30s`, drain eased (`2%→5%` over
`90s`), so a caught side takes ~2 minutes to wipe instead of ~1.

**Warship attrition:** warships get their own gentler start plus a
**convex** decay curve — a ship caught when its side is first doomed
lasts about as long as troops, but the rate ramps up steeply so a side
at full attrition still loses its fleet in ~2s. Adds a `curveExponent`
argument to `doomsdayClockDrain` (1 = linear, used for troops; higher =
convex, used for ships).

**Determinism:** the drain curve is **integer-only** (fixed-point power,
no floats), so the floored per-tick loss is bit-identical on every
client in the lockstep sim. The linear troop path keeps its exact
existing integer form; only the convex warship path is reshaped.

**HUD countdown clarity:** the clock readout now shows a live countdown
in both states — `Will reach 16% in M:SS` while the bar is actively
climbing, and `Starts rising to 26% in M:SS` during a pause (previously
`Next 26% in …`, which read as if it jumped there instantly). Backed by
a new `secondsToTarget` field on the shared wave state so the sim and
HUD stay in agreement. All display text goes through `translateText()` /
`en.json`.
<img width="278" height="116" alt="image"
src="https://github.com/user-attachments/assets/e0be3d2c-bb88-46be-b344-34d63e4859bd"
/>
<img width="304" height="171" alt="image"
src="https://github.com/user-attachments/assets/da74bd05-0b9a-4aec-bbf5-e7380fbda88e"
/>

**Danger skull:** while a side is below the bar in the warn window, its
on-map skull now blinks progressively faster as the countdown runs out
(accelerating to the moment the drain begins), then holds steady once it
is actually draining — a clearer "you are about to be hit" cue.

Rationale: the previous schedule removed players heavily in the first
half of a match and could leave a drawn-out endgame. Holding the clock
at 0% early keeps the opening about fighting, and concentrating its
pressure in the back half reserves it for actually breaking stalemates.
Values are tuning starting points and easy to adjust in
`DoomsdayClock.ts` / the config defaults.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

<add your Discord username>
This commit is contained in:
Zixer1
2026-07-07 19:38:36 -07:00
committed by GitHub
parent 1db65a1877
commit 4ce57efbe2
11 changed files with 275 additions and 146 deletions
+14 -8
View File
@@ -85,19 +85,23 @@ export const SAM_CONSTRUCTION_TICKS = 30 * 10;
// Times in seconds. The required map share rises in waves (levels + times in
// DoomsdayClock.ts, chosen by `speed`). A side caught below the bar gets a
// warnSeconds cooldown ("Danger, decay in Xs"), then troops bleed to zero: the
// warn (10s) + the linear drain (~55s from full troops, sooner with fewer troops
// or a shrinking territory) make ~1 minute from caught to wiped out.
// warn (30s) + the linear drain (~90s from full troops, sooner with fewer troops
// or a shrinking territory) make ~2 minutes from caught to wiped out.
const DOOMSDAY_CLOCK_DEFAULTS = {
enabled: false,
speed: "normal" as DoomsdayClockSpeed,
warnSeconds: 10, // cooldown before decay after the bar catches you
warnSeconds: 30, // cooldown (the flashing danger cue) before decay begins
drainStartPercent: 2, // starts bleeding at once (already beats troop income)
drainMaxPercent: 6,
drainRampSeconds: 50, // ramps LINEARLY to the max over this long
// Warships bleed on the same start + ramp but to a much higher ceiling than
// troops, so a fleet at full attrition sinks in ~2s (50% of a ship's max
// health per second) instead of riding out the gentle troop rate. Ships only.
drainMaxPercent: 5,
drainRampSeconds: 90, // ramps LINEARLY to the max over this long (~1:30 to zero)
// Warships bleed on their OWN gentler start + a STEEP (convex) ramp to a much
// higher ceiling. A ship caught when its side is first doomed lasts about as
// long as troops (the low start + no income ≈ the troop net rate), but the rate
// curves up sharply (warshipDrainCurveExponent), so once a side has been under
// the clock the full ramp, ships sink in ~2s (50%/s). Ships only.
warshipDrainStartPercent: 1,
warshipDrainMaxPercent: 50,
warshipDrainCurveExponent: 8, // >1 = convex: stays gentle early, then spikes
};
export class Config {
@@ -134,7 +138,9 @@ export class Config {
drainStartPercent: d.drainStartPercent,
drainMaxPercent: d.drainMaxPercent,
drainRampSeconds: d.drainRampSeconds,
warshipDrainStartPercent: d.warshipDrainStartPercent,
warshipDrainMaxPercent: d.warshipDrainMaxPercent,
warshipDrainCurveExponent: d.warshipDrainCurveExponent,
};
}
spawnImmunityDuration(): Tick {