diff --git a/internal/plugin/dnd_expedition_cmd.go b/internal/plugin/dnd_expedition_cmd.go index fdc2e79..2df5a29 100644 --- a/internal/plugin/dnd_expedition_cmd.go +++ b/internal/plugin/dnd_expedition_cmd.go @@ -554,6 +554,14 @@ func (p *AdventurePlugin) expeditionCmdRun(ctx MessageContext) error { if r.initErr != "" { return p.SendDM(ctx.Sender, r.initErr) } + // Emergence seam: a natural run-complete (boss down / dead-end node) + // surfaces the player alive just like an extract or abandon — roll pet + // arrival here too. The roll lives in the real callers, not in + // runAutopilotWalk, so the sim path (which calls the walk directly) + // never fires arrival DMs. See maybeRollPetArrivalOnEmerge. + if r.reason == stopComplete { + p.maybeRollPetArrivalOnEmerge(ctx.Sender) + } return p.streamFlow(ctx.Sender, r.stream, r.finalMsg) } diff --git a/internal/plugin/expedition_autorun.go b/internal/plugin/expedition_autorun.go index 52f7bcb..ae251ba 100644 --- a/internal/plugin/expedition_autorun.go +++ b/internal/plugin/expedition_autorun.go @@ -154,6 +154,11 @@ func (p *AdventurePlugin) tryAutoRun(e *Expedition, now time.Time) error { // "no expedition" / "no run" — race with abandon/extract. Silent. return nil } + // Emergence seam: a run-complete reached by the background ticker is + // still a live emergence — roll pet arrival. See maybeRollPetArrivalOnEmerge. + if r.reason == stopComplete { + p.maybeRollPetArrivalOnEmerge(uid) + } if !shouldDMAutoRun(r) { return nil }