Skip to content

feat: Man0g1 "Souls Gone Wild" — implement the White Wolf Gate escort duty + fix the five static bugs (#41)#204

Merged
swstegall merged 15 commits into
developfrom
feat/issue-41-man0g1-souls-gone-wild
Jul 9, 2026
Merged

feat: Man0g1 "Souls Gone Wild" — implement the White Wolf Gate escort duty + fix the five static bugs (#41)#204
swstegall merged 15 commits into
developfrom
feat/issue-41-man0g1-souls-gone-wild

Conversation

@swstegall

Copy link
Copy Markdown
Owner

Closes #41.

Summary

Implements Man0g1 "Souls Gone Wild" end-to-end: all five static bugs fixed, and the fully-stubbed White Wolf Gate escort duty (SEQ_065) built as a real content instance — the man0l1 Sisipu-escort machinery re-instantiated for Gridania with zero Rust changes (every content subsystem proved name-agnostic).

Research base: pms + MeteorReborn source archaeology, the Mirke Menagerie Loremonger transcript, the 7 linked playthrough videos (OCR-mined via youtube-watcher into full storyboards), bahamut-client-data sheets, and the wire-proven man0l1 escort (#46/#199).

Static fixes (issue items 1, 3–7)

# Fix Evidence
1 SEQ_003SEQ_005 after the Roost scene pms never declares SEQ_003 (StartSequence(nil)); MeteorReborn declares it but nothing ever advances 3→5; AetheryteParent's 110006 arm gates on SEQ_005
3 getJournalMapMarkerList undeclared-data nil-index removed
4/5 MRKR_KID_TRIGGER=11000602, MRKR_GATE_TRIGGER=11000603 declared + markers returned (Miounne beats included) MeteorReborn man0g1.lua:123-125; quest_marker.csv:108-110
6 onNotice "Test" message + doubled EndEvent removed gate tutorial (processEventTu_001) kept
7 Hereward/Geva beat pays 2000 gil (not the TODO's 1000) Geva's own line: "An even two thousand gil, no more." (Mirke + Part 3 footage)
+ Fufucha's debrief pays 3000 gil "You obtain 3000 gil." on film (Part 6, 01:12)

The escort duty

  • startMan0g1Content — the wire-proven 10-step entry (rescue latch → journal → CreateContentArea("Man0g101", …, zone 150) → deferred director kick → processEvent170 duty-start CS → EndEventDoZoneChangeContent spawnType 16). Retail CS pair confirmed from Mirke: 170 = duty start, 180 = end cutscene (the beat the old stub played without the duty).
  • SimpleContentMan0g101.lua — Powle leads the trail, Sansa tags along, neither fights (retail: "Do something! You are an adventurer, aren't you?"); ankle-biter ambushes engage the player (same chigoe family as man0l1 — attested on film in this duty too); either kid dying or the 30-min expiry fails (SEQ_060 rollback + gate eject + retry); kid-hit "Ouch!" barks; the full retail Mirke bark set; duty banners; minimap halo on Powle (moveState 2, man0l1 Sisipu escort (Treasures of the Main): player can't auto-attack Ankle Biters (no TP/skills/EXP) — stale registry-zone, reassign_zone missing on content-warp path #199 semantics); arrival → escortComplete.
  • QuestDirectorMan0g101.lua — veil dismissal → park → processEvent180SEQ_070 + unbind → ContentFinished → stump-scene warp.
  • SEQ_065 rescue arm — relogs/wedged saves roll back to SEQ_060 (man0l1 SEQ_050 pattern).
  • seed/074 — fills + spawns the five trigger classes that were never spawned upstream in either reference (1090200–1090204: empty classPath, zero spawn rows — the CNJ/kids/gate/stump pushes were all dead beyond the escort stub); adds BTN_TRIGGER's missing pushWithCircle + pmeteor's own station for it; Powle/Sansa as FighterAlly-scripted battle npcs wearing the kids' real names (displayNameIds 1000029/1100025 — non-standard mapping) + cloned appearances; zone-150 ankle-biter group; and the five missing private-area registrations the quest tail addresses (155 L0/3/4, 150 L0/1 — also absent upstream).

Provisional geometry (the one open round)

The duty currently runs a short, clearly-flagged placeholder arc beside the Lifemend Stump (wiki marker zone 150 ≈ (-800, 20, -1050)). Next round: record the real White-Wolf-Gate→stump walk (inbound 0x00CA breadcrumbs, the man0l1 round-7f recipe) — the TRAIL, the gate-trigger station, and the ambush points all re-home from that recording. Also flagged for that round: whether White Wolf Gate needs a 155↔150 seamless-bound row, and live-verifying the 150 private-area stump scenes. Bark say-ids await a man0g1 quest-sheet DAT decode (literal-text fallback ships meanwhile).

Verification

  • All six CI gates green: fmt --check, clippy -D warnings, build --locked, test --workspace --locked, content-test 9/9, smoke-localSMOKE_OK all services.
  • luac -p clean on all three Lua files (the content script/director only load when the duty starts, so the gates alone don't parse them).
  • Upstream-diff verdicts (SEQ_003, markers, gil idiom, dead startMan0g1Content, trigger/private-area absence) each confirmed against both pms and MeteorReborn.

swstegall added 15 commits July 5, 2026 19:49
… duty + fix the five static bugs (#41)

Static fixes (issue items 1, 3-7):
- SEQ_003 -> SEQ_005 after the Roost Echo scene. Upstream truth: pms
  never declares SEQ_003 (StartSequence(nil)); MeteorReborn declares
  SEQ_003=3 but nothing ever advances 3->5, and AetheryteParent's
  110006 arm gates on SEQ_005 (unlike man0l1, whose aetheryte checks
  SEQ_003) -- the 3->5 hop never existed upstream.
- getJournalMapMarkerList: removed the undeclared-`data` nil-index,
  declared MRKR_KID_TRIGGER=11000602 / MRKR_GATE_TRIGGER=11000603
  (MeteorReborn parity; sheet rows in bahamut-client-data
  quest_marker.csv) and returns Miounne's marker on the
  talk-to-Miounne beats.
- onNotice: dropped the "Test" SendMessage + doubled EndEvent; the
  gate tutorial (processEventTu_001) stays.
- Hereward/Geva beat now pays 2000 gil -- retail dialogue "An even two
  thousand gil, no more." overrides the upstream "--give 1000g" TODO.
- Fufucha's debrief pays 3000 gil ("You obtain 3000 gil." on film,
  playthrough Part 6 01:12).

The escort duty (issue item 2) -- net-new, the man0l1 machinery
re-instantiated for Gridania (zero Rust changes; all content machinery
is name-agnostic):
- startMan0g1Content: the wire-proven 10-step entry (rescue latch ->
  journal -> CreateContentArea("Man0g101", zone 150) -> deferred
  director kick -> processEvent170 duty-start CS -> EndEvent ->
  DoZoneChangeContent spawnType 16).
- SimpleContentMan0g101.lua: Powle leads the TRAIL, Sansa tags along,
  neither fights (retail: "Do something! You are an adventurer,
  aren't you?"); ankle-biter ambushes engage the PLAYER; either kid
  dying or the 30-minute expiry fails the duty (SEQ_060 rollback +
  gate eject + retry); kid-hit "Ouch!" barks off HP snapshots; the
  full retail Mirke bark set (literal-text fallback until the man0g1
  quest-sheet say ids are DAT-decoded); duty banners
  (protect/bound/timer), minimap halo on Powle (moveState 2, #199
  semantics), duty music, arrival -> escortComplete signal.
- QuestDirectorMan0g101.lua: questBaseRewardSeting veil dismissal ->
  park on escortComplete -> processEvent180 end cutscene ->
  StartSequence(SEQ_070) + unbind -> ContentFinished -> stump-scene
  warp (zone 150 PrivateAreaMasterPast 0).
- SEQ_065 rescue arm (man0l1 SEQ_050 pattern): relogs/wedged saves
  roll back to SEQ_060 so the gate re-arms.
- seed/074: the five never-spawned trigger classes (1090200-1090204,
  empty upstream in BOTH pms and MeteorReborn) get the man0l1
  push-trigger recipe + spawn stations; BTN_TRIGGER 1090046 gains its
  missing pushWithCircle and pmeteor's own spawn station; Powle/Sansa
  as FighterAlly-scripted battle npcs wearing the kids' real
  displayNameIds (1000029/1100025) + cloned appearances; zone-150
  ankle-biter group (retail-calibrated lvl 5-7 / hp 60); the five
  missing private-area registrations the quest tail addresses (155
  L0/3/4, 150 L0/1 -- absent upstream too).

PROVISIONAL geometry, replaced when the White-Wolf-Gate->Lifemend-
Stump walk is recorded (the man0l1 round-7f 0x00CA recipe): the duty
trail runs a short arc beside the stump (wiki marker zone 150
~(-800, 20, -1050)); the gate trigger + ambush points re-home from
the recording.

Sources: pms + MeteorReborn source archaeology, Mirke Menagerie
Loremonger transcript, the 7-part retail playthrough (OCR-mined),
bahamut-client-data sheets, and the man0l1 escort (#46/#199).
…s after the warp (#41)

Live 2026-07-06 02:03:19: talking to Miounne played the cutscene then
hung. The drain shows why: the pms port ships SetLoginDirector
COMMENTED OUT, so the burst missed the login-routing tell
(processor.rs is_login_scoped_burst) and the AfterQuestWarpDirector
noticeEvent kick was direct-dispatched pre-wipe ("KickEvent dispatched
directly to client (runtime drain)") in the same flush as the same-map
resident-geometry wipe that destroys its owner actor. The client drops
the answer for a wiped owner, onNotice/processEventTu_001 never fire,
and the kick-armed staging veil never clears — the exact class the
man0l1 Baderon menu-lock fix documented ("Round-3 live test").
Upstream never hit this because pmeteor's same-zone DoZoneChange
doesn't wipe.

- player:SetLoginDirector(director) uncommented — the zone-in bundle
  now re-emits the director spawn + kick post-reload on a loaded actor
  table (the canonical sequence in AfterQuestWarpDirector.lua, which
  already routes 110006 to onNotice).
- seq000_onTalk's Miounne branch returns true and onTalk early-returns:
  the fall-through shipped a second EndEvent + UpdateENPCs AFTER the
  DoZoneChange (drain commands 8-9) — the mid-reload EndEvent /
  desktopWidgetMode-16 hazard.

luac -p clean; content-test 9/9.
…dania zone halves, pin scene-PA warps to 155 (#41)

Live 2026-07-06 follow-up: 'the Conjurers' guild loads no new NPCs' is
literal — the ENTIRE SEQ_015 CNJ cast (Soileine + the Stillglade Echo
scene: O-App-Pesi, Ingram, Hetzkin, Gugula, Biddy, Challinie, scene
copies of Yda/Papalymo/Swethyna) has ZERO spawn rows in every upstream.

Gridania is two seamless halves in one coordinate space (bound 029/1):
155 fst0Town01 = north (Roost + all 17 upstream scene-PA rows), 206
fst0Town01a = south (guild district, 154 populace rows — Hereward,
Growery, aetheryte plaza). Boundary z ~ -1270.

- seed/075: Soileine at the Fane door (206 public) + the 9-NPC Echo
  cast in 155 PrivateAreaMasterPast 0 around the quest's own warp
  anchor (positions provisional); re-homes the 074 CNJ/gate/BTN
  triggers to 206 (their stations are all south of the boundary — the
  074 'translation' of BTN's upstream 206 row was wrong).
- man0g1.lua: the five public-fired scene warps (CNJ push, Opyltyl
  x3, Burchard) now pin zone 155 via explicit DoZoneChange —
  WarpToPrivateArea resolves the CURRENT zone's PAs, so fired from the
  206 half it missed and fell back to public. The SEQ_090 PA4 hop
  stays WarpToPrivateArea (fires from inside a 155 PA).

luac -p clean; content-test 9/9.
…spawns crash the client (#41)

Live 2026-07-06 ~04:38: Wine died walking up to the Conjurers' Guild.
seed/075's cast rows spawn classes that ship with EMPTY classPath in
BOTH upstreams (only displayNameIds are real): Soileine 1000234 (206
public — streams in as you approach the Fane door) + the six Echo
vets. A classPath-less actor gives the client nothing to load — the
established crash family (chigoe DepictionJudge lesson, populace
flavor). Yda/Papalymo/Swethyna were already filled, which is why the
Roost scene worked and the Fane approach crashed.

Fill = the Miounne populace recipe (PopulaceStandard + flags 19 +
talkDefault/noticeEvent), guarded WHERE classPath=''.
…h) + Fane arrangement/poses from retail refs (#41)

Exit crash (live 2026-07-06 05:17:39): Swethyna's WarpToPublicArea from
the 155 scene PA stranded the player in 155-public deep in the 206
half — a world-state retail never produces. The first 206-band actor
partner-streamed into that half-loaded client (Kinnison, 0x46700082,
data fully healthy) froze the render thread (socket pinged 27s more,
then exit). Fix: every PA exit now lands the correct public half
explicitly — Swethyna→206 Fane door, kids-exit→206 Growery,
Nuala→206 Barracks, stump-exit→155 north (was landing 150-public).

Arrangement/poses per the user's retail video frames:
- Yda no longer lies on the floor: seed/075 copied her town
  animationId 1007 = the man0g0 collapsed-in-forest pose pack; scene
  rows now animationId 0.
- Post-cutscene: Yda+O-App-Pesi+Swethyna tight trio in front of the
  player; vets pushed to the balcony ring; Soileine scene copy added
  (visible in both retail frames).
- Public Fane: Soileine moved from the door to the reception counter,
  Hetzkin added beside her (retail frame). 'Foforyo'/'Marcelle'/
  'Concessa' remain unmapped (no name→class table server-side).

luac -p clean; content-test 9/9.
…tlock) + Fane desk nudge (#41)

Live 2026-07-06: the Botanist-guild (Greatloam Growery) instance loads
with no NPCs → the SEQ_050 emote drill can't complete → softlock. Same
dead-upstream family as the CNJ cast: the six kids + Opyltyl + Fufucha
had ZERO rows in the SEQ_050 dance-practice PA (155 PA1) and SEQ_055
kids-chat PA (155 PA2) — the kids exist only in a different quest's
scene (etc5g1_*, 206 PA5), Opyltyl only in public 206. Fufucha
(1000237) + Nicollaux (1000409) also ship classPath-empty (crash
class) — filled first.

- seed 078: 8-NPC casts in both instance PAs (class ids match the
  quest onEmote/onStateChange handlers); positions provisional around
  each scene's warp anchor.
- Fane desk: Soileine + Hetzkin pushed ~6u deeper behind the counter
  (were mid-room per the live report).

Recovery for the stuck save: the idempotent seed loader applies 078 on
rebuild+reboot without a wipe; login re-warps to PA1, or Opyltyl (206
public) re-enters the drill.

Verified seed applies to a copy of the live DB (all 8 drill NPCs
classPath-live, zero remaining empties); content-test 9/9; smoke
SMOKE_OK.
…auto-cutscene), lift underground kids to a retail arc (#41)

Live 2026-07-06 with retail ref frames:
- Opyltyl was spawned inside the drill (PA1) + chat (PA2) instances by
  seed/078, but his SEQ_050 onTalk branch RE-WARPS the player into PA1
  — brushing him mid-dance resets the drill and blocks the auto-advance
  to the Zezekuta/Fufucha cutscene ('he bugs it'). Retail has only
  Fufucha + the six kids on the grounds. DELETE both instance rows; his
  public 206 entry NPC stays (SEQ_040 entry + re-entry recovery intact).
  The emote-completion auto-cutscene (onEmote: all 6 flags ->
  processEvent150 -> SEQ_055) was already correct — Opyltyl was the
  blocker.
- Underground kids: seed/078 spread them to z<=-1510 where the Growery
  ground rises above the anchor's y=12 (stub navmesh, no ground query)
  so Elyn/Nicollaux sank. Re-arced within the anchor's proven-flat band
  (z ~ -1494..-1502), spread in x to match the retail left-to-right
  order (Ryd..Nicollaux; ref frame). SEQ_055 chat kids pulled to the
  same band.

Verified against a copy of the live DB (Opyltyl absent from both PAs,
kids in-arc at y=12); content-test 9/9; smoke SMOKE_OK.
…te actually fires (#41)

Live 2026-07-06 (user pointed at man0l1's working Sisipu emote step):
clapping at a kid did nothing. A player emote only reaches the quest
onEmote hook (processor.rs EventStart event_type 3) when the TARGET's
gamedata_actor_class carries an emoteEventConditions entry matching the
emote; man0l1's SISIPU_EMOTE (1000155) has that block, the kid classes
(only talkDefault+noticeEvent from seed 076/078) did not, so the client
routed the emote to the free EmoteStandardCommand and the drill could
never register. Each kid now carries one emoteEventCondition for the
emote it teaches (Nicollaux Clap 107, Sansa Bow 105, Powle Cheer 106,
Aunillie Beckon 108, Ryd Surprised 101, Elyn Lookout 122 — matched to
man0g1.lua onEmote's DoEmote calls). Wrong emote at a kid matches no
condition and no-ops (retail). Also tightened the drill cluster to
within ~6u of the known-flat spawn anchor (the seed/079 spread
straddled a fence/ravine and re-buried Nicollaux).

Verified against a copy of the live DB (all 6 emoteIds correct);
content-test 9/9; smoke SMOKE_OK.
…rror (#41)

Live 2026-07-06: after the man0g1 dance drill's last emote the quest
did not auto-advance to the Zezekuta/Fufucha cutscene. Root cause is an
engine binding bug, not data: LuaQuestDataHandle.GetFlags/GetFlag serve
from the immutable snapshot (this.flags) taken when quest:GetData() was
called, but SetFlag only QUEUED a QuestSetFlag command without touching
that snapshot. So in the 6th onEmote, 'data:SetFlag(FLAG_EMOTE_DONE6)'
then 'bit32.band(data:GetFlags(), 0x7E) == 0x7E' on the next line still
read the pre-6th value (0x3E != 0x7E) and the completion beat
(processEvent150 -> StartSequence SEQ_055) never fired. The log shows
six QuestSetFlag drains and zero processEvent150.

Fix: SetFlag/ClearFlag are now add_method_mut and update this.flags
immediately (|= / &= ~ the bit) in addition to queuing the command
(which still owns DB persistence). Same write-through-the-mirror rule
the SetMod / LuaParty:AddMember bindings already follow — hardens any
quest that sets-then-checks a flag in one hook. (Counter mutators have
the same latent staleness but aren't hit same-call today; noted.)

fmt/clippy/build/test all green; content-test 9/9; smoke SMOKE_OK.
…warp (#41 escort trigger)

Live 2026-07-06: walking to the White Wolf Gate started no escort duty
(softlock). Log: 'quest ENPC broadcast skipped — no live NPC
class_id=1090202' at the SEQ_055->060 warp instant. Root cause: the
KIDS_TRIGGER push does StartSequence(SEQ_060) THEN DoZoneChange to the
gate zone (206), so SEQ_060 onStateChange armed GATE_TRIGGER (1090202,
in 206) while the player was STILL in the origin zone (155) -> the
cross-zone broadcast found no live NPC and it was never re-armed. The
drill kids + CNJ trigger worked only because the player was already in
those triggers' zone when their sequence armed.

The seamless path already re-establishes active-quest ENpcs on the
destination (handle_update_position 3c, man0l1's Zephyr Gate); explicit
DoZoneChange warps did not. handle_zone_in_complete now runs the same
idempotent re-establish (apply_quest_update_enpcs per active quest) once
the destination zone is current. GUARDED: skipped inside a content
instance (active_content_script set) so it can't re-fire the escort
SEQ_065 one-shot FLAG_ESCORT_HANDOFF rescue arm and roll the duty back;
skipped when a deferred login warp just replayed (its own RX 0x0007
re-establishes). Also fixes the post-escort SEQ_070 stump triggers,
same cross-zone-arm class.

fmt/clippy/build/test green; content-test 9/9; smoke SMOKE_OK.
…and-mark (#41)

The provisional GATE_TRIGGER (Greatloam Growery, -235/-1455, out of
bounds ~460u from the gate) never started the duty. Decoded the
player's exact position at the true White Wolf Gate from the previous
session's last 0x00CA packet (peer 64785): x=-194.73 y=3.54 z=-1021.33
rot=-1.642. Moved the trigger there. Zone can't be confirmed 155 vs 206
from the (rolled-off) log, so hedged: spawn 1090202 at the gate coords
in BOTH zones — a single XZ is in exactly one zone so the other copy is
inert, no double-fire, and whichever half it is the trigger arms via the
cross-zone re-establish (665ee92).

Seed-only; content-test 9/9; smoke SMOKE_OK. Reboot (no rebuild) applies.
092_remove_bluebadger_closed_gate.sql is a legitimate DELETE-only
data-removal migration, but every_migration_decompresses_to_sqlite_sql
only whitelisted CREATE TABLE / INSERT / ALTER TABLE / UPDATE, so it
failed the non-empty-migration guard. Add DELETE to the token union
(the same way UPDATE was added for the data-fix path).
@swstegall
swstegall merged commit 548e219 into develop Jul 9, 2026
6 checks passed
@swstegall
swstegall deleted the feat/issue-41-man0g1-souls-gone-wild branch July 9, 2026 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Man0g1 "Souls Gone Wild" (Gridania MSQ #2): implement stubbed escort duty + fix SEQ_003/journal-marker/onNotice bugs

1 participant