diff --git a/assets/images/ships-transport.png b/assets/images/ships-transport.png new file mode 100644 index 0000000..6aaf88b Binary files /dev/null and b/assets/images/ships-transport.png differ diff --git a/assets/images/ships-transport.psd b/assets/images/ships-transport.psd new file mode 100644 index 0000000..485000c Binary files /dev/null and b/assets/images/ships-transport.psd differ diff --git a/js/combat/Encounters.js b/js/combat/Encounters.js index 1d63dea..148579e 100644 --- a/js/combat/Encounters.js +++ b/js/combat/Encounters.js @@ -8,8 +8,12 @@ * * WHEN — while the system allows encounters (NEUTRAL — faction null —, * not home, no wave run in flight, the player upright; the scene - * passes `ctx.allowed`, `encounterAllowed` is the pure gate), a - * ROLL lands on the interval schedule (data/combat.json → + * passes `ctx.allowed`, `encounterAllowed` is the pure gate) AND the + * scene's own quest hold stands off — GameScene ANDs in + * `_encountersLockedByQuest`, the 'Special Delivery' gate: the rolls + * stay off until that quest is completed and claimed (a scene-side + * state, so it never crosses this module's pure contract). A roll then + * lands on the interval schedule (data/combat.json → * encounters.rollIntervalMin..rollIntervalMax, seconds); each roll * fires at chanceMin..chanceMax probability. ONE AT A TIME: the * schedule holds while an encounter is engaged. diff --git a/js/scenes/GameScene.js b/js/scenes/GameScene.js index 37f9aab..0149296 100644 --- a/js/scenes/GameScene.js +++ b/js/scenes/GameScene.js @@ -2621,7 +2621,8 @@ export class GameScene extends Phaser.Scene { // returns the composition to spawn — off-screen, streamed in by // their own AI (the clock is pure; the scene stays the spawner). const comp = this.encounters.update(_time, { - allowed: encounterAllowed({ + allowed: + encounterAllowed({ ship: this.ship, isHomeSystem: this.isHomeSystem, faction: this.systemRecord ? this.systemRecord.faction : null, @@ -2630,7 +2631,7 @@ export class GameScene extends Phaser.Scene { this.combat.waveRun.state !== 'done' && this.combat.waveRun.state !== 'cancelled', playerDown: this.combat.playerDown, - }), + }) && !this._encountersLockedByQuest(), // the 'Special Delivery' hold live: this.combat.active, xpLevel: this.combat.xp.level, }); @@ -3754,6 +3755,22 @@ export class GameScene extends Phaser.Scene { return cap !== '' && cap === name; } + /** + * The random-encounter hold (the 'Special Delivery' gate): the run's + * first flights into neutral space stay calm while the delivery chain + * is live — the director rolls (EncounterDirector, the pure clock) + * only once 'Special Delivery' is COMPLETED AND CLAIMED (the same + * witness the run's chain uses — QuestState.isClaimed; the claim is + * the scene's own, paid out only on a fully satisfied quest). Quests + * off (the config kill-switch, data/game.json) = the gate is off too: + * with no quest there is nothing to wait on. + * @returns {boolean} true while random encounters stay locked + */ + _encountersLockedByQuest() { + if (config.get('quests.enabled', true) === false) return false; + return this.questState?.isClaimed?.('special_delivery') !== true; + } + /** * LAND (commsAction) — the surface sequence: SurfaceScene starts ON * TOP of this scene, handed the world's planets.png sheet frame so it