- Add Krea2 turbo renders and new capital-apex/mindweave surface images
- Update capital-zenith surface variants with revised art
- Re-export planets.png spritesheet from updated planets.psd
- Add combat.factionScaling config: linear ramp from 1.2x damage / 2.0x armor at level 10 to the shipped 2.0x / 3.5x at level 35, clamped at both ends
- Implement isFactionType and factionScaleForLevel in EnemyTypes.js; extend enemyTypeConfig to accept a level and apply the ramp to weapon.damage, hull, and shields for faction roster fighters only (pirates, neutrals, transport stay flat)
- CombatSystem.spawnEnemy now passes the player's XP level so faction enemies scale with progression
- Gate GameScene escort rendezvous logic on originSystemId match before checking proximity, preventing convoy link in the wrong system
- Add dev tests covering ramp math at levels 0/10/20/35/99, per-fighter consistency, flat ramDamage/xp, and non-faction exclusion
- 180 new side-quest descriptions (9 pools × 20) in data/sidequests.json:
Apex, MindWeave, and Zenith each post patrol/bounty/escort copy in their
own doctrine voice, every body naming the seeded rival via {enemy}
- descriptionPool() in SideQuests.js now chains faction's own pool → shared
war-voice pool → neutral fallback; neutral boards never read war copy
- New dev/lib/faction-voice-lint.mjs catches article disagreement ("an Apex
hull"), doubled words, and unresolved braces across every rival resolution
- dev/jobboard-voice-shot.mjs renders all 180 listings through the real
JobBoardWindow panel, asserting no clipping, overflow, or grammar faults
- Updated sidequests/jobboard tests to assert pool counts, {enemy} presence,
dossier uniqueness, fallback chain, and zero brace leaks
- Documented the pool structure in README and PROJECT_NOTES
- The ship console's CURRENT HULL / SHIELDS now reflect the combat system's hull and shields values at all times, not just while a fight is active.
- Previously, with no combat running the plate fell back to the maxima, masking damage that persists between fights until a landing or jump-arrival repair.
- Updated the shipScreen.json comment and ShipScreenModel docs to describe the ledger as persistent across fights and clarify `nominalLabel` is an at-full copy, not an out-of-combat flag.
- Bank one skill point per combat level landed past 5 via the manager's 'levelup' event, landing in the class ledger whether or not a class is on file yet (spend stays gated).
- Surface the bank in the HUD: the level-up feed line now notes "+1 SKILL POINT" for levels above 5.
- Extend the deck's attract cue to shimmer the SHIP slot when skill points are banked and no ship console is open, with research winning the slot when both are ready.
- Add `bulletHoming` config section and `stepHomingBullets` pass so Mindweave bolts/shells bend toward the nearest live hostile for a capped duration (turn-rate limited, range-limited, own-wing exempt)
- Extend telekinetic wave (`stepTkwaves`) to shove/pull every hostile craft in radius, not just the player; own wing and cloaked craft are excluded
- Extend illusion pop rule so rival-faction enemy tracers can pop their own court's phantoms (all-hostile), while own-wing bolts pass through and unclaimed courts remain player-only
- Tag illusions and tkwaves with `factionId` to support the all-hostile targeting rules
- Switch apex_mauler weapon from single "lance" to 3-bolt "burst" pattern (16 dmg each, fan 0.12 rad) per updated design comment
- Add `dev/faction-battle-sim.mjs`: headless Node harness that runs the real combat stack with Phaser stubs to fight random faction compositions and print win/loss + power reports
- Bullets and seekers now carry their shooter's factionId so a
faction's fire drops rival-faction craft but never its own wing
(classic pack bolts/seekers are unaffected)
- NPC kills via crossfire are silent: no kill count, XP, defeat comm,
or screen shake — the war is the world's, not the player's ledger
- Survivors of a neutral-to-player side peel off once their rivals are
spent, clearing the sky for the director's cadence
- Bump faction cooldown to 40–60 s and update data/encounters tests
- Faction space now deploys the local faction AND one rival as hostile groups
(previously only an opposing faction attacked, local was allies), with both
sized off the level band.
- Each ship's hostility toward the player is stamped from reputation standing
vs `encounters.faction.hostileBelow`; neutral factions are observe-only —
untargetable by guns/skills/escort fire, their bolts/seekers/rams fizzle past
the ship, and they peel off once rivals are spent.
- Add faction-space encounter timers (tighter roll interval + cooldown) in
`encounters.faction`, applied via `ctx.faction` in the director.
- Route all player-targeting paths through a new `_playerPool()`/
`playerTargetable()` gate; add `_peelOff` disengage and rival-live check.
- Update tests for the two-group compose, disposition, and faction timers.
- Reduce data/gates.json activation.researchDuration from 45 s to 20 s so the "Unlock Jumpgates" tech completes faster once all NAV points are discovered.
- Update SystemCategory.js doc comment and fallback default to match the new duration.
- Adjust system-category.test.mjs assertions (duration, save/restore remaining-time math) to the 20 s value.
- Gate locks (bounty + escort) now show "ACCESS TO {system}" instead of a generic level plate, so the player sees which system they need access to first.
- Add `gateLockSystemName`, `postingGateLocked`, and `gateLockCopy` helpers in SideQuests.js with badge/sub/deny forms; fall back to level-gate copy when no system is named.
- Stamp `gateLocked` on postings so JobBoardWindow, GameScene, and SurfaceScene can branch between gate words and the existing level-gate words.
- Update bounty/escort tests to assert the gate lock names the target system (and only that system) plus the opening level.
- Faction-controlled systems now spawn only opposing-faction hostiles (never pirates/neutrals), and local-faction fighters join the player as allies during the fight.
- Rework patrol, bounty, and escort side-quests to the faction war model: patrols are always in-system vs one seeded rival; bounties target a rival's member in a hostile neighbor gated on jump-gate access; escorts rescue a trapped transport in hostile space and deliver it back through the gate into friendly territory.
- Implement per-faction reputation standing (setFaction/changeFaction) with save persistence; faction-owned places now resolve standingFor() via the controlling faction.
- Job boards on faction worlds post only faction-context missions with generic war-voice descriptions ({enemy} placeholder); neutral boards unchanged.
- Update all dev test suites (encounters, bounty-hunts, escort, escort-flow, reputation, sidequests) to cover the new faction rules and gate locks.
- 8 new enemy types (frames 50–57) split 4/4: combo seekers fire flank-angle homing missiles with capped turn-rate; elementals throw darts that bloom into growing, damage-ticking electrified zones
- Balance follows the faction rule (2× damage, 3.5× armor vs pirate raider), documented in combat.json comments
- New `flank_barrage` and `shock_dart` abilities in EnemyAbilities.js with shared `stepShock` frame step wired into CombatSystem.update
- Generalized `_combatBootMode` to accept any id from `factionIds()` so new factions need no code change
- Painted frames 50–57 in ships-enemy.png/.psd; added capital-mindweave-surface-01 reference art
- Tests: dev/zenith.test.mjs (Node), dev/zenith-arena-browser.mjs (Playwright), updated combat.test.mjs roster and sheet-art checks, extended cdp-faction-arena.mjs with zenith tab
- Register 8 Apex (frames 30-37) and 8 Mindweave (frames 40-47) in
data/combat.json enemyTypes at 2x damage / 3.5x armor vs the pirate,
each with a distinct weapon pattern and ability kit
- New shared skill layer (js/combat/EnemyAbilities.js): cloaked ambush,
flank strafe, blink reposition, illusion decoys, telekinetic waves;
wired into CombatSystem update/collide with cloak gating on fire,
targeting, and player skills
- Add sweep/spiral/shell weapon patterns in js/combat/Weapons.js
- New ?combat=apex / ?combat=mindweave duel arena with an in-game
DuelPicker (keys 1-8 / click, 0 = whole pack); plain ?combat demo
unchanged
- Paint the 16 missing spritesheet frames (ships-enemy.png) so the
fighters are visible; add a PNG alpha regression test guarding against
blank cells
- Add dev/cdp-faction-arena.mjs CDP smoke check and extend
dev/combat.test.mjs with faction roster, balance, ability, duel, and
sheet-pixel checks
- Capture hull/shields/energy from the live CombatSystem into record.combat on save
- Stage `combatVitals` in prepareLoad so a panel load restores the damage the run was flying with (clamped, hull floored at 1) instead of always starting full
- Add `repairFull()` to CombatSystem and call it on world landing (startLanding) so the dock heals the ship
- Set `combatRepair` flag in jumpThroughGate's record so a system arrival also triggers a full repair, while panel loads never carry it
- Clear both staged values in resetRunState for fresh runs
- Carry `cargoCosts` through jump records to preserve per-bay pricing across systems
- Raise the ship's mining capacity from build records before applying saved minerals, so holds above the base 250 cap survive load/jump-cut instead of being clamped away.
- Re-assert the EXPLORE story gate after blanket slot re-arming in SurfaceScene to prevent it coming back live pre-'Special Delivery'.
- Move ui_hover SFX from row/node hover to close-button hover in BuildWindow and ResearchWindow.
- Introduce a `compact` hook on CommsHub so scenes can flag which
comms should play as the small right-edge plate.
- GameScene passes `true` for 'visual' comms while combat is active,
keeping the battlefield clear while the clip and voice still play.
- CommsBox builds a header/text/button-free variant scaled to
`box.combat.scale` (2/3) with a configurable right-edge margin.
- Guard `beginText` and `setVoice` for the compact form, which has no
text object or status dot.
- Inferno (Offense, seat 2): flame arcs home on the nearest 2 hostiles
(3 from rank 4) and ignite a burn that ticks on the combat loop's clock;
scales in duration and per-tick damage with rank.
- Nanobots (Passive, no seat): mends 1% of max hull every 2s per point;
a full hull banks nothing.
- Wire flameArc + hullRegen effect handlers in SkillEffects.js and the
arc/burn/step logic in CombatSystem.js.
- Add firstPassiveOf() / hullRegen() to the skill ledger so the combat
loop can read the mend's interval and color off the ranked def.
- Update icon sheet, dev shots, and the browser/combat/data test suites for
the two new skills.
- Combat bands split at level 6 (5–7), 9–12, and 13+ (9–12 cap) with
weighted pirate rosters; compose() now draws `pirateTypes` from the band
roster and GameScene uses it when spawning one-shots.
- Patrol encounters gain tiered bands (levels 6+) with mixed rosters,
fatter rolling waves (up to 20 deep / 6 concurrent), contact-count odds
overrides, and per-tier `xpScale` dials; legacy array rosters normalize
to the weighted table.
- Bounty hunts replace the stubbed marksman band with tuned tiers
(marksman 6–10, deadeye 11–14, apex 15+): growing pickets and escorts,
escort roster mixes, elite armor/damage scales 3×–4×, and `xpScale`
trims so payout pace tracks the curve.
- Tests updated to assert the new band shapes, roster draws, XP scaling,
and tier resolution; skills-bar screenshots refreshed.
- Raise a new GameOverOverlay (scrim, semi-transparent "GAME OVER" headline, Return to Main Menu / Continue) when the player's hull breaks in combat
- Add _playerWentDown beat that freezes the world and owns the screen, gating input and the update loop while active
- Route the death decision through a skill seam (_playerDeathRespawns / _syncDeathFlowRespawn) so a future respawnOnDeath skill can override; default ends the run
- Wire Continue to resume the newest save (SaveManager.latest -> prepareLoad) then scene.restart, mirroring the menu's Continue
- Update combat.json playerShip comment to reflect the real death flow replacing the placeholder respawn in the main game
- Add toxic-takeoff-01.mp4 asset
- SaveManager gains softGet/softPut/softClear and an optional `soft` field in the bank blob, invisible to listSlots/filledCount but counted by hasAny/latest/exportAll.
- latest() now considers the soft record as a Continue target, winning only on a strictly newer timestamp (a same-moment manual slot stays the pick); a soft winner reports slot null.
- SurfaceScene auto-snapshots via captureState on launch and calls softPut in a try/catch so a save failure never blocks take-off.
- MenuScene logs "auto" instead of a numeric slot when resuming the soft record.
- Tests cover soft round-trip, Continue eligibility, latest tie-breaking vs manual slots, persistence across managers, softClear, and exportAll inclusion.
- Enforce the sequential law in `visitWaypoint` for all route kinds: only the first unvisited waypoint logs (bounty's `anyOrder` escape hatch still applies to the kill's check-off)
- Compass target and in-world beacons now track the NEXT waypoint in order instead of the nearest unvisited; only that one pulses while the rest stand dim until their turn
- Contact checklist rows no longer name which waypoint a contact stands on, keeping the ambush hidden from the tracker
- Update data comments and tests to reflect patrols flying 1, 2, 3 in order
- Strung hostiles dim to the data's fx.stunAlpha and show a crackling discharge (Fx.stunArms) while the snare holds, then recover with a release beat when the stun expires or on kill/revive
- Add Fx.stunArms/killStunArms + jagBolt helpers for the per-frame discharge (jagged bolts, sparks, hull stutters) tied to the snare's clock
- Store per-craft stun color/alpha from the bola catch and drive hold/release via _stunHoldFrame/_stunRelease in CombatSystem
- Add dev/stun-shot.mjs CDP script + screenshots, extend combat tests for dim/discharge/release, bump skills-bar test timeout, reformat skills.json, refresh toxic-land video
- Define both skills in data/skills.json with energy, cooldown, ranks, seats,
and per-rank stun/decoy durations
- Implement projectile flight, target intercept, capture radius, and stun in
CombatSystem (launchBoleadoras / _stepBoleadoras / _snagBola)
- Implement Blink: spawn a decoy sprite at the old position, teleport the
hull to a random visible-screen point, and let the decoy absorb aggro and
enemy bolts until its clock expires
- Add bola orb/string visuals and snag readout in Fx.js (bolaStart/Step/Stop,
snag)
- Register new combat SFX events (bola_snag, blink) in CombatSfx.js
- Extend SkillEffects with stunMs/decoyMs helpers and boleadoras/blink handlers
- Update tests: Wraith model checks, Boleadoras/Blink combat behavior, decoy
aggro/fizzle, and SFX event roster
- Add canvas-drawn comet head, arrival fireball, and damage-radius ring textures in Fx.js, replacing the old single-dot streak/ring combo
- Switch all ram-related (and general glow) sprites from ADD to SCREEN blend to fix the "dark box" artifact over semi-transparent nebula backgrounds
- Introduce ember trail particles that linger ~1 s behind the hull during the dash, plus a flickering comet head that rides the ship and is released on impact
- Add `ramBlast()` impact sequence: ignition flash, blooming fireball, exact damage-radius ring, shock ripples, and debris sparks
- Wire in the heaviest camera shake beat (`ram`) and layer the data's sfxEvent under a thunder boom at impact
- Bump skill fx `ringLifeMs` to 480 so the damage-radius ring lingers long enough to read
- Add dev CDP harnesses (`cdp.mjs`, `ram-shot.mjs`, `ram-video.mjs`) and reference stills/video for the charge's three key frames
- Drop the `rankT` text object, its draw/update logic and data spec (`rankTextPx`) so seats only show icon, veil, sweep and seconds.
- Update `_ensureSkillIcons` fallback wording to reference frame/veil/sweep instead of rank stamp.
- Adjust browser test: stop asserting on rank text, recompute seat clip from the seat box center, and remove the rank-pixel regression check.
- Refresh screenshot fixtures (`dev/shots/*`) to match the new seat layout.
- On a confirmed class lock-in, GameScene now sets `_skillTreeAfterPick` and, once the class window's close beat finishes, delays 320 ms then opens SkillWindow with `source: 'classPick'`, handing the player straight into spending their banked points.
- Add an e2e check in classes-browser.test.mjs that a confirmed pick closes the class console and opens the skill tree with spendable points.
- Refresh skills-window screenshot fixtures to match current rendering.
- Introduce `escortActive` and `escortActivate` strings in sidequests.json so escort contracts display distinct copy instead of the generic patrol text.
- Update JobBoardWindow status and action buttons to branch on `category === 'escort'`, falling back to existing bounty/patrol labels.
- Add `_syncEscortSpeed()` in GameScene: when an escort contract is open, the transport is on screen, and combat is inactive, cap the player hull and escort fighters at the transport's own top speed; restore each craft to its normal/roster speed otherwise.
- Fighters keep their base speed on `_baseSpeed` so the restore is exact even after a jump re-form.
- Per-frame writes only on change to avoid steady-state churn or steering disturbance.
- Hook the sync into `updateSideQuestRoute()` so it runs each frame off the escort lane (no-op elsewhere).
- Cover the new behavior in `escort-flow.test.mjs`: match applies in view/out-of-combat, restores on combat open, transport off-screen, and contract end; give test crafts distinct speeds so match and restore are observable.
- Stamp `closedByButtonAt` in `SkillWindow.close()` so the scene can tell a console ✕ click apart from a world click on the same frame.
- Add a 20ms guard in `GameScene` that suppresses the fly-here action when the close just fired, matching the mining menu and comms panel contract.
- Add `skillWindow` to the full-screen click guard so clicks don't
steer the ship or fold panels while the skill tree is open.
- Include the skill window in the shared `_frozen` condition so the
world parks (throttle/velocity cleared) behind it, matching the
class console and level-up marquee contract.
- Add the fill graphics to the container before the label so it paints behind the text instead of covering it at 0.8 alpha
- Use C.dim instead of C.faint for inert labels and borders, which were too dark on the panel fill
- Swap tag/text colors so inert tags stay readable while active tags dim appropriately
- Refresh dev screenshots to match the corrected rendering
- Replace per-skill rows with a scrollable tile grid (authored `node` cells or
auto-layout by dependency depth), elbow join lines, rank badges and empty sockets
- Add detail drawer at bottom third showing the selected skill's copy plus ADD A SKILL
POINT, RESET and ASSIGN/CLEAR SEAT actions
- Introduce a console session in SkillState: points spent while open are PENDING and
resettable; closing commits them; reset cascades down a branch bought this session
or refuses when it would strand a committed point
- Wire GameScene onReset/onClose doors, toast copy and passive re-roll on refund
- Move all tree geometry/palette knobs into data/skills.json (window.tree) with clamped
fallbacks in SkillModel.windowSpec()
- Add pure treeLayout()/classTreeLayout() to SkillModel for canvas-free layout tests
- Expand dev/skills-window-browser.test.mjs to verify regions, tile placement, session
lifecycle, scroller (wheel/drag/thumb), and pixel-prove the gold join line
- Update README, PROJECT_NOTES and screenshot fixtures
- New `data/skills.json` (registered in manifest) owns every number/string for the
per-class skill trees, type taxonomy, seat table, bar look, and window copy;
code only reads behavior off it so adding a third skill is a data edit.
- Pure skill model + ledger (`js/skills/SkillModel.js`, `SkillState.js`): rank
points from the class bank, spend-through-unlock edges, seat bindings, save
round-trip via `record.playerSkills` (old saves load as an empty ledger).
- Combat runtime in `CombatSystem.js`: cast pipeline with per-skill recharge
clocks, energy spend/regen (now on by default), Ramming Charge dash + arrival
blast (4x..12x weapon damage by rank), Reinforced Plating hull pool re-rolled
from base so it never compounds; charge passes through the pack and settles once.
- UI: full-screen Skill Tree console (`js/ui/SkillWindow.js`) opened from the ship
console's SKILL TREE door (lit when the class owns a tree), combat bar seats draw
icon/rank/cooldown/affordability states with click-to-assign and right-click clear,
keys 1-8 cast or place.
- Evidence: new Node + Playwright suites (`skills.test.mjs`, `skills-combat.test.mjs`,
`skills-bar-browser.test.mjs`, `skills-window-browser.test.mjs`,
`skills-devdoor-browser.test.mjs`) plus screenshots in `dev/shots/`; README and
PROJECT_NOTES document the layer and dev doors (`?skill`, `?skilltree`).
- Introduce data/classes.json as the single source for roster, copy, accents, icon frames, perk numbers, and console chrome
- Add ClassModel (pure reads), ClassState (idempotent run ledger + save round-trip), and Aiming (intercept solve + barrel geometry) so perks are testable without Phaser
- Wire CombatSystem to the class ledger: Vanguard dual-cannon volley, Wraith predictive aim with a max-lead fallback to raw aim; cooldown unchanged
- Add full-screen ClassWindow console over the frozen GameScene (looping muted feed, Trestle briefing, themed tabs, PICK confirm), opened once at level 5 when combat is quiet or via the ship console door / ?level5 dev param
- Persist record.playerClass in saves and clear it on New Game; ship console now shows a class plate with crest + banked skill points instead of the choose-class door
The level-up marquee, jump clip, and ack comm freezes gate world
drivers but not physics: the Arcade world still steps, so coasting
bodies kept gliding through the beat. Add _parkForFreeze() to zero
ship velocity/acceleration and all enemy/ally body motion every
frozen frame, killing any mid-freeze injection on the next tick.
A jump cut destroys all cameras and rebuilds a fresh main, leaving the stored split pointing at dead cameras. Worse, `main.ignore()` on the old main sets the id bit that the new main reuses, so objects assigned to the UI pass get ignored by the only live pass — rendering nowhere (the "clip plays in the background" bug).
- Add `liveSplit()` which validates the stored split against the scene's live camera list and clears it when stale
- Route `ensureUiCameras`, `assignUi`, and `assignWorld` through `liveSplit()` so a stale split is dropped and the pipeline falls back to a single pass
- Add three arid theme tracks (arid-01/02/03) and a new spacestation-03 track
- Point station 15-17 at the new arid tracks instead of reusing lava
- Add spacestation variant 2 and reorder existing station frame mappings
- Every N pirates killed in a system (default 10) grants +1 standing to all settlements in that system, configured via data/reputation.json bonuses.pirateKills
- Track per-system kill tallies as registry-backed run state, persisted through SaveData.js capture/prepare/reset with normalization and legacy-save fallback
- Wire the payout into GameScene.recordPirateKill, called from CombatSystem's onKill seam, clamping at max standing and showing a toast when at least one place moves
- Add reputation tests covering the milestone data shape, standing writes, save round-trip, garbage-entry rejection, legacy-load, and New Game reset
- Hold jumps while the escort convoy is linked or setting down: the
player must have the transport, its surviving escorts, and the gate
all on screen before leaving the system.
- Close the destination world's landing door until the transport has
reached it (phase delivered) or been destroyed (phase failed), so a
lost convoy never blocks the reset remedy.
- Arm OAC's escortDeny visual beat plus flat toasts on both denials.
- Register the transport captain character and add neutral-transport-link
(convoy link greeting) and neutral-transport-delivery (delivery thanks)
comms with audio pools, all once:false so they re-arm per contract.
- Require the convoy to be on screen before the delivery begins, not just
the destination world.
- Update escort-flow and comms tests for the new witness gates, landing
door, and transport captain beats.
- Add `bonuses` config (sideQuest 10%, perGoodSold 2%) to reputation.json
- Implement `Reputation.creditFraction()` with sub-point pending credit that pays out whole points across multiple sales
- Wire GameScene to award the issuer's standing on side-quest claim and on goods sold in the shop
- Serialize/deserialize pending credits through save/load round-trip
- Add tests covering crediting, clamping, home-world pinning, and save round-trip
- `escortCompassTarget()` now emits a `destination` kind carrying the world's discovery id (a planet's name / a station's settlement id) instead of reusing the `waypoint`/idx shape, which autopilot resolved to `route.waypoints[0]` — the escort's rendezvous point in the destination system
- `autopilotTo()` gains an `escort:obj:` branch that resolves the payload to the world's discovery entry, and maps a stale content-record-id payload onto the live entity so the click no longer silently no-ops
- `_escortDestinationObject()` now matches the live planet/station by discovery name (a Planet's `name` is its class label) and returns the entity's `discoveryId` for compass/autopilot identity
- Compass offscreen listing suppresses the destination world's own slot when the escort arrow stands it, mirroring the existing route-gate rule
- Add headless unit test (`dev/escort-compass.test.mjs`) covering planet/station destinations, the stale record-id payload, and the old `patrol:wp:` shape, plus a Playwright repro (`dev/escort-compass-click.mjs`) that drives a real mouse click on the chip in a live browser
- Switch the escort quest compass arrow and its default fallback from green (#7ce8a4) to magenta (#e87cf5) in both data/sidequests.json and GameScene.js so the escort marker is visually distinct.
- Replace near-certain short-interval rolls with a middling 40–60% chance on a 20–30s interval and a longer 15–30s cooldown, targeting roughly 2–8 engagements per run
- Update the escort director comments in sidequests.json and GameScene.js to describe the new tuning
- Replace the instant "delivered" completion with a two-phase flow:
phase `delivering` (convoy breaks off, glides beside the destination
world, fades out) followed by the seal/payout on fade close.
- The delivery now requires the destination world to be visible in the
camera's world rect (`_escortObjectOnScreen`) rather than merely within
a distance radius.
- Add `_runEscortDelivery` (time-driven ease-out approach + alpha/scale
fade, frame-rate independent) and `_sealEscortDelivery` (tear down
convoy, close phase, complete quest, toast).
- Extend `normalizeEscort` to accept the new `delivering` phase.
- `_escortDestinationObject` now returns the world's rendered radius so
the landing slot can anchor just off the planet/station disc.
- Add `escortArrivingToast` UI string for the "convoy inbound" moment.
- Update escort-flow test to assert the delivering → delivered sequence
and add a standalone repro script (`dev/escort-delivery-repro.mjs`).
- New "Escort" contract type on neutral job boards (minLevel 3): the player
links a persistent transport + 1-2 escort fighters at a local waypoint,
flies them through a seeded 1-2 hop gate route to a planet/station, and
delivers with the sky clear.
- TransportShip extends NeutralShip with a lapsed break-off window and a
`persistent` flag so CombatSystem's active/neutralOnly gates skip it; the
convoy never holds the jump gate or landing door open.
- Dedicated escort EncounterDirector (9-18s rolls, 85-100% chance) fires
mixed-fighter waves ~5x more frequently than normal encounters; one wave at
a time via the patrol contact's law.
- Transport loss fails the contract; landing on a planet resets it with a
fresh rendezvous in the current system and full-armor transport, same
destination.
- 20 unique escort dossier descriptions, reward band raised to 120-175 CR.
- Three new test suites: escort.test.mjs (model), escort-flow.test.mjs
(scene flow), escort-browser.test.mjs (real browser boot + board render).
- Add `_encountersLockedByQuest()` to GameScene: while the delivery
chain is live (not yet completed AND claimed), keep the encounter
director from rolling. Quests disabled = gate off too.
- AND the new scene-side hold into `encounters.update`'s `allowed` so
the pure clock stays untouched; document the contract in Encounters.js.
- Ship new transport sprite assets (ships-transport.png/.psd).